PlatformIO Community

GSM SMS sending not working (SIM800 and Arduino)

I try to send SMS with PDU mod but it doesn’t work. when i try to send PDUpack it wasn’t send.
Screenshot 2021-10-11 200410

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 = &phone;                                    // Указатель на переменную с телефонным номером
  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() {

}

Have you verified stand-alone that the SIM800 SMS sending works by typing in the AT commands directly? https://www.smssolutions.net/tutorials/gsm/sendsmsat/

yes, and its not working
Screenshot 2021-10-12 210437

You mave connected the module wrongly to the Arduino. What is your wireup?

Are you able to hookup a USB-serial adapter directly to the SIM module and enter AT commands via the serial monitor to trigger an SMS? Basic checks like sending “AT” and getting a response are working?

I think, I connect it right, because when I try to send “AT”, get answer “OK”

Try sending the commands

AT+CGMR 

and

AT+CGSN 

to check whether it outputs more than just “OK”. Per https://www.elecrow.com/wiki/images/2/20/SIM800_Series_AT_Command_Manual_V1.09.pdf it should give you some software revision and the IMEI of the SIM card…

Yes, its work correctly.

OK. I try to send a few AT commands and its look something like this:
Безымянный
I think commands: AT+CNUM and ATX1 works incorrectly.