01 |
Содержание:
|
Проверено — автор рекомендует: GSM/GPRS-модуль SIM800L Видео-инструкция о покупке со скидками на Aliexpress |
02 |
В этой статье цикла будут подробно описаны вопросы работы с USSD-запросами и получения ответов на них и их обработки, будет описана работа с кодировкой UCS2, в том числе и в USSD-ответах, а также подробно описан процесс отправки SMS-сообщений в PDU-формате на языках отличных от латиницы (кириллица — русский и пр.). Для полноценной работы с GSM/GPRS-модулем SIM800L понадобится официальный справочник по AT-командам — SIM800 Series_AT Command Manual_V1.10.pdf (4,01 MB).
|
|
03 |
|
|
04 |
Здесь и далее в статье, в примерах с модулем SIM800L используется одна схема: |
|
06 |
USSD-запросы Часто бывает полезно, чтобы GSM-модуль сам отслеживал состояние баланса SIM-карты и вовремя информировал владельца о приближении окончания денег. Как известно самый простой способ узнать об остатке — отправить USSD-запрос. |
USSD (англ. Unstructured Supplementary Service Data) — стандартный сервис в сетях GSM, позволяющий организовать интерактивное взаимодействие между абонентом сети и сервисным приложением в режиме передачи коротких сообщений.
|
07 | На заметку: |
Если в коде не предусмотрена обработка кодировки UCS2 и для работы используется текстовый режим (Text Mode), то необходимо использовать USSD-команды, возвращающие сообщения не на кириллице.
Например, стандартный USSD-запрос проверки баланса для оператора Билайн — *102# вернет ответ в кодировке UCS2 вида: 1 +CUSD: 0, "003100390038002E............02A0033003100390023", 72 Для возврата ответа в текстовом режиме должен использоваться другой USSD-запрос — #102#. Он вернет уже понятный ответ: 1 2 +CUSD: 0, " Vash balans 198.02 r.
Dlya Vas - nedelya besplatnogo SMS-obsh'eniya s druz'yami! Podkl.: *319#", 15 |
|
08 |
Для отправки USSD-запроса существует команда AT+CUSD=<n>[,<str>[,<dcs>]] (по-умолчанию установлена кодировка IRA). |
|
|
10 |
Исполнение команды, в случае корректного её исполнения, вернет ответ OK. Но непосредственно USSD-ответ будет получен в виде незапрашиваемого уведомления +CUSD. Именно его нужно отслеживать и обрабатывать, когда оно придет. Пример кода: |
|
11 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
String _response = ""; // Переменная для хранения ответа модуля
void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
sendATCommand("AT", true); // Отправили AT для настройки скорости обмена данными
_response = sendATCommand("AT+CUSD=1,\"*100#\"", true); // Здесь необходимо указать свой USSD-запрос
}
String sendATCommand(String cmd, bool waiting) {
String _resp = ""; // Переменная для хранения результата
Serial.println(cmd); // Дублируем команду в монитор порта
SIM800.println(cmd); // Отправляем команду модулю
if (waiting) { // Если необходимо дождаться ответа...
_resp = waitResponse(); // ... ждем, когда будет передан ответ
// Если Echo Mode выключен (ATE0), то эти 3 строки можно закомментировать
if (_resp.startsWith(cmd)) { // Убираем из ответа дублирующуюся команду
_resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
}
Serial.println(_resp); // Дублируем ответ в монитор порта
}
return _resp; // Возвращаем результат. Пусто, если проблема
}
String waitResponse() { // Функция ожидания ответа и возврата полученного результата
String _resp = ""; // Переменная для хранения результата
long _timeout = millis() + 10000; // Переменная для отслеживания таймаута (10 секунд)
while (!SIM800.available() && millis() < _timeout) {}; // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
if (SIM800.available()) { // Если есть, что считывать...
_resp = SIM800.readString(); // ... считываем и запоминаем
}
else { // Если пришел таймаут, то...
Serial.println("Timeout..."); // ... оповещаем об этом и...
}
return _resp; // ... возвращаем результат. Пусто, если проблема
}
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
_response.trim(); // Убираем лишние пробелы в начале и конце
Serial.println(_response); // Если нужно выводим в монитор порта
//....
if (_response.startsWith("+CUSD:")) { // Пришло уведомление о USSD-ответе
if (_response.indexOf("\"") > -1) { // Если ответ содержит кавычки, значит есть сообщение (предохранитель от "пустых" USSD-ответов)
String msgBalance = _response.substring(_response.indexOf("\"") + 2); // Получаем непосредственно текст
msgBalance = msgBalance.substring(0, msgBalance.indexOf("\""));
Serial.println("USSD: " + msgBalance); // Выводим полученный ответ
}
}
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
} |
|
13 |
Далее, не составит труда получить необходимую информацию и задать требуемую логику приложения. Функция по «извлечению» состояния баланса из сообщения: |
|
14 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
String balance[5] = { // Несколько строк для примера
"Vash balans 198.02 r.\r\nDlya Vas - nedelya besplatnogo SMS-obsh'eniya s druz'yami! Podkl.: *319#",
"Баланс 200,15 р.",
"Your balance is 1 500.24",
"Баланс вашего счета равен -2 523,94 рубля",
"Задолженность на вашем счете составляет: -542,78"
};
for (int i = 0; i < sizeof(balance) / sizeof(String); i++) {
Serial.println("Строка для извлечения баланса:\r\n" + balance[i] + "\r\n");
Serial.println("Извлеченный баланс: " + (String)getFloatFromString(balance[i]));
Serial.println("--------------\r\n");
}
}
float getFloatFromString(String str) { // Функция извлечения цифр из сообщения - для парсинга баланса из USSD-запроса
bool flag = false;
String result = "";
str.replace(",", "."); // Если в качестве разделителя десятичных используется запятая - меняем её на точку.
for (int i = 0; i < str.length(); i++) {
if (isDigit(str[i]) || (str[i] == (char)46 && flag)) { // Если начинается группа цифр (при этом, на точку без цифр не обращаем внимания),
if (result == "" && i > 0 && (String)str[i - 1] == "-") { // Нельзя забывать, что баланс может быть отрицательным
result += "-"; // Добавляем знак в начале
}
result += str[i]; // начинаем собирать их вместе
if (!flag) flag = true; // Выставляем флаг, который указывает на то, что сборка числа началась.
}
else { // Если цифры закончились и флаг говорит о том, что сборка уже была,
if (str[i] != (char)32) { // Если порядок числа отделен пробелом - игнорируем его, иначе...
if (flag) break; // ...считаем, что все.
}
}
}
return result.toFloat(); // Возвращаем полученное число.
}
void loop() {
} |
|
16 |
Полный пример: |
|
17 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
String _response = ""; // Переменная для хранения ответа модуля
void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
sendATCommand("AT", true); // Отправили AT для настройки скорости обмена данными
// Команды настройки модема при каждом запуске
//_response = sendATCommand("AT+CLIP=1", true); // Включаем АОН
//_response = sendATCommand("AT+DDET=1", true); // Включаем DTMF
//_response = sendATCommand("AT+CMGF=1", true); // Включаем текстовый режим SMS (Text mode)
_response = sendATCommand("AT+CUSD=1,\"*100#\"", true); // Здесь необходимо указать свой USSD-запрос
}
String sendATCommand(String cmd, bool waiting) {
String _resp = ""; // Переменная для хранения результата
Serial.println(cmd); // Дублируем команду в монитор порта
SIM800.println(cmd); // Отправляем команду модулю
if (waiting) { // Если необходимо дождаться ответа...
_resp = waitResponse(); // ... ждем, когда будет передан ответ
// Если Echo Mode выключен (ATE0), то эти 3 строки можно закомментировать
if (_resp.startsWith(cmd)) { // Убираем из ответа дублирующуюся команду
_resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
}
Serial.println(_resp); // Дублируем ответ в монитор порта
}
return _resp; // Возвращаем результат. Пусто, если проблема
}
String waitResponse() { // Функция ожидания ответа и возврата полученного результата
String _resp = ""; // Переменная для хранения результата
long _timeout = millis() + 10000; // Переменная для отслеживания таймаута (10 секунд)
while (!SIM800.available() && millis() < _timeout) {}; // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
if (SIM800.available()) { // Если есть, что считывать...
_resp = SIM800.readString(); // ... считываем и запоминаем
}
else { // Если пришел таймаут, то...
Serial.println("Timeout..."); // ... оповещаем об этом и...
}
return _resp; // ... возвращаем результат. Пусто, если проблема
}
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
_response.trim(); // Убираем лишние пробелы в начале и конце
Serial.println(_response); // Если нужно выводим в монитор порта
//....
if (_response.startsWith("+CUSD:")) { // Пришло уведомление о USSD-ответе
if (_response.indexOf("\"") > -1) { // Если ответ содержит кавычки, значит есть сообщение (предохранитель от "пустых" USSD-ответов)
String msgBalance = _response.substring(_response.indexOf("\"") + 2); // Получаем непосредственно текст
msgBalance = msgBalance.substring(0, msgBalance.indexOf("\""));
Serial.println("USSD: " + msgBalance); // Выводим полученный USSD-ответ
float balance = getFloatFromString(msgBalance); // Извлекаем информацию о балансе
Serial.println("\r\nBalance: " + (String)balance ); // Выводим информацию о балансе
}
}
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
void sendSMS(String phone, String message)
{
String _result = "";
sendATCommand("AT+CMGF=1", true); // Включаем текстовый режима SMS (Text mode)
sendATCommand("AT+CMGS=\"" + phone + "\"", true); // Переходим в режим ввода текстового сообщения
_result = sendATCommand(message + (String)((char)26), true); // После текста отправляем перенос строки и Ctrl+Z
}
float getFloatFromString(String str) { // Функция извлечения цифр из сообщения - для парсинга баланса из USSD-запроса
bool flag = false;
String result = "";
str.replace(",", "."); // Если в качестве разделителя десятичных используется запятая - меняем её на точку.
for (int i = 0; i < str.length(); i++) {
if (isDigit(str[i]) || (str[i] == (char)46 && flag)) { // Если начинается группа цифр (при этом, на точку без цифр не обращаем внимания),
if (result == "" && i > 0 && (String)str[i - 1] == "-") { // Нельзя забывать, что баланс может быть отрицательным
result += "-"; // Добавляем знак в начале
}
result += str[i]; // начинаем собирать их вместе
if (!flag) flag = true; // Выставляем флаг, который указывает на то, что сборка числа началась.
}
else { // Если цифры закончились и флаг говорит о том, что сборка уже была,
if (str[i] != (char)32) { // Если порядок числа отделен пробелом - игнорируем его, иначе...
if (flag) break; // ...считаем, что все.
}
}
}
return result.toFloat(); // Возвращаем полученное число.
} |
|
19 |
Декодирование PDU Ну, а что же делать абонентам Мегафон, для которых провайдер исключил получение данных в некириллическом формате. Ничего страшного в этом нет. |
PDU (англ. Protocol Description Unit (не Protocol Data Unit)) — один из протоколов передачи SMS-сообщений в GSM-сетях
|
20 |
Рассмотрим на примере отправленного USSD-запроса баланса и полученного ответа, что нужно делать, чтобы получить вменяемый результат. Отправляем USSD-запрос: |
|
22 |
Полученный USSD-ответ 003700360031002E003200330440002E представляет из себя строку в кодировке UCS2 (по сути это первый, устаревший вариант кодировки Unicode спецификации до версии 1.1, не поддерживающий суррогатные символы). В данной кодировке каждый символ имеет фиксированную ширину — 2 байта (16 бит), при этом каждый из байт представлен в HEX-формате. Таким образом каждые четыре знака UCS2-последовательности кодируют всего один символ: |
|
24 |
Таблица кодов UCS2 для кириллических символов. С её помощью нетрудно в ручном режиме осуществить преобразование любой строки в UCS2-формате: |
|
25 |
Кодировка UCS2
|
|
26 |
Для того, чтобы автоматически преобразовывать UCS2-строку в читаемый вид, автором написана функция UCS2ToString(): |
|
27 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 String UCS2ToString(String s) { // Функция декодирования UCS2 строки
String result = "";
unsigned char c[5] = ""; // Массив для хранения результата
for (int i = 0; i < s.length() - 3; i += 4) { // Перебираем по 4 символа кодировки
unsigned long code = (((unsigned int)HexSymbolToChar(s[i])) << 12) + // Получаем UNICODE-код символа из HEX представления
(((unsigned int)HexSymbolToChar(s[i + 1])) << 8) +
(((unsigned int)HexSymbolToChar(s[i + 2])) << 4) +
((unsigned int)HexSymbolToChar(s[i + 3]));
if (code <= 0x7F) { // Теперь в соответствии с количеством байт формируем символ
c[0] = (char)code;
c[1] = 0; // Не забываем про завершающий ноль
} else if (code <= 0x7FF) {
c[0] = (char)(0xC0 | (code >> 6));
c[1] = (char)(0x80 | (code & 0x3F));
c[2] = 0;
} else if (code <= 0xFFFF) {
c[0] = (char)(0xE0 | (code >> 12));
c[1] = (char)(0x80 | ((code >> 6) & 0x3F));
c[2] = (char)(0x80 | (code & 0x3F));
c[3] = 0;
} else if (code <= 0x1FFFFF) {
c[0] = (char)(0xE0 | (code >> 18));
c[1] = (char)(0xE0 | ((code >> 12) & 0x3F));
c[2] = (char)(0x80 | ((code >> 6) & 0x3F));
c[3] = (char)(0x80 | (code & 0x3F));
c[4] = 0;
}
result += String((char*)c); // Добавляем полученный символ к результату
}
return (result);
}
unsigned char HexSymbolToChar(char c) {
if ((c >= 0x30) && (c <= 0x39)) return (c - 0x30);
else if ((c >= 'A') && (c <= 'F')) return (c - 'A' + 10);
else return (0);
} |
|
28 |
Использование функции: |
|
29 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
String UCS2ToDecode = "003700360031002E003200330440002E";
Serial.println("Входная строка:");
Serial.println(UCS2ToDecode);
Serial.println("Результат декодирования:");
Serial.println(UCS2ToString(UCS2ToDecode));
}
void loop() {
} |
|
30 |
Результат работы: |
|
32 |
Полный пример запроса баланса посредством USSD, с последующим декодированием ответа и парсингом суммы из полученной строки: |
|
33 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
String _response = ""; // Переменная для хранения ответа модуля
void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
sendATCommand("AT", true); // Отправили AT для настройки скорости обмена данными
_response = sendATCommand("AT+CUSD=1,\"*100#\"", true); // Здесь необходимо указать свой USSD-запрос
}
String sendATCommand(String cmd, bool waiting) {
String _resp = ""; // Переменная для хранения результата
Serial.println(cmd); // Дублируем команду в монитор порта
SIM800.println(cmd); // Отправляем команду модулю
if (waiting) { // Если необходимо дождаться ответа...
_resp = waitResponse(); // ... ждем, когда будет передан ответ
// Если Echo Mode выключен (ATE0), то эти 3 строки можно закомментировать
if (_resp.startsWith(cmd)) { // Убираем из ответа дублирующуюся команду
_resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
}
Serial.println(_resp); // Дублируем ответ в монитор порта
}
return _resp; // Возвращаем результат. Пусто, если проблема
}
String waitResponse() { // Функция ожидания ответа и возврата полученного результата
String _resp = ""; // Переменная для хранения результата
long _timeout = millis() + 10000; // Переменная для отслеживания таймаута (10 секунд)
while (!SIM800.available() && millis() < _timeout) {}; // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
if (SIM800.available()) { // Если есть, что считывать...
_resp = SIM800.readString(); // ... считываем и запоминаем
}
else { // Если пришел таймаут, то...
Serial.println("Timeout..."); // ... оповещаем об этом и...
}
return _resp; // ... возвращаем результат. Пусто, если проблема
}
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
_response.trim(); // Убираем лишние пробелы в начале и конце
Serial.println(_response); // Если нужно выводим в монитор порта
//....
if (_response.startsWith("+CUSD:")) { // Пришло уведомление о USSD-ответе
String msgBalance = _response.substring(_response.indexOf("\"") + 1); // Получаем непосредственно содержимое ответа
msgBalance = msgBalance.substring(0, msgBalance.indexOf("\""));
Serial.println("USSD ответ: " + msgBalance); // Выводим полученный ответ
// Ответ в UCS2-формате - декодируем и извлекаем число
msgBalance = UCS2ToString(msgBalance); // Декодируем ответ
Serial.println("Декодируем: " + msgBalance); // Выводим полученный ответ
float balance = getFloatFromString(msgBalance); // Парсим ответ на содержание числа
Serial.println("Результат парсинга суммы: " + (String(balance))); // Выводим полученный ответ
}
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
String UCS2ToString(String s) { // Функция декодирования UCS2 строки
String result = "";
unsigned char c[5] = ""; // Массив для хранения результата
for (int i = 0; i < s.length() - 3; i += 4) { // Перебираем по 4 символа кодировки
unsigned long code = (((unsigned int)HexSymbolToChar(s[i])) << 12) + // Получаем UNICODE-код символа из HEX представления
(((unsigned int)HexSymbolToChar(s[i + 1])) << 8) +
(((unsigned int)HexSymbolToChar(s[i + 2])) << 4) +
((unsigned int)HexSymbolToChar(s[i + 3]));
if (code <= 0x7F) { // Теперь в соответствии с количеством байт формируем символ
c[0] = (char)code;
c[1] = 0; // Не забываем про завершающий ноль
} else if (code <= 0x7FF) {
c[0] = (char)(0xC0 | (code >> 6));
c[1] = (char)(0x80 | (code & 0x3F));
c[2] = 0;
} else if (code <= 0xFFFF) {
c[0] = (char)(0xE0 | (code >> 12));
c[1] = (char)(0x80 | ((code >> 6) & 0x3F));
c[2] = (char)(0x80 | (code & 0x3F));
c[3] = 0;
} else if (code <= 0x1FFFFF) {
c[0] = (char)(0xE0 | (code >> 18));
c[1] = (char)(0xE0 | ((code >> 12) & 0x3F));
c[2] = (char)(0x80 | ((code >> 6) & 0x3F));
c[3] = (char)(0x80 | (code & 0x3F));
c[4] = 0;
}
result += String((char*)c); // Добавляем полученный символ к результату
}
return (result);
}
unsigned char HexSymbolToChar(char c) {
if ((c >= 0x30) && (c <= 0x39)) return (c - 0x30);
else if ((c >= 'A') && (c <= 'F')) return (c - 'A' + 10);
else return (0);
}
float getFloatFromString(String str) { // Функция извлечения цифр из сообщения - для парсинга баланса из USSD-запроса
bool flag = false;
String result = "";
str.replace(",", "."); // Если в качестве разделителя десятичных используется запятая - меняем её на точку.
for (int i = 0; i < str.length(); i++) {
if (isDigit(str[i]) || (str[i] == (char)46 && flag)) { // Если начинается группа цифр (при этом, на точку без цифр не обращаем внимания),
result += str[i]; // начинаем собирать их вместе
if (!flag) flag = true; // Выставляем флаг, который указывает на то, что сборка числа началась.
}
else { // Если цифры закончились и флаг говорит о том, что сборка уже была,
if (flag) break; // считаем, что все.
}
}
return result.toFloat(); // Возвращаем полученное число.
} |
|
35 |
Отправка SMS на русском языке (кириллице, и не только) — PDU-формат Раз тема UCS2 уже затронута, нельзя обойти обратную операцию — конвертация обычного текста в UCS2-строку. Такую задачу нужно решать при отправке SMS на языках, отличных от латиницы, на кириллице в том числе. Делается это при помощи представления сообщения в, специально созданном для таких целей, PDU-формате. |
PDU (англ. Protocol Description Unit (не Protocol Data Unit)) — один из протоколов передачи SMS-сообщений в GSM-сетях.
Протокол PDU также очень подробно описан на Wikipedia. |
36 |
Но перед тем, как приступить к самой конвертации, нужно разобрать процедуру отправки SMS в PDU-формате, так как она совершенно отличается от отправки SMS в текстовом формате (Text Mode). |
|
|
38 | На заметку: |
В данном разделе будут использоваться следующие обозначения для представления чисел:
Поскольку PDU-пакет будет формироваться из байт в шестнадцатеричном представлении, каждый из них должен быть представлен двумя символами. Например, шестнадцатиричное представление байта 00001011b — Bh (число 11 в десятичном формате — 11d) должно быть дополнено нулем до двух знаков — 0Bh. 00001011b = 11d = 0Bh. |
|
39 | На заметку: |
Для простой конвертации значений в бинарный (двоичный) / десятичный / шестнадцатиричный формат, можно использовать штатный калькулятор Windows в варианте «Программист». Для этого, необходимо ввести значение в существующем представлении и переключить режим отображения на заданный:
|
|
40 |
Для формирования PDU-пакета, необходимо ознакомиться с его структурой — из каких полей он состоит, и какой длины эти поля могут быть. Структура PDU-пакета: |
|
42 |
Описание полей PDU-пакета: |
|
43 |
|
|
44 |
Теперь, для того, чтобы все стало понятно, вместе с подробным описанием каждого из полей PDU-пакета, будет поэтапно показано, как формируется PDU-пакет на примере сообщения с текстом "Тест формата PDU!", отправляемого на номер +7 (890) 123-45-67. |
|
45 |
|
|
46 |
|
|
47 |
|
|
48 |
|
|
49 |
|
Подробнее о значениях поля PID можно прочитать в ETSI GSM 03.40, пункт 9.2.3.9
|
50 |
|
Подробнее о значениях, которые может принимать поле DCS можно почитать здесь DCS Values
|
51 |
|
|
52 |
|
|
53 | На заметку: |
Поскольку каждый символ кодируется двумя байтами, а максимальная длина сообщения 140 байт, то максимальная длина отправляемого сообщения в PDU-формате может составлять 70 символов (140/2=70).
|
|
54 |
|
Кодировка UCS2 |
55 |
Сформированный PDU-пакет будет выглядеть так: 0001000B918779103254F6000822 042204350441044200200444043E0440043C04300442043000200050004400550021. |
|
56 |
Для того, чтобы отправить SMS в PDU-формате, в первую очередь необходимо установить режим отправки PDU: |
|
57 |
1 AT+CMGF=0 |
|
58 |
Далее, командой AT+CMGS=<length> в параметре <length> необходимо передать длину PDU-пакета в байтах без учета поля SCA. Поскольку каждый байт кодируется двумя символами, нужно исключить поле SCA и разделить оставшееся количество символов пополам. Таким образом, значение параметра <length> будет равно 47: |
|
59 |
1 AT+CMGS=47 |
|
60 |
Далее, как и при отправке SMS в текстовом формате — передается PDU-пакет, и в завершение Ctrl+Z. |
|
61 |
Отправка SMS в PDU-формате: Arduino Программная реализация отправки SMS в PDU-формате очень проста, за исключением части, отвечающей за кодирование строки в UCS2-формат. Блок кодирования состоит из нескольких функций и выглядит так: |
|
62 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 // =================================== Блок кодирования строки в представление UCS2 =================================
String StringToUCS2(String s)
{
String output = ""; // Переменная для хранения результата
for (int k = 0; k < s.length(); k++) { // Начинаем перебирать все байты во входной строке
byte actualChar = (byte)s[k]; // Получаем первый байт
unsigned int charSize = getCharSize(actualChar); // Получаем длину символа - кличество байт.
// Максимальная длина символа в UTF-8 - 6 байт плюс завершающий ноль, итого 7
char symbolBytes[charSize + 1]; // Объявляем массив в соответствии с полученным размером
for (int i = 0; i < charSize; i++) symbolBytes[i] = s[k + i]; // Записываем в массив все байты, которыми кодируется символ
symbolBytes[charSize] = '\0'; // Добавляем завершающий 0
unsigned int charCode = symbolToUInt(symbolBytes); // Получаем DEC-представление символа из набора байтов
if (charCode > 0) { // Если все корректно преобразовываем его в HEX-строку
// Остается каждый из 2 байт перевести в HEX формат, преобразовать в строку и собрать в кучу
output += byteToHexString((charCode & 0xFF00) >> 8) +
byteToHexString(charCode & 0xFF);
}
k += charSize - 1; // Передвигаем указатель на начало нового символа
}
return output; // Возвращаем результат
}
unsigned int getCharSize(unsigned char b) { // Функция получения количества байт, которыми кодируется символ
// По правилам кодирования UTF-8, по старшим битам первого октета вычисляется общий размер символа
// 1 0xxxxxxx - старший бит ноль (ASCII код совпадает с UTF-8) - символ из системы ASCII, кодируется одним байтом
// 2 110xxxxx - два старших бита единицы - символ кодируется двумя байтами
// 3 1110xxxx - 3 байта и т.д.
// 4 11110xxx
// 5 111110xx
// 6 1111110x
if (b < 128) return 1; // Если первый байт из системы ASCII, то он кодируется одним байтом
// Дальше нужно посчитать сколько единиц в старших битах до первого нуля - таково будет количество байтов на символ.
// При помощи маски, поочереди исключаем старшие биты, до тех пор пока не дойдет до нуля.
for (int i = 1; i <= 7; i++) {
if (((b << i) & 0xFF) >> 7 == 0) {
return i;
}
}
return 1;
}
unsigned int symbolToUInt(const String& bytes) { // Функция для получения DEC-представления символа
unsigned int charSize = bytes.length(); // Количество байт, которыми закодирован символ
unsigned int result = 0;
if (charSize == 1) {
return bytes[0]; // Если символ кодируется одним байтом, сразу отправляем его
}
else {
unsigned char actualByte = bytes[0];
// У первого байта оставляем только значимую часть 1110XXXX - убираем в начале 1110, оставляем XXXX
// Количество единиц в начале совпадает с количеством байт, которыми кодируется символ - убираем их
// Например (для размера 2 байта), берем маску 0xFF (11111111) - сдвигаем её (>>) на количество ненужных бит (3 - 110) - 00011111
result = actualByte & (0xFF >> (charSize + 1)); // Было 11010001, далее 11010001&(11111111>>(2+1))=10001
// Каждый следующий байт начинается с 10XXXXXX - нам нужны только по 6 бит с каждого последующего байта
// А поскольку остался только 1 байт, резервируем под него место:
result = result << (6 * (charSize - 1)); // Было 10001, далее 10001<<(6*(2-1))=10001000000
// Теперь у каждого следующего бита, убираем ненужные биты 10XXXXXX, а оставшиеся добавляем к result в соответствии с расположением
for (int i = 1; i < charSize; i++) {
actualByte = bytes[i];
if ((actualByte >> 6) != 2) return 0; // Если байт не начинается с 10, значит ошибка - выходим
// В продолжение примера, берется существенная часть следующего байта
// Например, у 10011111 убираем маской 10 (биты в начале), остается - 11111
// Теперь сдвигаем их на 2-1-1=0 сдвигать не нужно, просто добавляем на свое место
result |= ((actualByte & 0x3F) << (6 * (charSize - 1 - i)));
// Было result=10001000000, actualByte=10011111. Маской actualByte & 0x3F (10011111&111111=11111), сдвигать не нужно
// Теперь "пристыковываем" к result: result|11111 (10001000000|11111=10001011111)
}
return result;
}
}
String byteToHexString(byte i) { // Функция преобразования числового значения байта в шестнадцатиричное (HEX)
String hex = String(i, HEX);
if (hex.length() == 1) hex = "0" + hex;
hex.toUpperCase();
return hex;
} |
|
63 | На заметку: |
Для лучшего понимания работы функций кодирования, автор рекомендует ознакомиться с принципами кодирования UTF-8.
|
|
64 |
Использование: |
|
65 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
String strTest = "Тест формата PDU!";
Serial.println("Входная строка:");
Serial.println(">> " + strTest);
Serial.println("Результат кодирования:");
Serial.println(">> " + StringToUCS2(strTest));
}
void loop() {
} |
|
66 |
|
Все примеры исполняются на этой схеме
|
67 |
Функция по отправке SMS в PDU-формате немного отличается от отправки SMS в текстовом формате. Дополнительно вводятся функции по формированию PDU-пакета, по схеме, описанной выше. Полный пример отправки SMS в PDU-формате: |
|
68 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
String _response = ""; // Переменная для хранения ответа модуля
void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
sendATCommand("AT", true); // Отправили AT для настройки скорости обмена данными
String strTest = "Тест формата PDU с прочими символами ₡❿𦈘!";
sendSMSinPDU("+7928xxxxxxx", strTest);
}
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
_response.trim(); // Убираем лишние пробелы в начале и конце
Serial.println(_response); // Если нужно выводим в монитор порта
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
void sendSMSinPDU(String phone, String message)
{
Serial.println("Отправляем сообщение: " + message);
// ============ Подготовка PDU-пакета =============================================================================================
// В целях экономии памяти будем использовать указатели и ссылки
String *ptrphone = ☎ // Указатель на переменную с телефонным номером
String *ptrmessage = &message; // Указатель на переменную с сообщением
String PDUPack; // Переменная для хранения PDU-пакета
String *ptrPDUPack = &PDUPack; // Создаем указатель на переменную с PDU-пакетом
int PDUlen = 0; // Переменная для хранения длины PDU-пакета без SCA
int *ptrPDUlen = &PDUlen; // Указатель на переменную для хранения длины PDU-пакета без SCA
getPDUPack(ptrphone, ptrmessage, ptrPDUPack, ptrPDUlen); // Функция формирующая PDU-пакет, и вычисляющая длину пакета без SCA
Serial.println("PDU-pack: " + PDUPack);
Serial.println("PDU length without SCA:" + (String)PDUlen);
// ============ Отправка PDU-сообщения ============================================================================================
sendATCommand("AT+CMGF=0", true); // Включаем PDU-режим
sendATCommand("AT+CMGS=" + (String)PDUlen, true); // Отправляем длину PDU-пакета
sendATCommand(PDUPack + (String)((char)26), true); // После PDU-пакета отправляем Ctrl+Z
}
void getPDUPack(String *phone, String *message, String *result, int *PDUlen)
{
// Поле SCA добавим в самом конце, после расчета длины PDU-пакета
*result += "01"; // Поле PDU-type - байт 00000001b
*result += "00"; // Поле MR (Message Reference)
*result += getDAfield(phone, true); // Поле DA
*result += "00"; // Поле PID (Protocol Identifier)
*result += "08"; // Поле DCS (Data Coding Scheme)
//*result += ""; // Поле VP (Validity Period) - не используется
String msg = StringToUCS2(*message); // Конвертируем строку в UCS2-формат
*result += byteToHexString(msg.length() / 2); // Поле UDL (User Data Length). Делим на 2, так как в UCS2-строке каждый закодированный символ представлен 2 байтами.
*result += msg;
*PDUlen = (*result).length() / 2; // Получаем длину PDU-пакета без поля SCA
*result = "00" + *result; // Добавляем поле SCA
}
String getDAfield(String *phone, bool fullnum) {
String result = "";
for (int i = 0; i <= (*phone).length(); i++) { // Оставляем только цифры
if (isDigit((*phone)[i])) {
result += (*phone)[i];
}
}
int phonelen = result.length(); // Количество цифр в телефоне
if (phonelen % 2 != 0) result += "F"; // Если количество цифр нечетное, добавляем F
for (int i = 0; i < result.length(); i += 2) { // Попарно переставляем символы в номере
char symbol = result[i + 1];
result = result.substring(0, i + 1) + result.substring(i + 2);
result = result.substring(0, i) + (String)symbol + result.substring(i);
}
result = fullnum ? "91" + result : "81" + result; // Добавляем формат номера получателя, поле PR
result = byteToHexString(phonelen) + result; // Добавляем длиу номера, поле PL
return result;
}
String sendATCommand(String cmd, bool waiting) {
String _resp = ""; // Переменная для хранения результата
Serial.println(cmd); // Дублируем команду в монитор порта
SIM800.println(cmd); // Отправляем команду модулю
if (waiting) { // Если необходимо дождаться ответа...
_resp = waitResponse(); // ... ждем, когда будет передан ответ
// Если Echo Mode выключен (ATE0), то эти 3 строки можно закомментировать
if (_resp.startsWith(cmd)) { // Убираем из ответа дублирующуюся команду
_resp = _resp.substring(_resp.indexOf("\r", cmd.length()) + 2);
}
Serial.println(_resp); // Дублируем ответ в монитор порта
}
return _resp; // Возвращаем результат. Пусто, если проблема
}
String waitResponse() { // Функция ожидания ответа и возврата полученного результата
String _resp = ""; // Переменная для хранения результата
long _timeout = millis() + 10000; // Переменная для отслеживания таймаута (10 секунд)
while (!SIM800.available() && millis() < _timeout) {}; // Ждем ответа 10 секунд, если пришел ответ или наступил таймаут, то...
if (SIM800.available()) { // Если есть, что считывать...
_resp = SIM800.readString(); // ... считываем и запоминаем
}
else { // Если пришел таймаут, то...
Serial.println("Timeout..."); // ... оповещаем об этом и...
}
return _resp; // ... возвращаем результат. Пусто, если проблема
}
// =================================== Блок декодирования UCS2 в читаемую строку UTF-8 =================================
String UCS2ToString(String s) { // Функция декодирования UCS2 строки
String result = "";
unsigned char c[5] = ""; // Массив для хранения результата
for (int i = 0; i < s.length() - 3; i += 4) { // Перебираем по 4 символа кодировки
unsigned long code = (((unsigned int)HexSymbolToChar(s[i])) << 12) + // Получаем UNICODE-код символа из HEX представления
(((unsigned int)HexSymbolToChar(s[i + 1])) << 8) +
(((unsigned int)HexSymbolToChar(s[i + 2])) << 4) +
((unsigned int)HexSymbolToChar(s[i + 3]));
if (code <= 0x7F) { // Теперь в соответствии с количеством байт формируем символ
c[0] = (char)code;
c[1] = 0; // Не забываем про завершающий ноль
} else if (code <= 0x7FF) {
c[0] = (char)(0xC0 | (code >> 6));
c[1] = (char)(0x80 | (code & 0x3F));
c[2] = 0;
} else if (code <= 0xFFFF) {
c[0] = (char)(0xE0 | (code >> 12));
c[1] = (char)(0x80 | ((code >> 6) & 0x3F));
c[2] = (char)(0x80 | (code & 0x3F));
c[3] = 0;
} else if (code <= 0x1FFFFF) {
c[0] = (char)(0xE0 | (code >> 18));
c[1] = (char)(0xE0 | ((code >> 12) & 0x3F));
c[2] = (char)(0x80 | ((code >> 6) & 0x3F));
c[3] = (char)(0x80 | (code & 0x3F));
c[4] = 0;
}
result += String((char*)c); // Добавляем полученный символ к результату
}
return (result);
}
unsigned char HexSymbolToChar(char c) {
if ((c >= 0x30) && (c <= 0x39)) return (c - 0x30);
else if ((c >= 'A') && (c <= 'F')) return (c - 'A' + 10);
else return (0);
}
// =================================== Блок кодирования строки в представление UCS2 =================================
String StringToUCS2(String s)
{
String output = ""; // Переменная для хранения результата
for (int k = 0; k < s.length(); k++) { // Начинаем перебирать все байты во входной строке
byte actualChar = (byte)s[k]; // Получаем первый байт
unsigned int charSize = getCharSize(actualChar); // Получаем длину символа - кличество байт.
// Максимальная длина символа в UTF-8 - 6 байт плюс завершающий ноль, итого 7
char symbolBytes[charSize + 1]; // Объявляем массив в соответствии с полученным размером
for (int i = 0; i < charSize; i++) symbolBytes[i] = s[k + i]; // Записываем в массив все байты, которыми кодируется символ
symbolBytes[charSize] = '\0'; // Добавляем завершающий 0
unsigned int charCode = symbolToUInt(symbolBytes); // Получаем DEC-представление символа из набора байтов
if (charCode > 0) { // Если все корректно преобразовываем его в HEX-строку
// Остается каждый из 2 байт перевести в HEX формат, преобразовать в строку и собрать в кучу
output += byteToHexString((charCode & 0xFF00) >> 8) +
byteToHexString(charCode & 0xFF);
}
k += charSize - 1; // Передвигаем указатель на начало нового символа
if (output.length() >= 280) break; // Строка превышает 70 (4 знака на символ * 70 = 280) символов, выходим
}
return output; // Возвращаем результат
}
unsigned int getCharSize(unsigned char b) { // Функция получения количества байт, которыми кодируется символ
// По правилам кодирования UTF-8, по старшим битам первого октета вычисляется общий размер символа
// 1 0xxxxxxx - старший бит ноль (ASCII код совпадает с UTF-8) - символ из системы ASCII, кодируется одним байтом
// 2 110xxxxx - два старших бита единицы - символ кодируется двумя байтами
// 3 1110xxxx - 3 байта и т.д.
// 4 11110xxx
// 5 111110xx
// 6 1111110x
if (b < 128) return 1; // Если первый байт из системы ASCII, то он кодируется одним байтом
// Дальше нужно посчитать сколько единиц в старших битах до первого нуля - таково будет количество байтов на символ.
// При помощи маски, поочереди исключаем старшие биты, до тех пор пока не дойдет до нуля.
for (int i = 1; i <= 7; i++) {
if (((b << i) & 0xFF) >> 7 == 0) {
return i;
}
}
return 1;
}
unsigned int symbolToUInt(const String& bytes) { // Функция для получения DEC-представления символа
unsigned int charSize = bytes.length(); // Количество байт, которыми закодирован символ
unsigned int result = 0;
if (charSize == 1) {
return bytes[0]; // Если символ кодируется одним байтом, сразу отправляем его
}
else {
unsigned char actualByte = bytes[0];
// У первого байта оставляем только значимую часть 1110XXXX - убираем в начале 1110, оставляем XXXX
// Количество единиц в начале совпадает с количеством байт, которыми кодируется символ - убираем их
// Например (для размера 2 байта), берем маску 0xFF (11111111) - сдвигаем её (>>) на количество ненужных бит (3 - 110) - 00011111
result = actualByte & (0xFF >> (charSize + 1)); // Было 11010001, далее 11010001&(11111111>>(2+1))=10001
// Каждый следующий байт начинается с 10XXXXXX - нам нужны только по 6 бит с каждого последующего байта
// А поскольку остался только 1 байт, резервируем под него место:
result = result << (6 * (charSize - 1)); // Было 10001, далее 10001<<(6*(2-1))=10001000000
// Теперь у каждого следующего бита, убираем ненужные биты 10XXXXXX, а оставшиеся добавляем к result в соответствии с расположением
for (int i = 1; i < charSize; i++) {
actualByte = bytes[i];
if ((actualByte >> 6) != 2) return 0; // Если байт не начинается с 10, значит ошибка - выходим
// В продолжение примера, берется существенная часть следующего байта
// Например, у 10011111 убираем маской 10 (биты в начале), остается - 11111
// Теперь сдвигаем их на 2-1-1=0 сдвигать не нужно, просто добавляем на свое место
result |= ((actualByte & 0x3F) << (6 * (charSize - 1 - i)));
// Было result=10001000000, actualByte=10011111. Маской actualByte & 0x3F (10011111&111111=11111), сдвигать не нужно
// Теперь "пристыковываем" к result: result|11111 (10001000000|11111=10001011111)
}
return result;
}
}
String byteToHexString(byte i) { // Функция преобразования числового значения байта в шестнадцатиричное (HEX)
String hex = String(i, HEX);
if (hex.length() == 1) hex = "0" + hex;
hex.toUpperCase();
return hex;
} |
|
70 |
Все работает: |
|
72 |
Установка срока жизни SMS — биты VPF (Validity Period Format) и поле VP (Validity Period) В примере используемом в статье, в целях упрощения, битам VPF было присвоено значение 00. И здесь стоит напомнить, что эти биты задают формат поля VP, могут принимать и другие значения, которые будут влиять на содержимое PDU-пакета, и, как следствие, отношение SMS-центра к отправленному SMS:
|
|
73 |
Здесь имеет смысл повторить, что данная информация становится актуальной в случаях, когда нет возможности доставить SMS адресату, когда он, например, находится вне зоны действия сети. И этим параметром можно указать срок, в течение которого SMS-центр не будет его удалять, а будет пытаться его доставить. По истечению этого срока, если сообщение не было доставлено, оно будет удалено. |
|
74 |
Далее будут рассмотрены 2 возможных варианта формата поля VP, задаваемые битами VPF. |
|
75 |
Вариант 1 — поле VP содержит данные в относительном формате. Данный вариант устанавливается значениями битов VPF — 10. И говорит о том, что поле VP хранит данные о сроке жизни SMS в относительном формате, то есть по отношению ко времени его создания. Например, 30 минут, сутки или 30 дней, с момента получения сообщения SMS-центром. |
|
76 |
Теперь поле PDU-type примет вид 00010001b=11h. |
|
77 |
В данном случае, длина поля VP составляет 1 байт. Значение этого байта устанавливается в соответствии с таблицей: |
|
78 |
|
|
79 |
Например, при необходимом времени жизни сообщения 5 часов, значение будет рассчитано по формуле 1 строки таблицы. Здесь нужно будет решить простое уравнение: |
|
80 |
$$(VP + 1) \times 5~минут = 300~минут~(5~часов \times 60~минут), \\
(VP + 1) = 60, \\
VP=59
$$
|
|
81 |
Таким образом поле VP должно будет принять значение 59d = 3Bh. |
|
82 |
Исходя из вышеизложенного весь PDU-пакет необходимо пересобрать — изменить поле PDU-type и добавить поле VP: |
|
83 |
SCA PDU-type MR DA PID DCS VP UDL UD
|
|
84 |
Также, необходимо пересчитать, с учетом изменившегося PDU-пакета, его длину, отправляемую параметром команды AT+CMGS=<length>. |
|
85 |
Вариант 2 — поле VP содержит данные в абсолютном формате. Данный вариант устанавливается значениями битов VPF — 11. И говорит о том, что поле VP хранит данные о конкретном времени, до наступления которого сообщение не будет удалено. Например, срок жизни сообщения до 09:20:50 30 декабря 2017 года. При наступлении этого времени, если сообщение не было доставлено, оно удалится. |
|
86 |
Поле PDU-type теперь будет иметь значение 00011001b=19h. |
|
87 |
В данном варианте, длина поля VP составляет 7 байт и его структура выглядит так: |
|
88 |
|
|
89 |
Каждый байт представлен двухзначным десятичным числом с переставленными цифрами. При обозначении года используются последние две цифры. Часовой пояс показывает смещение времени относительно Гринвича (GMT), выраженную в четвертях часа. В случае отрицательного смещения (GMT-2), третий бит байта устанавливается в 1. Например, необходимо установить следующее значение 25 марта 2018 года 15:23:54 (GMT-7). |
|
90 |
Г М Д Ч м с ЧП
было:
|
|
91 |
Поле VP принимает значение: 8130525132548А. |
|
92 |
Но здесь имеет смысл подробнее остановиться на получении корректного значения часового пояса. Если с положительным смещением, все более-менее просто, то с отрицательным, алгоритм требует пояснений. Ниже представлены несколько примеров получения 7 байта поля VP: |
|
93 |
Пример №1. GMT-7 — 8A
В 7 часах 28 четвертей часа (7×4=28). Меняем цифры местами 28→82. 82h = 10000010b. Поскольку значение отрицательное, меняем третий бит на 1: 10000010b→10001010b. Представление в HEX-формате: 10001010b→8Ah. |
|
94 |
Пример №2. GMT-3 — 29
В 3 часах 12 четвертей часа (3×4=12). Меняем цифры местами 12→21. 21h = 00100001b. Поскольку значение отрицательное, меняем третий бит на 1: 00100001b→00101001b. Представление в HEX-формате: 00101001b→29h. |
|
95 |
Пример №3. GMT+4 — 61
В 4 часах 16 четвертей часа (4×4=16). Меняем цифры местами 16→61. 61h. Поскольку значение положительное, менять ничего не нужно — 61h. |
|
96 |
Теперь, также как и в предыдущем варианте весь PDU-пакет нужно пересобрать — изменить поле PDU-type и добавить поле VP: |
|
97 |
SCA PDU-type MR DA PID DCS VP UDL UD
|
|
98 |
И снова нужно будет пересчитать, с учетом изменившегося PDU-пакета, его длину, отправляемую параметром команды AT+CMGS=<length>. |
|
100 |
Что почитать:
|
|
101 |
Похожие запросы:
|
|