Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
AT_CellularSMS.cpp
00001 /* 00002 * Copyright (c) 2017, Arm Limited and affiliates. 00003 * SPDX-License-Identifier: Apache-2.0 00004 * 00005 * Licensed under the Apache License, Version 2.0 (the "License"); 00006 * you may not use this file except in compliance with the License. 00007 * You may obtain a copy of the License at 00008 * 00009 * http://www.apache.org/licenses/LICENSE-2.0 00010 * 00011 * Unless required by applicable law or agreed to in writing, software 00012 * distributed under the License is distributed on an "AS IS" BASIS, 00013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00014 * See the License for the specific language governing permissions and 00015 * limitations under the License. 00016 */ 00017 00018 #if MBED_CONF_CELLULAR_USE_SMS 00019 00020 #include <time.h> 00021 #include <stdlib.h> 00022 #include <stdio.h> 00023 #include "ThisThread.h" 00024 #include "AT_CellularSMS.h" 00025 #include "CellularUtil.h" 00026 #include "CellularLog.h" 00027 00028 using namespace mbed_cellular_util; 00029 using namespace mbed; 00030 using namespace std; 00031 using namespace rtos; 00032 00033 #define CTRL_Z "\x1a" 00034 #define ESC "\x1b" 00035 00036 const uint8_t SMS_STATUS_SIZE = 12 + 1; 00037 const uint8_t FIRST_OCTET_DELIVER_SUBMIT = 17; 00038 const uint8_t TP_VALIDITY_PERIOD_24_HOURS = 167; 00039 const uint8_t TP_PROTOCOL_IDENTIFIER = 0; 00040 const uint8_t SMS_DATA_CODING_SCHEME = 0; 00041 00042 const uint8_t SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE = 134; 00043 const uint8_t SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE = 153; 00044 #define NVAM '?' // Not Valid ascii, ISO-8859-1 mark 00045 00046 // mapping table from 7-bit GSM to ascii (ISO-8859-1) 00047 static const unsigned char gsm_to_ascii[] = { 00048 64, // 0 00049 163, // 1 00050 36, // 2 00051 165, // 3 00052 232, // 4 00053 233, // 5 00054 249, // 6 00055 236, // 7 00056 242, // 8 00057 199, // 9 00058 10, // 10 00059 216, // 11 00060 248, // 12 00061 13, // 13 00062 197, // 14 00063 229, // 15 00064 NVAM, // 16 00065 95, // 17 00066 NVAM, // 18 00067 NVAM, // 19 00068 NVAM, // 20 00069 NVAM, // 21 00070 NVAM, // 22 00071 NVAM, // 23 00072 NVAM, // 24 00073 NVAM, // 25 00074 NVAM, // 26 00075 27, // 27 00076 198, // 28 00077 230, // 29 00078 223, // 30 00079 201, // 31 00080 32, // 32 00081 33, // 33 00082 34, // 34 00083 35, // 35 00084 164, // 36 00085 37, // 37 00086 38, // 38 00087 39, // 39 00088 40, // 40 00089 41, // 41 00090 42, // 42 00091 43, // 43 00092 44, // 44 00093 45, // 45 00094 46, // 46 00095 47, // 47 00096 48, // 48 00097 49, // 49 00098 50, // 50 00099 51, // 51 00100 52, // 52 00101 53, // 53 00102 54, // 54 00103 55, // 55 00104 56, // 56 00105 57, // 57 00106 58, // 58 00107 59, // 59 00108 60, // 60 00109 61, // 61 00110 62, // 62 00111 63, // 63 00112 161, // 64 00113 65, // 65 00114 66, // 66 00115 67, // 67 00116 68, // 68 00117 69, // 69 00118 70, // 70 00119 71, // 71 00120 72, // 72 00121 73, // 73 00122 74, // 74 00123 75, // 75 00124 76, // 76 00125 77, // 77 00126 78, // 78 00127 79, // 79 00128 80, // 80 00129 81, // 81 00130 82, // 82 00131 83, // 83 00132 84, // 84 00133 85, // 85 00134 86, // 86 00135 87, // 87 00136 88, // 88 00137 89, // 89 00138 90, // 90 00139 196, // 91 00140 214, // 92 00141 209, // 93 00142 220, // 94 00143 167, // 95 00144 191, // 96 00145 97, // 97 00146 98, // 98 00147 99, // 99 00148 100, // 100 00149 101, // 101 00150 102, // 102 00151 103, // 103 00152 104, // 104 00153 105, // 105 00154 106, // 106 00155 107, // 107 00156 108, // 108 00157 109, // 109 00158 110, // 110 00159 111, // 111 00160 112, // 112 00161 113, // 113 00162 114, // 114 00163 115, // 115 00164 116, // 116 00165 117, // 117 00166 118, // 118 00167 119, // 119 00168 120, // 120 00169 121, // 121 00170 122, // 122 00171 228, // 123 00172 246, // 124 00173 241, // 125 00174 252, // 126 00175 224 // 127 00176 }; 00177 00178 const int GSM_TO_ASCII_TABLE_SIZE = sizeof(gsm_to_ascii) / sizeof(gsm_to_ascii[0]); 00179 00180 AT_CellularSMS::AT_CellularSMS(ATHandler &at, AT_CellularDevice &device) : _cb(0), _mode(CellularSMSMmodeText), 00181 _use_8bit_encoding(false), _sim_wait_time(0), _sms_message_ref_number(1), 00182 _sms_info(NULL), _at(at), _device(device) 00183 { 00184 } 00185 00186 AT_CellularSMS::~AT_CellularSMS() 00187 { 00188 } 00189 00190 void AT_CellularSMS::cmt_urc() 00191 { 00192 tr_debug("CMT_URC called"); 00193 //+CMT: <oa>,[<alpha>],<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data> 00194 (void)_at.consume_to_stop_tag(); 00195 // call user defined callback function 00196 if (_cb) { 00197 _cb(); 00198 } else { 00199 tr_warn("cmt_urc, no user defined callback for receiving sms!"); 00200 } 00201 } 00202 00203 void AT_CellularSMS::cmti_urc() 00204 { 00205 //+CMTI: <mem>,<index>, 00206 tr_debug("CMTI_URC called"); 00207 // call user defined callback function 00208 if (_cb) { 00209 _cb(); 00210 } else { 00211 tr_warn("cmti_urc, no user defined callback for receiving sms!"); 00212 } 00213 } 00214 00215 nsapi_error_t AT_CellularSMS::set_cnmi() 00216 { 00217 if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CNMI)) { 00218 return NSAPI_ERROR_UNSUPPORTED ; 00219 } 00220 00221 return _at.at_cmd_discard("+CNMI", "=2,1"); 00222 } 00223 00224 nsapi_error_t AT_CellularSMS::set_cmgf(int msg_format) 00225 { 00226 if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CMGF)) { 00227 return NSAPI_ERROR_UNSUPPORTED ; 00228 } 00229 00230 return _at.at_cmd_discard("+CMGF", "=", "%d", msg_format); 00231 } 00232 00233 nsapi_error_t AT_CellularSMS::set_csmp(int fo, int vp, int pid, int dcs) 00234 { 00235 if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CSMP)) { 00236 return NSAPI_ERROR_UNSUPPORTED ; 00237 } 00238 00239 return _at.at_cmd_discard("+CSMP", "=", "%d%d%d%d", fo, vp, pid, dcs); 00240 } 00241 00242 nsapi_error_t AT_CellularSMS::set_csdh(int show_header) 00243 { 00244 if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CSDH)) { 00245 return NSAPI_ERROR_UNSUPPORTED ; 00246 } 00247 00248 return _at.at_cmd_discard("+CSDH", "=", "%d", show_header); 00249 } 00250 00251 nsapi_error_t AT_CellularSMS::initialize(CellularSMSMmode mode, 00252 CellularSMSEncoding encoding) 00253 { 00254 _use_8bit_encoding = (encoding == CellularSMSEncoding8Bit); 00255 00256 _at.set_urc_handler("+CMTI:", callback(this, &AT_CellularSMS::cmti_urc)); 00257 _at.set_urc_handler("+CMT:", callback(this, &AT_CellularSMS::cmt_urc)); 00258 00259 _at.lock(); 00260 set_cnmi(); //set new SMS indication 00261 set_cmgf(mode); //set message format/PDU 00262 00263 if (mode == CellularSMSMmodeText) { 00264 set_csmp(FIRST_OCTET_DELIVER_SUBMIT, TP_VALIDITY_PERIOD_24_HOURS, TP_PROTOCOL_IDENTIFIER, 00265 SMS_DATA_CODING_SCHEME); //set Set Text Mode Parameters(default values for SMS-SUBMIT and RECEIVE) 00266 set_csdh(1);//set header extra info as it's needed 00267 } 00268 00269 _mode = mode; 00270 00271 return _at.unlock_return_error(); 00272 } 00273 00274 void AT_CellularSMS::set_extra_sim_wait_time(int sim_wait_time) 00275 { 00276 _sim_wait_time = sim_wait_time; 00277 } 00278 00279 char *AT_CellularSMS::create_pdu(const char *phone_number, const char *message, uint8_t message_length, uint8_t msg_parts, 00280 uint8_t msg_part_number, uint8_t &header_size) 00281 { 00282 int totalPDULength = 0; 00283 const bool is_number_international = (phone_number[0] == '+'); 00284 if (is_number_international) { 00285 ++phone_number; 00286 } 00287 int number_len = strlen(phone_number); 00288 00289 totalPDULength += number_len; 00290 if (number_len & 0x01) { // if phone number length is not even length we must pad it and so +1 00291 totalPDULength += 1; 00292 } 00293 00294 totalPDULength += 16; // all other than phone number and message length 00295 if (msg_parts > 1) {// add more space for UDH 00296 totalPDULength += 12; 00297 } 00298 // there might be need for padding so some more space 00299 totalPDULength += 2; 00300 00301 // 8-bit message, converted to hex so it will take twice as much space 00302 totalPDULength += message_length * 2; 00303 00304 // terminating nullbyte, because callers use strlen() to find out PDU size 00305 totalPDULength += 1; 00306 00307 char *pdu = new char[totalPDULength]; 00308 memset(pdu, 0, totalPDULength); 00309 int x = 0; 00310 // See more how to create PDU from 3GPP specification 23040 00311 // first two define that we use service center number which is set with +CSCA 00312 pdu[x++] = '0'; 00313 pdu[x++] = '0'; 00314 // First Octet of the TPDU. 41 means SMS SUBMIT, no validity period, no status report, use User Data Header. 00315 // 01 means SMS SUBMIT, no validity period, no status report, NO User Data Header. 00316 if (msg_parts > 1) { // concatenated, must use UDH 00317 pdu[x++] = '4'; 00318 } else { 00319 pdu[x++] = '0'; 00320 } 00321 pdu[x++] = '1'; 00322 // assign a message reference automatically. We have defined TP-RD bit as 0 so duplicates are not rejected. 00323 pdu[x++] = '0'; 00324 pdu[x++] = '0'; 00325 // [6] and [7] Length of the Destination Phone Number 00326 int_to_hex_str(number_len, pdu + x); 00327 x += 2; 00328 // Type of the Destination Phone Number 00329 if (is_number_international) { 00330 pdu[x++] = '9'; // international 00331 } else { 00332 pdu[x++] = '8'; // unknown 00333 } 00334 pdu[x++] = '1'; 00335 00336 // phone number as reverse nibble encoded 00337 int i = 0; 00338 for (; i < number_len; i += 2) { 00339 if (i + 1 == number_len) { 00340 pdu[x++] = 'f'; 00341 } else { 00342 pdu[x++] = phone_number[i + 1]; 00343 } 00344 pdu[x++] = phone_number[i]; 00345 } 00346 00347 // Protocol Identifier 00348 pdu[x++] = '0'; 00349 pdu[x++] = '0'; 00350 // Data Coding Scheme, GSM 7-bit default alphabet = '00', 8-bit '04' 00351 pdu[x++] = '0'; 00352 00353 if (_use_8bit_encoding) { 00354 pdu[x++] = '4'; 00355 } else { 00356 pdu[x++] = '0'; 00357 } 00358 00359 uint8_t udhlen = 0; 00360 // Length can be update after we have created PDU, store position for later use. 00361 int lengthPos = x; 00362 x += 2; 00363 00364 int paddingBits = 0; 00365 if (msg_parts > 1) { // concatenated, must use UDH 00366 // user data header length in chars 00367 pdu[x++] = '0'; 00368 udhlen = 6; // udh length in chars (5) + udhl length in chars 00369 pdu[x++] = '5'; 00370 // Information element identifier 00371 pdu[x++] = '0'; 00372 pdu[x++] = '0'; 00373 // Information element data length 00374 pdu[x++] = '0'; 00375 pdu[x++] = '3'; 00376 // A reference number (must be the same for all parts of the same larger messages) 00377 int_to_hex_str(_sms_message_ref_number & 0xFF, pdu + x); 00378 x += 2; 00379 // How many parts does this message have? 00380 int_to_hex_str(msg_parts, pdu + x); 00381 x += 2; 00382 // this is a part number 00383 int_to_hex_str(msg_part_number, pdu + x); 00384 x += 2; 00385 00386 // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary 00387 paddingBits = (udhlen * 8) % 7; 00388 if (paddingBits) { 00389 paddingBits = 7 - paddingBits; 00390 udhlen += 1; 00391 } 00392 } 00393 00394 if (_use_8bit_encoding) { 00395 char_str_to_hex_str(message, message_length, pdu + x); 00396 } else { 00397 // we might need to send zero length sms 00398 if (message_length) { 00399 if (pack_7_bit_gsm_and_hex(message, message_length, pdu + x, paddingBits) == 0) { 00400 delete [] pdu; 00401 return NULL; 00402 } 00403 } 00404 } 00405 00406 // now we know the correct length of the UDL (User Data Length) 00407 int_to_hex_str(message_length + udhlen, pdu + lengthPos); 00408 header_size = x; 00409 00410 return pdu; 00411 } 00412 00413 nsapi_size_or_error_t AT_CellularSMS::send_sms(const char *phone_number, const char *message, int msg_len) 00414 { 00415 int single_sms_max_length = _use_8bit_encoding ? 00416 SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE : 00417 SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE; 00418 if ((_mode == CellularSMSMmodeText && msg_len > single_sms_max_length) || !phone_number) { 00419 return NSAPI_ERROR_PARAMETER ; 00420 } 00421 00422 _at.lock(); 00423 00424 int write_size = 0; 00425 00426 ThisThread::sleep_for(_sim_wait_time); 00427 00428 if (_mode == CellularSMSMmodeText) { 00429 _at.cmd_start_stop("+CMGS", "=", "%s", phone_number); 00430 00431 ThisThread::sleep_for(_sim_wait_time); 00432 _at.resp_start("> ", true); 00433 00434 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 00435 write_size = _at.write_bytes((uint8_t *)message, msg_len); 00436 if (write_size < msg_len) { 00437 // sending can be cancelled by giving <ESC> character (IRA 27). 00438 _at.cmd_start(ESC); 00439 _at.cmd_stop(); 00440 _at.unlock(); 00441 return write_size; 00442 } 00443 // <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body. 00444 _at.cmd_start(CTRL_Z); 00445 _at.cmd_stop(); 00446 _at.resp_start("+CMGS:"); 00447 _at.resp_stop(); 00448 } 00449 } else { 00450 // supports uncompressed 8 bit data and GSM 7 bit default alphabet data. Current implementation uses only 00451 // GSM 7 bit default but support is done for 8 bit data. 00452 int sms_count; 00453 int concatenated_sms_length = _use_8bit_encoding ? 00454 SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE : 00455 SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE; 00456 00457 if (msg_len <= single_sms_max_length) { 00458 // single message 00459 sms_count = 1; 00460 } else { 00461 // concatenated message 00462 sms_count = msg_len / concatenated_sms_length; 00463 if (msg_len % concatenated_sms_length != 0) { 00464 sms_count++; 00465 } 00466 } 00467 00468 int remaining_len = msg_len; 00469 int pdu_len; 00470 int msg_write_len = 0; 00471 uint8_t header_len; 00472 char *pdu_str; 00473 for (int i = 0; i < sms_count; i++) { 00474 00475 header_len = 0; 00476 if (sms_count == 1) { 00477 pdu_len = msg_len; 00478 } else { 00479 pdu_len = remaining_len > concatenated_sms_length ? concatenated_sms_length : remaining_len; 00480 } 00481 00482 pdu_str = create_pdu(phone_number, message + i * concatenated_sms_length, pdu_len, 00483 sms_count, i + 1, header_len); 00484 if (!pdu_str) { 00485 _at.unlock(); 00486 return NSAPI_ERROR_NO_MEMORY ; 00487 } 00488 pdu_len = strlen(pdu_str); 00489 00490 // 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 00491 00492 _at.cmd_start_stop("+CMGS", "=", "%d", (pdu_len - 2) / 2); 00493 00494 ThisThread::sleep_for(_sim_wait_time); 00495 _at.resp_start("> ", true); 00496 00497 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 00498 write_size = _at.write_bytes((uint8_t *)pdu_str, pdu_len); 00499 if (write_size < pdu_len) { 00500 // calculate exact size of what we have send 00501 if (write_size <= header_len) { 00502 // managed only to write header or some of it so actual msg write size in this iteration is 0 00503 write_size = 0; 00504 } else { 00505 write_size = (write_size - header_len) / 2; // as hex encoded so divide by two 00506 } 00507 msg_write_len += write_size; 00508 00509 // sending can be cancelled by giving <ESC> character (IRA 27). 00510 _at.cmd_start(ESC); 00511 _at.cmd_stop(); 00512 _at.unlock(); 00513 delete [] pdu_str; 00514 return msg_write_len; 00515 } 00516 00517 // <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body. 00518 _at.cmd_start(CTRL_Z); 00519 _at.cmd_stop(); 00520 _at.resp_start("+CMGS:"); 00521 _at.resp_stop(); 00522 } 00523 delete [] pdu_str; 00524 remaining_len -= concatenated_sms_length; 00525 if (_at.get_last_error() != NSAPI_ERROR_OK ) { 00526 return _at.unlock_return_error(); 00527 } 00528 } 00529 } 00530 00531 _sms_message_ref_number++; 00532 nsapi_error_t ret = _at.get_last_error(); 00533 _at.unlock(); 00534 00535 return (ret == NSAPI_ERROR_OK ) ? msg_len : ret; 00536 } 00537 00538 void AT_CellularSMS::set_sms_callback(Callback<void()> func) 00539 { 00540 _cb = func; 00541 } 00542 00543 nsapi_error_t AT_CellularSMS::set_cpms(const char *memr, const char *memw, const char *mems) 00544 { 00545 return _at.at_cmd_discard("+CPMS", "=", "%s%s%s", memr, memw, mems); 00546 } 00547 00548 nsapi_error_t AT_CellularSMS::set_csca(const char *sca, int type) 00549 { 00550 return _at.at_cmd_discard("+CSCA", "=", "%s%d", sca, type); 00551 } 00552 00553 nsapi_size_or_error_t AT_CellularSMS::set_cscs(const char *chr_set) 00554 { 00555 return _at.at_cmd_discard("+CSCS", "=", "%s", chr_set); 00556 } 00557 00558 nsapi_error_t AT_CellularSMS::delete_sms(sms_info_t *sms) 00559 { 00560 _at.lock(); 00561 for (int i = 0; i < sms->parts; i++) { 00562 _at.at_cmd_discard("+CMGD", "=", "%d", sms->msg_index[i]); 00563 } 00564 00565 return _at.unlock_return_error(); 00566 } 00567 00568 // we need this as for example concatenated sms can get different sms reference numbers 00569 // if for example last part take much more time to arrive. This situation happened while testing. 00570 // What this means that after this we can't read another sms because we always read the oldest sms 00571 // that was corrupted. So we need to have delete all messages. 00572 nsapi_error_t AT_CellularSMS::delete_all_messages() 00573 { 00574 return _at.at_cmd_discard("+CMGD", "=1,4"); 00575 } 00576 00577 // read msg in text mode 00578 nsapi_size_or_error_t AT_CellularSMS::read_sms_from_index(int msg_index, char *buf, uint16_t len, char *phone_num, 00579 char *time_stamp) 00580 { 00581 /* 00582 * +CMGR: <stat>,<oa>,<alpha>,<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data><CR><LF>OK<CR><LF> 00583 */ 00584 ThisThread::sleep_for(_sim_wait_time); 00585 _at.cmd_start_stop("+CMGR", "=", "%d", msg_index); 00586 00587 // TODO: NOTE: If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), 00588 // 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. 00589 // for now we support sms reading of received messages 00590 int buf_len = 0; 00591 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 00592 char status[SMS_STATUS_SIZE]; 00593 // first we read msg status and with that we can decide how the rest of message format 00594 _at.resp_start("+CMGR:"); 00595 00596 if (_at.info_resp()) { 00597 _at.read_string(status, SMS_STATUS_SIZE); 00598 uint16_t status_len = strlen(status); 00599 if (((status_len == sizeof("REC READ") - 1) && memcmp("REC READ", status, status_len) == 0) 00600 || ((status_len == sizeof("REC UNREAD") - 1) && memcmp("REC UNREAD", status, status_len) == 0)) { 00601 // Received message 00602 if (phone_num) { 00603 _at.read_string(phone_num, SMS_MAX_PHONE_NUMBER_SIZE); 00604 } else { 00605 _at.skip_param(); // <oa>,<alpha> 00606 } 00607 _at.skip_param(); // <alpha> 00608 if (time_stamp) { 00609 int len = _at.read_string(time_stamp, SMS_MAX_TIME_STAMP_SIZE); 00610 if (len < (SMS_MAX_TIME_STAMP_SIZE - 2)) { 00611 time_stamp[len++] = ','; 00612 _at.read_string(&time_stamp[len], SMS_MAX_TIME_STAMP_SIZE - len); 00613 } 00614 } 00615 (void)_at.consume_to_stop_tag(); // consume until <CR><LF> 00616 if (buf) { 00617 _at.read_string(buf, len); 00618 buf_len = strlen(buf); 00619 } 00620 } 00621 } 00622 _at.resp_stop(); 00623 } 00624 00625 return (_at.get_last_error() == NSAPI_ERROR_OK ) ? buf_len : _at.get_last_error(); 00626 } 00627 00628 // read msg in PDU mode 00629 nsapi_size_or_error_t AT_CellularSMS::read_sms(sms_info_t *sms, char *buf, char *phone_num, char *time_stamp) 00630 { 00631 // +CMGR: <stat>,[<alpha>],<length><CR><LF><pdu> 00632 int index; 00633 if (sms->parts == sms->parts_added) { 00634 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. 00635 int status; 00636 int msg_len; 00637 index = 0; 00638 int pduSize; 00639 00640 for (int i = 0; i < sms->parts; i++) { 00641 ThisThread::sleep_for(_sim_wait_time); 00642 _at.cmd_start_stop("+CMGR", "=", "%d", sms->msg_index[i]); 00643 _at.resp_start("+CMGR:"); 00644 00645 if (_at.info_resp()) { 00646 status = _at.read_int(); 00647 _at.skip_param(); // <alpha> 00648 if ((_at.get_last_error() == NSAPI_ERROR_OK ) && (status == 0 || status == 1)) { 00649 msg_len = _at.read_int(); 00650 if (msg_len > 0) { 00651 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 00652 pdu = new char[pduSize]; 00653 memset(pdu, 0, pduSize); 00654 if (!pdu) { 00655 _at.resp_stop(); 00656 return NSAPI_ERROR_NO_MEMORY ; 00657 } 00658 _at.read_string(pdu, pduSize, true); 00659 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 00660 msg_len = get_data_from_pdu(pdu, NULL, NULL, phone_num, buf + index); 00661 if (msg_len >= 0) { // we need to allow zero length messages 00662 index += msg_len; 00663 } else { 00664 delete [] pdu; 00665 _at.resp_stop(); 00666 return -1; 00667 } 00668 } 00669 delete [] pdu; 00670 } 00671 } 00672 } 00673 _at.resp_stop(); 00674 } 00675 00676 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 00677 if (time_stamp) { 00678 strcpy(time_stamp, sms->date); 00679 } 00680 buf[index] = '\0'; 00681 } 00682 } else { 00683 tr_warn("NOT all concatenated parts were received..."); 00684 index = SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ; 00685 } 00686 00687 return index; 00688 } 00689 00690 nsapi_size_or_error_t AT_CellularSMS::get_sms(char *buf, uint16_t len, char *phone_num, uint16_t phone_len, 00691 char *time_stamp, uint16_t time_len, int *buf_size) 00692 { 00693 // validate buffer sizes already here to avoid any necessary function calls and locking of _at 00694 if ((phone_num && phone_len < SMS_MAX_PHONE_NUMBER_SIZE) || (time_stamp && time_len < SMS_MAX_TIME_STAMP_SIZE) || 00695 buf == NULL) { 00696 return NSAPI_ERROR_PARAMETER ; 00697 } 00698 00699 _at.lock(); 00700 00701 nsapi_size_or_error_t err = list_messages(); 00702 if (err == NSAPI_ERROR_OK ) { 00703 // we return the oldest sms and delete it after successful read 00704 sms_info_t *info = get_oldest_sms_index(); 00705 00706 if (info) { 00707 if (info->msg_size > len) { 00708 tr_warn("Given buf too small, len is: %d but is must be: %d", len, info->msg_size); 00709 if (buf_size) { 00710 *buf_size = info->msg_size; 00711 } 00712 free_linked_list(); 00713 _at.unlock(); 00714 return NSAPI_ERROR_PARAMETER ; 00715 } 00716 00717 if (_mode == CellularSMSMmodePDU) { 00718 err = read_sms(info, buf, phone_num, time_stamp); 00719 } else { 00720 err = read_sms_from_index(info->msg_index[0], buf, len, phone_num, time_stamp); 00721 } 00722 00723 if (err > 0) { 00724 int delerr = delete_sms(info); 00725 if (delerr) { 00726 err = delerr; 00727 } 00728 } 00729 } else { 00730 // No messages were found, return -1 00731 err = -1; 00732 } 00733 } 00734 00735 free_linked_list(); 00736 00737 _at.unlock(); 00738 00739 // update error only when there really was an error, otherwise we return the length 00740 if (_at.get_last_error()) { 00741 err = _at.get_last_error(); 00742 } 00743 return err; 00744 } 00745 00746 nsapi_size_or_error_t AT_CellularSMS::get_data_from_pdu(const char *pdu, sms_info_t *info, int *part_number, 00747 char *phone_number, char *msg) 00748 { 00749 int index = 0; 00750 int tmp; 00751 bool userDataHeader; 00752 int oaLength; 00753 int dataScheme; 00754 nsapi_size_or_error_t err = NSAPI_ERROR_OK ; 00755 00756 // read Length of the SMSC information 00757 oaLength = hex_str_to_int(pdu, 2); 00758 index += 2; // length we just read 00759 index += oaLength * 2; // skip service center number 00760 00761 // read first the lower part of first octet as there is message type 00762 index++; 00763 tmp = hex_str_to_int(pdu + index, 1); 00764 //ThisThread::sleep_for(200); 00765 if ((tmp & 0x03) == 0) {// SMS-DELIVER type, last two bits should be zero 00766 // UDH present? Check from first octets higher part 00767 tmp = hex_str_to_int(pdu + (--index), 1); 00768 userDataHeader = ((tmp & 0x04) == 0) ? false : true; 00769 00770 index += 2; // we just read the high bits of first octet so move +2 00771 // originating address length 00772 oaLength = hex_str_to_int(pdu + index, 2); 00773 index += 2; // add index over address length 00774 int type = hex_str_to_int(pdu + index, 1); 00775 index += 2; // add index over type 00776 if (phone_number) { 00777 // phone number as reverse nibble encoded 00778 int a = 0, field_length = oaLength; 00779 00780 if (type == 9) { 00781 //add the plus sign in case of international number (23.040 chapter address fields) 00782 phone_number[a++] = '+'; 00783 field_length++; 00784 } 00785 00786 for (; a < field_length; a += 2) { 00787 if ((a + 1) == field_length) { 00788 phone_number[a] = pdu[index + 1]; 00789 index++; 00790 } else { 00791 phone_number[a] = pdu[index + 1]; 00792 phone_number[a + 1] = pdu[index]; 00793 index += 2; 00794 } 00795 } 00796 phone_number[field_length] = '\0'; 00797 } else { 00798 index += oaLength; 00799 } 00800 00801 00802 if (oaLength & 0x01) { // if phone number length is odd then it has padded F so skip that 00803 index++; 00804 } 00805 index += 2; // skip TP-Protocol identifier 00806 00807 dataScheme = hex_str_to_int(pdu + index, 2); 00808 index += 2; // skip TP-Data-Coding-Scheme 00809 00810 // next one is date, it's length is 7 octets according to 3GPP TS 23.040 00811 // create time string 00812 if (info) { 00813 int i = 0; 00814 // year 00815 info->date[i++] = pdu[index + 1]; 00816 info->date[i++] = pdu[index]; 00817 index += 2; 00818 info->date[i++] = '/'; 00819 // month 00820 info->date[i++] = pdu[index + 1]; 00821 info->date[i++] = pdu[index]; 00822 index += 2; 00823 info->date[i++] = '/'; 00824 // Day 00825 info->date[i++] = pdu[index + 1]; 00826 info->date[i++] = pdu[index]; 00827 index += 2; 00828 info->date[i++] = ','; 00829 // Hour 00830 info->date[i++] = pdu[index + 1]; 00831 info->date[i++] = pdu[index]; 00832 index += 2; 00833 info->date[i++] = ':'; 00834 // Minute 00835 info->date[i++] = pdu[index + 1]; 00836 info->date[i++] = pdu[index]; 00837 index += 2; 00838 info->date[i++] = ':'; 00839 // Second 00840 info->date[i++] = pdu[index + 1]; 00841 info->date[i++] = pdu[index]; 00842 index += 2; 00843 // timezone related to GMT. pdu[index+1] most significant bit indicates the sign related to gmt 00844 tmp = hex_str_to_int(pdu + index + 1, 1); 00845 if (tmp & 0x08) { 00846 info->date[i++] = '-'; 00847 } else { 00848 info->date[i++] = '+'; 00849 } 00850 00851 // pdu[index+1 & 0x07 is the most significant bits of the timezone 00852 // pdu [index] is the least significant bits 00853 info->date[i++] = '0' + (tmp & 0x07); 00854 info->date[i++] = pdu[index]; 00855 info->date[i] = '\0'; 00856 index += 2; 00857 } else { 00858 index += 14; 00859 } 00860 00861 int udl = hex_str_to_int(pdu + index, 2); 00862 index += 2; 00863 00864 int paddingBits = 0; 00865 int partnro = 1; 00866 if (userDataHeader) { 00867 // we need to read User Defined Header to know what part number this message is. 00868 index += read_udh_from_pdu(pdu + index, info, partnro, paddingBits); 00869 } 00870 00871 if (part_number) { 00872 *part_number = partnro; 00873 } 00874 00875 if (msg) { 00876 // we are reading the message 00877 err = read_pdu_payload(pdu + index, udl, dataScheme, msg, paddingBits); 00878 } else { 00879 if (dataScheme == 0x00) { 00880 // when listing messages we need to calculated length. Other way would be unpacking the whole message. 00881 err = strlen(pdu + index) >> 1; 00882 err *= 8; 00883 err /= 7; 00884 } else if (dataScheme == 0x04) { 00885 err = strlen(pdu + index) >> 1; 00886 } else { 00887 return NSAPI_ERROR_UNSUPPORTED ; 00888 } 00889 } 00890 00891 return err; 00892 } else { 00893 // message was not DELIVER so discard it 00894 return NSAPI_ERROR_UNSUPPORTED ; 00895 } 00896 } 00897 00898 // read params from User Defined Header 00899 int AT_CellularSMS::read_udh_from_pdu(const char *pdu, sms_info_t *info, int &part_number, int &padding_bits) 00900 { 00901 00902 int index = 0; 00903 int udhLength = hex_str_to_int(pdu, 2); 00904 index += 2; 00905 00906 // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary 00907 padding_bits = ((udhLength + 1) * 8) % 7; // +1 is for udhLength itself 00908 00909 if (padding_bits) { 00910 padding_bits = 7 - padding_bits; 00911 } else { 00912 padding_bits = 0; 00913 } 00914 00915 int tmp = hex_str_to_int(pdu + index, 2); 00916 index += 4; 00917 00918 if (tmp == 0) { // 8-bit reference number 00919 if (info) { 00920 info->msg_ref_number = (uint16_t)hex_str_to_int(pdu + index, 2); 00921 } 00922 index += 2; 00923 } else { // 16-bit reference number 00924 if (info) { 00925 info->msg_ref_number = (uint16_t)hex_str_to_int(pdu + index + 2, 2); 00926 tmp = hex_str_to_int(pdu + index, 2); 00927 info->msg_ref_number |= (tmp << 8); 00928 } 00929 index += 4; 00930 } 00931 00932 if (info) { 00933 info->parts = hex_str_to_int(pdu + index, 2); 00934 } 00935 index += 2; 00936 00937 part_number = hex_str_to_int(pdu + index, 2); 00938 index += 2; 00939 00940 return (udhLength * 2 + 2); // udh in hex and udhl 00941 } 00942 00943 nsapi_size_or_error_t AT_CellularSMS::read_pdu_payload(const char *pdu, int msg_len, int scheme, char *msg, int padding_bits) 00944 { 00945 if (scheme == 0x00) { 00946 // 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii 00947 return unpack_7_bit_gsm_to_str(pdu, strlen(pdu) / 2, msg, padding_bits, msg_len); 00948 } else if (scheme == 0x04) { 00949 // 8bit scheme so just convert hexstring to charstring 00950 return hex_str_to_char_str(pdu, strlen(pdu), msg); 00951 } else { 00952 tr_error("Received unsupported data coding scheme: 0x%02x", scheme); 00953 return NSAPI_ERROR_UNSUPPORTED ; 00954 } 00955 } 00956 00957 void AT_CellularSMS::free_linked_list() 00958 { 00959 sms_info_t *info = _sms_info; 00960 sms_info_t *old; 00961 while (info) { 00962 old = info; 00963 info = info->next_info; 00964 delete old; 00965 } 00966 _sms_info = NULL; 00967 } 00968 00969 void AT_CellularSMS::add_info(sms_info_t *info, int index, int part_number) 00970 { 00971 // check for same message reference id. If found, update it and delete the given info. 00972 // if NOT found then add to the end of the list. 00973 00974 if (!_sms_info) { 00975 info->msg_index[part_number - 1] = index; // part numbering starts from 1 so -1 to put to right index 00976 _sms_info = info; 00977 return; 00978 } 00979 sms_info_t *current = _sms_info; 00980 sms_info_t *prev = NULL; 00981 bool found_msg = false; 00982 while (current) { 00983 prev = current; 00984 // sms messages can have same reference number so additional checks are needed. 00985 // TODO: should we include phone number also? 00986 if (current->msg_ref_number == info->msg_ref_number && current->parts > current->parts_added && 00987 info->parts > info->parts_added) { 00988 // multipart sms, update msg size and index 00989 current->msg_size += info->msg_size; 00990 current->msg_index[part_number - 1] = index; // part numbering starts from 1 so -1 to put to right index 00991 current->parts_added++; 00992 // update oldest part as date 00993 if (compare_time_strings(info->date, current->date) == -1) { 00994 strcpy(current->date, info->date); 00995 } 00996 found_msg = true; 00997 break; 00998 } 00999 current = current->next_info; 01000 } 01001 01002 if (found_msg) { 01003 // info was added to existing item in linked list, must be deleted 01004 delete info; 01005 } else { 01006 // message not found, add to linked list 01007 info->msg_index[part_number - 1] = index; 01008 prev->next_info = info; 01009 } 01010 } 01011 01012 // reads all the messages to the linked list AT_CellularSMS::_sms_info 01013 nsapi_error_t AT_CellularSMS::list_messages() 01014 { 01015 // TODO: NOTE: If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), 01016 // 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. 01017 // for now we assume that only SMS-DELIVER messages are read. 01018 if (_mode == CellularSMSMmodePDU) { 01019 _at.cmd_start_stop("+CMGL", "=4"); 01020 } else { 01021 _at.cmd_start_stop("+CMGL", "=\"ALL\""); 01022 } 01023 01024 sms_info_t *info = NULL; 01025 // init for 1 so that in text mode we will add to the correct place without any additional logic in addInfo() in text mode 01026 int part_number = 1; 01027 int index = 0; 01028 int length = 0; 01029 char *pdu = NULL; 01030 01031 _at.resp_start("+CMGL:"); 01032 while (_at.info_resp()) { 01033 info = new sms_info_t(); 01034 if (_mode == CellularSMSMmodePDU) { 01035 //+CMGL: <index>,<stat>,[<alpha>],<length><CR><LF><pdu>[<CR><LF> 01036 // +CMGL:<index>,<stat>,[<alpha>],<length><CR><LF><pdu> 01037 //[...]] 01038 index = _at.read_int(); 01039 _at.skip_param(2); // <stat>,[<alpha>] 01040 length = _at.read_int(); 01041 length = length * 2 + 20; // *2 as it's hex encoded and +20 as service center number is not included in size given by CMGL 01042 pdu = new char[length]; 01043 memset(pdu, 0, length); 01044 _at.read_string(pdu, length, true); 01045 if (_at.get_last_error() == NSAPI_ERROR_OK ) { 01046 info->msg_size = get_data_from_pdu(pdu, info, &part_number); 01047 } 01048 } else { 01049 // +CMGL: <index>,<stat>,<oa/da>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[<CR><LF> 01050 // +CMGL: <index>,<stat>,<da/oa>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[...]] 01051 index = _at.read_int(); 01052 (void)_at.consume_to_stop_tag(); // consume until <CR><LF> 01053 (void)_at.consume_to_stop_tag(); // consume until <CR><LF> 01054 } 01055 01056 if (index >= 0) { 01057 add_info(info, index, part_number); 01058 } else { 01059 delete info; 01060 info = NULL; 01061 } 01062 delete [] pdu; 01063 pdu = NULL; 01064 } 01065 01066 01067 _at.resp_stop(); 01068 01069 return _at.get_last_error(); 01070 } 01071 01072 AT_CellularSMS::sms_info_t *AT_CellularSMS::get_oldest_sms_index() 01073 { 01074 /* 01075 * Different scenarios when finding the oldest concatenated sms 01076 * 01077 * 1. Find first parts first and it was received first 01078 * 2. Find first parts first and it was NOT received first -> older timestamp might exist in some other part 01079 * 3. Find other than first part first and it was received first 01080 * 4. Find other than first part first and it was NOT received first -> older timestamp might exist in some other part 01081 * 01082 * So must take all message to a linked list and loop that for the oldest 01083 */ 01084 01085 // if text mode we need to read sms with +CMGR because time stamp is optional while looping with +CMGL 01086 sms_info_t *retVal = NULL; 01087 sms_info_t *current = _sms_info; 01088 nsapi_size_or_error_t err = 0; 01089 while (current) { 01090 if (_mode == CellularSMSMmodeText) { 01091 ThisThread::sleep_for(_sim_wait_time); 01092 err = read_sms_from_index(current->msg_index[0], NULL, 0, NULL, current->date); 01093 if (err != 0) { 01094 return NULL; 01095 } 01096 } 01097 01098 if (retVal == NULL) { 01099 retVal = current; 01100 } else if (compare_time_strings(current->date, retVal->date) == -1) { 01101 // found older sms, update return value to oldest 01102 retVal = current; 01103 } 01104 current = current->next_info; 01105 } 01106 01107 return retVal; 01108 } 01109 01110 // if time_string_1 is greater (more fresh date) then return 1, same 0, smaller -1. Error -2 01111 int AT_CellularSMS::compare_time_strings(const char *time_string_1, const char *time_string_2) 01112 { 01113 time_t t1; 01114 time_t t2; 01115 01116 bool success = create_time(time_string_1, &t1) && create_time(time_string_2, &t2); 01117 int retVal = -2; 01118 01119 if (success) { 01120 time_t diff = t1 - t2; 01121 01122 if (diff > 0) { 01123 retVal = 1; 01124 } else if (diff == 0) { 01125 retVal = 0; 01126 } else { 01127 retVal = -1; 01128 } 01129 } 01130 01131 return retVal; 01132 } 01133 01134 bool AT_CellularSMS::create_time(const char *time_string, time_t *time) 01135 { 01136 const int kNumberOfElements = 8; 01137 tm time_struct = { 0 }; 01138 int gmt = 0; 01139 char sign; 01140 bool retVal = false; 01141 01142 if (sscanf(time_string, "%d/%d/%d,%d:%d:%d%c%d", &time_struct.tm_year, &time_struct.tm_mon, &time_struct.tm_mday, 01143 &time_struct.tm_hour, &time_struct.tm_min, &time_struct.tm_sec, &sign, &gmt) == kNumberOfElements) { 01144 *time = mktime(&time_struct); 01145 // add timezone as seconds. gmt is in quarter of hours. 01146 int x = (60 / 4) * 60 * gmt; 01147 if (sign == '+') { 01148 *time += x; 01149 } else { 01150 *time -= x; 01151 } 01152 retVal = true; 01153 } 01154 01155 return retVal; 01156 } 01157 01158 uint16_t AT_CellularSMS::pack_7_bit_gsm_and_hex(const char *str, uint16_t len, char *buf, 01159 int number_of_padding_bit) 01160 { 01161 uint16_t strCnt = 0; 01162 uint16_t i = 0; 01163 uint8_t shift; 01164 char tmp; 01165 01166 if (len == 0) { 01167 return 0; 01168 } 01169 // convert to 7bit gsm first 01170 char *gsm_str = new char[len]; 01171 for (uint16_t y = 0; y < len; y++) { 01172 for (int x = 0; x < GSM_TO_ASCII_TABLE_SIZE; x++) { 01173 if (gsm_to_ascii[x] == static_cast<unsigned char>(str[y])) { 01174 gsm_str[y] = x; 01175 } 01176 } 01177 } 01178 01179 // then packing and converting to hex 01180 if (number_of_padding_bit) { 01181 tmp = gsm_str[strCnt] << number_of_padding_bit; 01182 strCnt++; 01183 char_str_to_hex_str(&tmp, 1, &buf[i * 2]); 01184 i++; 01185 } 01186 01187 while (strCnt < len) { 01188 if (number_of_padding_bit) { 01189 shift = (i + number_of_padding_bit - 2) % 7; 01190 } else { 01191 shift = i % 7; 01192 } 01193 01194 if (strCnt + 1 == len) { 01195 tmp = (gsm_str[strCnt] >> shift); 01196 } else { 01197 tmp = (gsm_str[strCnt] >> shift) | (gsm_str[strCnt + 1] << (7 - shift)); 01198 } 01199 01200 char_str_to_hex_str(&tmp, 1, buf + (i * 2)); 01201 01202 if (shift == 6) { 01203 strCnt++; 01204 } 01205 strCnt++; 01206 i++; 01207 } 01208 01209 delete [] gsm_str; 01210 01211 return i; 01212 } 01213 01214 uint16_t AT_CellularSMS::unpack_7_bit_gsm_to_str(const char *str, int len, char *buf, int padding_bits, 01215 int msg_len) 01216 { 01217 int strCount = 0; 01218 uint16_t decodedCount = 0; 01219 uint8_t shift; 01220 char tmp; 01221 char tmp1; 01222 01223 if (padding_bits) { 01224 hex_to_char(str, tmp); 01225 buf[decodedCount] = gsm_to_ascii[(tmp >> padding_bits) & 0x7F]; 01226 strCount++; 01227 decodedCount++; 01228 } 01229 01230 while (strCount < len) { 01231 shift = (strCount - padding_bits) % 7; 01232 hex_to_char(str + strCount * 2, tmp); 01233 if (shift == 0) { 01234 buf[decodedCount] = gsm_to_ascii[tmp & 0x7F]; 01235 } else if (shift == 6) { 01236 hex_to_char(str + (strCount - 1) * 2, tmp1); 01237 buf[decodedCount] = gsm_to_ascii[(((tmp1 >> 2)) | (tmp << 6)) & 0x7F]; 01238 if (decodedCount + 1 < msg_len) { 01239 hex_to_char(str + strCount * 2, tmp); 01240 decodedCount++; 01241 buf[decodedCount] = gsm_to_ascii[(tmp >> 1) & 0x7F]; 01242 } 01243 } else { 01244 hex_to_char(str + (strCount - 1) * 2, tmp1); 01245 buf[decodedCount] = gsm_to_ascii[(((tmp1 >> (8 - shift))) | ((tmp << shift))) & 0x7F]; 01246 } 01247 01248 strCount++; 01249 decodedCount++; 01250 } 01251 01252 return decodedCount; 01253 } 01254 01255 ATHandler &AT_CellularSMS::get_at_handler() 01256 { 01257 return _at; 01258 } 01259 01260 #endif //MBED_CONF_CELLULAR_USE_SMS
Generated on Tue Jul 12 2022 13:54:02 by
