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