I try to send SMS with PDU mod but it doesn’t work. when i try to send PDUpack it wasn’t send.
CODE:
#include <SoftwareSerial.h>
#define Restart_GSM_pin 16
#define Rx_out 3 // принимающая линия UART для работы GSM модуля (A1)
#define Tx_out 2 // передающая линия UART для работы GSM модуля (A0)
#define number_lim 5 // предел для создания номеров не больше 9
#define Stop_number "+00000000000" // условно пустой (не сушествуюший) номер
String Phone_numbers [number_lim];
// Подключение GSM
SoftwareSerial SIM800(Tx_out, Rx_out);
extern int __bss_end;
extern void *__brkval;
// Функция, возвращающая количество свободного ОЗУ (RAM)
int memoryFree()
{
int freeValue;
if((int)__brkval == 0)
freeValue = ((int)&freeValue) - ((int)&__bss_end);
else
freeValue = ((int)&freeValue) - ((int)__brkval);
return freeValue;
}
//========================================================= GSM funcs ======================================================================
String byteToHexString(byte i) { // Функция преобразования числового значения байта в шестнадцатиричное (HEX)
String hex = String(i, HEX);
if (hex.length() == 1) hex = "0" + hex;
hex.toUpperCase();
return hex;
}
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 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; // Возвращаем результат
}
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;
}
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 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; // ... возвращаем результат. Пусто, если проблема
}
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; // Возвращаем результат. Пусто, если проблема
}
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
}
// =================================== Блок декодирования UCS2 в читаемую строку UTF-8 =================================
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);
}
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);
}
// =================================== Блок кодирования строки в представление UCS2 =================================
void restart_GSM(){ // Функция перезагрузки модема
digitalWrite(Restart_GSM_pin, LOW);
delay(500);
digitalWrite(Restart_GSM_pin, HIGH);
delay(500);
}
void number_list_creating(){ // создание списка номеров в микро контроллере
int pos = 0;
String string_to_mode = "";
int index = 0;
while (index < number_lim){
//Serial.println("AT+CPBR="+String(index));
SIM800.println("AT+CPBR="+String(index));
string_to_mode = String(SIM800.readString());
string_to_mode.replace("\n","");
string_to_mode.replace(" ","");
string_to_mode.remove(string_to_mode.indexOf("+"),1);
if (string_to_mode.indexOf("+") != -1){
pos = string_to_mode.indexOf("+");
string_to_mode = string_to_mode.substring(pos,pos+12);
}else{
pos = string_to_mode.indexOf("8");
string_to_mode = string_to_mode.substring(pos,pos+11);
pos = string_to_mode.indexOf("8");
string_to_mode[pos] = '7';
}
if (index > 0){
Phone_numbers[index] = string_to_mode;
}
index++;
SIM800.flush();
Serial.flush();
}
}
void number_creating_inSIM(){ // создание номеров на сим карте при необходимости
int index_of_numbers = 1;
SIM800.println("AT+CPBR=1");
if( SIM800.find('+') == false and SIM800.find('8') == false){
while (index_of_numbers < number_lim){
Serial.println(sendATCommand("AT+CPBW="+String(index_of_numbers)+","+'"'+Stop_number+'"'+","+"145,"+'"'+"Operator "+String(index_of_numbers)+'"'+"\r\n", true));
index_of_numbers+=1;
}
}
SIM800.flush();
Serial.flush();
}
//===========================================================================================================================================
// 1 - Работа насоса 1 восcтановлена, 2 - Работа насоса 2 восcтановлена, 3 - Работа насосв не требуется
// 4 - Насосы запущены в режиме чередования, 5 - Запущено два насоса одновременно,
// 6 - Авария насоса 1, 7 - Авария насоса 2, 8 - Авария насоса 1 и 2
void SMS_sender(int choose) { // Функция отправки СМС по событию
int index_for_send = 1;
while (index_for_send < number_lim) {
if (Phone_numbers[index_for_send] != Stop_number) {
if (choose == 1) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Работа 1 восcтанов."));
}
if (choose == 2) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Работа 2 восcтанов."));
}
if (choose == 3) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Работа не требуется"));
}
if (choose == 4) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Насосы чередуются"));
}
if (choose == 5) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Запущено 2 насоса"));
}
if (choose == 6) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Авария насоса 1"));
}
if (choose == 7) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Авария насоса 2"));
}
if (choose == 8) {
sendSMSinPDU(Phone_numbers[index_for_send], F("Авария насоса 1 и 2"));
}
}
index_for_send += 1;
}
}
void setup() {
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
while ( sendATCommand("AT", true).indexOf("OK") == -1){ // продолжит выполнение если AT вернет True
//Serial.println("GSM not responsing, restart");
Serial.println(sendATCommand("AT", true).indexOf("OK"));
restart_GSM();
}
number_creating_inSIM();
number_list_creating();
// Serial.println(Phone_numbers[0]);
// Serial.println(Phone_numbers[1]);
// Serial.println(Phone_numbers[2]);
// Serial.println(Phone_numbers[3]);
// Serial.println(Phone_numbers[4]);
sendATCommand("AT+cpin?", true);
sendSMSinPDU("+79xxxxxxxxx", "да да");
}
void loop() {
}