+7(960) 250-82-68 spam@mirossa.ru


 mirossa        1С           C         PHP       JAVA       MCU  


Статьи
 
 

28 сентября 2019 г

Arduino. Исходящий звонок с воспроизведением звукового файла. Отладка

Плата Arduino Uno и SIM800c GPRS/GSM Shield

драйвер для USB-UART CH340/CH341 (Windows)

Плата Arduino Uno и SIM900 GPRS/GSM Shield

Блок питания (например, такой)

Блок питания

код скетча.

/*
 При отправке 'c' через монитор порта начинается голосовое соединение с указанным номером. 
 При ответе абонента начинается воспроизведение звукового файла на 20 секунд.
 После этого завершается соединение и останавливается проигрывание файла. Абонент может прервать вызов раньше.
 После этого вручную отправить 'h'.
 Инструкция по загрузке звукового файла ВНИЗУ.
*/
        
#include <SoftwareSerial.h>
SoftwareSerial Sim900Serial(7, 8); //Sim900Serial(2, 3); // RX, TX
#define response_size 100
char response[response_size];  // это буфер для служебного текста ответов сервера
int avail, f = 0;
unsigned long previous;
uint8_t x, answer;
char userText;
uint8_t statusCall;

uint8_t make_a_CALL(const char * telNum, uint8_t * statusCall);
      
void setup()
{
  Sim900Serial.begin(19200);
  Serial.begin(19200);
}

void loop()
{
if (  Serial.available() ){
    userText = Serial.read();
    while( Serial.available() > 0) 
          Serial.read();    // Clean the input buffer
          
if ( userText == 'c' ){   //call
     
    Serial.println("=== Отправка: ATD+79119991111;\n");
    answer = make_a_CALL("+79119991111", &statusCall);   //пару раз тут что-то заедало. отладить не успел(с третьей отправки 'c' зазвонило)
    Serial.println("=== конец: ATD+79119991111;\n");
    f = 1;
    
    if (answer != 0)
        Serial.println("call error");
        
} else if ( userText == 'h' ){   //end the call

    Serial.println("=== Отправка: ATH\n");
    answer = read_answer_CALL("ATH", "", NULL);
    Serial.println(response); // вывод всего пришедшего ответа
    f = 0;

/*    Serial.println("=== Отправка: AT+CLCC=0\n");
    answer = read_answer_CALL("AT+CLCC=0", "", NULL);  // отключаем получение статусов текущих звонков после отправки AT+CLCC
    Serial.println(response); // вывод всего пришедшего ответа
*/
}
         
}

  //debug
  delay(3000);

  if (f == 1){   //для скидывание f - отправить 'h'
    Serial.println("=== Отправка: AT+CLCC\n");
    answer = read_answer_CALL("AT+CLCC", "", NULL);   //пока просто визуальный контроль для проверки статуса звонка
                                                      //при разных условиях завершения звонка.
                                                      //Мне нужен статус "0 Active"
                                                      // это ответ (там третья цифра): 
                                                      //AT+CLCC
                                                      //+CLCC: 1,0,0,0,0,"+79119991111",145,""
                                                      //
                                                      //OK
                                                     //
                                                     //если абонент не ответил, то получаю
                                                     //AT+CLCC
                                                     //+CLCC: 1,0,3,0,0,"+79119991111",145,""
                                                     //
                                                     //OK
                                                     //   и затем(после авто прерывания вызова) 
                                                     //AT+CLCC
                                                     //OK
                                                     //
                                                     //лучше, наверное, сделать так:
                                                     //начать звонок
                                                     //следить за статусом +CLCC: 1,0,2,0,0,"+79119991111",145,""
                                                     //отсчитывать N секунд
                                                     //и если не ответили, то завершаем звонок
                                                     //а если произошло автопрерывание (само)
                                                     //то получаем
                                                     //AT+CLCC
                                                     //OK
                                                     //
                                                     //а если пользователь прервал, то получаем "пустую" команду
                                                     //AT+CLCC
                                                     //OK
                                                     // в любом случае проще смотреть, что после нормального ответа приходит
                                                     //эта пустая команда
                                                     //
                                                     //также при прерывании пользователем или сигнале занято получаю
                                                     //BUSY   - получит от команды ATD
                                                     //NO CARRIER
                                                     //NO ANSWER и другие (описаны в шилде для команды ATD)
                                                     
                                                                                                          
    Serial.println(response); // вывод всего пришедшего ответа
  }
/*    
  while ( Sim900Serial.available() > 0 ) {
         
          avail = Sim900Serial.read();
          Serial.println(avail);         
     }     
*/

} // main loop


// обработка большинства команд завершается на шаге stepP == 1
  uint8_t read_answer_CALL(const char * AtCommand, const char * expected_answer, char * CLCC_data){
      
      unsigned long previous;
      uint8_t x, answer;
      int avail;
      char * istr;
      int pars = 0, stepP = 0;
      char * cc, * ccEnd;
    
      x = 0;
      previous = millis();
      size_t sz, sz_ea;
      
      sz = strlen(AtCommand);
      sz_ea = strlen(expected_answer);
      
     answer = 1; // error

      while( Sim900Serial.available() > 0) 
          Sim900Serial.read();    // Clean the input buffer
    memset(response, '\0', response_size);    // Initialize the string
    
    Sim900Serial.print(AtCommand);    // Отправка AT команды 
    Sim900Serial.print("\r");
    
     while ( ((millis() - previous) < 5000) ){     // ! timeout нужно скидывать, когда получаю байты данных                                         
       
       if ( Sim900Serial.available() > 0 ) {
         
          avail = Sim900Serial.read();
//Serial.println(avail, DEC);           
          response[x] = avail;
          x++;
          
        //парсинг ответа
        if ( stepP == 0 ){ 
          istr = strstr ( response + pars, AtCommand);  
          if ( istr != NULL ){
             pars = (istr - response) + sz; 
             stepP = 1;
          }
        } else if ( stepP == 1 ){
              if (sz_ea == 0){ //передана пустая строка в expected_answer (в этой команде ждем ОК и завершаем обарботку;
//  Serial.println(avail, DEC);                
                  istr = strstr ( response + pars , "OK\r\n"); //not "OK\r"
                  if ( istr != NULL ){
                    answer = 0;
                    break;
                  }else{
                    istr = strstr ( response + pars , "ERROR\r\n");  // нужно ли это ?
                    if ( istr != NULL ){
                        break;
                    }
                  }
 
              }else{
                    stepP = 2;
                    continue;
                  
              }
            
            
        }else if ( stepP == 2 ){
//Serial.println("stepP == 2\n");
            istr = strstr ( response + pars, expected_answer); // +CLCC:   // пример: +CLCC: 1,0,3,0,0,"+79119991111",145,""
            if ( istr != NULL ){                               // ! хотя при AT+CLCC может быть и "пустой" ответ (если, 
								// например, пользователь сам скинул или обонент недоступен)
             pars = (istr - response) + sz_ea + 1; // пропускаем пробел. встаем на начало данных
             cc = response + pars; // cc - начало данных
             stepP = 3;
             
            }else{
              // то есть кроме +CLCC: с данными может быть и пустая команда - ищем ОК.
              istr = strstr ( response + pars , "OK\r\n"); //not "OK\r"
              if ( istr != NULL ){
              answer = 0;
              *CLCC_data = '\0';
              break;
              }else{
              istr = strstr ( response + pars , "ERROR\r\n");  // нужно ли это ?
                    if ( istr != NULL ){
                      *CLCC_data = '\0';
                      break;
                    } 
            }
            
          }
        }else if ( stepP == 3 ){
//Serial.println("stepP == 3\n");
            ccEnd = strstr(cc, "\r\n"); // конец
            if ( ccEnd != NULL ){
              //можно проверить на NULL
              strncpy (CLCC_data, cc, ccEnd - cc);
              CLCC_data[ccEnd - cc] = '\0';      // добавить в конец строки символ конца строки
              //break;

              pars = (ccEnd - response) + 2;
              stepP = 4;
              continue;
            }
        }else if ( stepP == 4 ){
//Serial.println("stepP == 4\n");
              istr = strstr ( response + pars , "OK\r\n");
                  if ( istr != NULL ){
                    answer = 0;
                    break;
                  }
          
         }
                  
       }
     }     
      
       
    response[x] = '\0';
    //Serial.println(response); // вывод всего пришедшего ответа
  
  return answer;
  }  // read_answer_CALL()

  // обработка большинства команд завершается на шаге stepP == 1
  uint8_t read_answer_CREC(const char * AtCommand, const char * expected_answer, char * CREC_data){
      
      unsigned long previous;
      uint8_t x, answer;
      int avail;
      char * istr;
      int pars = 0, stepP = 0;
      char * cc, * ccEnd;
    
      x = 0;
      previous = millis();
      size_t sz, sz_ea;
      
      sz = strlen(AtCommand);
      sz_ea = strlen(expected_answer);
      
     answer = 1; // error

      while( Sim900Serial.available() > 0) 
          Sim900Serial.read();    // Clean the input buffer
    memset(response, '\0', response_size);    // Initialize the string
    
    Sim900Serial.print(AtCommand);    // Отправка AT команды 
    Sim900Serial.print("\r");
    
     while ( ((millis() - previous) < 5000) ){     // ! timeout нужно скидывать, когда получаю байты данных                                         
       
       if ( Sim900Serial.available() > 0 ) {
         
          avail = Sim900Serial.read();
//Serial.println(avail, DEC);           
          response[x] = avail;
          x++;
          
        //парсинг ответа
        if ( stepP == 0 ){ 
          istr = strstr ( response + pars, AtCommand);  
          if ( istr != NULL ){
             pars = (istr - response) + sz; 
             stepP = 1;
          }
        } else if ( stepP == 1 ){
              if (sz_ea == 0){ //передана пустая строка в expected_answer (в этой команде ждем ОК и завершаем обарботку;
//  Serial.println(avail, DEC);                
                  istr = strstr ( response + pars , "OK\r\n"); //not "OK\r"
                  if ( istr != NULL ){
                    answer = 0;
                    break;
                  }else{
                    istr = strstr ( response + pars , "ERROR\r\n");  // нужно ли это ?
                    if ( istr != NULL ){
                        break;
                    }
                  }
 
              }else{
                    stepP = 2;
                    continue;
                  
              }
            
            
        }else if ( stepP == 2 ){
//Serial.println("stepP == 2\n");
            istr = strstr ( response + pars, expected_answer); // 
            if ( istr != NULL ){                               // 
             pars = (istr - response) + sz_ea + 1; // пропускаем пробел. встаем на начало данных
             cc = response + pars; // cc - начало данных
             stepP = 3;
             
            }
        }else if ( stepP == 3 ){
//Serial.println("stepP == 3\n");
            ccEnd = strstr(cc, "\r\n"); // конец
            if ( ccEnd != NULL ){
              //можно проверить на NULL
              strncpy (CREC_data, cc, ccEnd - cc);
              CREC_data[ccEnd - cc] = '\0';      // добавить в конец строки символ конца строки
              //break;

              pars = (ccEnd - response) + 2;
              stepP = 4;
              continue;
            }
        }else if ( stepP == 4 ){
//Serial.println("stepP == 4\n");
              istr = strstr ( response + pars , "OK\r\n");
                  if ( istr != NULL ){
                    answer = 0;
                    break;
                  }
          
         }
                  
       }
     }     
      
       
    response[x] = '\0';
    //Serial.println(response); // вывод всего пришедшего ответа
  
  return answer;
  }  // read_answer_CREC()

/////////////////////////////////////////////////////////////////////////////////
uint8_t make_a_CALL(const char * telNum, uint8_t * statusCall){
  
  unsigned long previous;
  uint8_t answer = 0;
  char fullComm[18];
  char CLCC_data[50];
  char CREC_data[3]; // 0, 1, 2
  char stat;
  
  *statusCall = 1; //any error
  sprintf(fullComm, "ATD%s;", telNum);  // ATD+79119991111;
  answer = read_answer_CALL(fullComm, "", NULL);
                                                            //ответы на эту команду
                                                            //ATD+79119991111;
                                                            //
                                                            //OK
                                                            
                                                            //ATD+79119991111;
                                                            //
                                                            //NO DIALTONE
                                                            //
                                                            //+CREG: 1
                  
  //Serial.println(response); // вывод всего пришедшего ответа
    if (answer != 0){
//Serial.println("call error");
       return answer;
    }

   // проверям изменение статуса дозвона
   // дозвон до N секунд 
    previous = millis();
    while ( ((millis() - previous) < 15000) ){     // ! timeout нужно скидывать, когда получаю байты данных                                         
       delay(500);
       //Serial.println("=== Отправка: AT+CLCC\n");   // ! сейчас расчет на один звонок в одно время.
       answer = read_answer_CALL("AT+CLCC", "+CLCC:", CLCC_data);  // в CLCC_data - +CLCC: 1,0,3,0,0,"+79119991111",145,""
       //Serial.println(response); // вывод всего пришедшего ответа  
       if (answer != 0){
//Serial.println("call error 1");  //debug
//if ( *CLCC_data == '\0' )  //debug
//Serial.println("*CLCC_data == 0 \n");   //debug 
          return answer;
       }
       if ( *CLCC_data == '\0' ){
//Serial.println("call error 2");
          return answer;
       }  
       // пока костыльный способ
       //можно было бы проверить, что это наш телефон, но сейчас расчет на один звонок в одно время.
       //берем сразу нужный байт - статус
       //Serial.println(CLCC_data); // вывод параметров звонка

           stat = CLCC_data[4];   // т.е. вся движуха ради одного байта. потом можно удалить CLCC_data. оставить адеса начала и 
								  // конца в общем буфере ответа
                                  // если будет сразу несколько звонков, то данных по ним будет много и буфер response будет мал.
          //Serial.write(stat);                                 
           //если в течении длительности цикла абонент ответил
           if ( stat == '0' ){
    
            *statusCall = 0; //ok
            
            // Playing armFile. Playing armFile. Playing armFile.
 //Serial.println("status Active\n");
                         /*   AT+CREC=4,"C:\User\14.amr",0,60
                              
                              OK
                              AT+CREC=4,"C:\User\14.amr",0,100

                              ERROR                    // при повторной попытке запуска
                              
                              +CREC: 0                 // после окончания воспроизведения приходит дополнительно, его пока не отслеживаю
             
                              AT+CREC=5                // СТОП
                              
                              +CREC: 0
                              
                              OK
                              AT+CREC=5                // СТОП с ERROR (второй раз)
                              
                              ERROR
                              AT+CREC?                 // статус
                              
                              +CREC: 0
                              
                              OK
                          */    
          answer = read_answer_CREC("AT+CREC=4,\"C:\\User\\14.amr\",1,100", "", NULL);
         
          if (answer != 0){ // может предыдущая мелодия почему-то не остановилась ??
Serial.println("Starting play error");
            read_answer_CREC("AT+CREC=5", "+CREC:", CREC_data);      // STOP. ответы не проверяю. при правильной работе предыдущих команд
								// будет ответ ERROR (это если мелодия не запускается не по
                                                                     //  причине уже запущенной другой мелодии)

            answer = read_answer_CREC("AT+CREC=4,\"C:\\User\\14.amr\",1,100", "", NULL);
            if (answer != 0){
                return answer;
            }
          }               
          break;
           }   
       
     }

     if ( *statusCall == 0 ){ //абонент ответил, то жден время, пока проигрывается запись
//Serial.println("call play ok");    
           // звонок до 20 секунд 
            previous = millis();
            while ( ((millis() - previous) < 20000) ){ // нужно знать длительность мелодии                                         
               delay(500);
               answer = read_answer_CALL("AT+CLCC", "+CLCC:", CLCC_data); // в CLCC_data - +CLCC: 1,0,3,0,0,"+79119991111",145,""
               if (answer != 0)
                  return answer;
               if ( *CLCC_data == '\0' ){ // нет списка любых текущих соединений
                  read_answer_CREC("AT+CREC=5", "+CREC:", CREC_data);      // STOP. ответы не проверяю
                  return answer;
               }
                   stat = CLCC_data[4];   
                   
                   //если статус звонка отличный от Active (например, пользователь скинул)
                   if ( stat != '0' ){
 //Serial.println("call ath 1");
                      delay(400);           
                      answer = read_answer_CALL("ATH", "", NULL); //end a call   - всегда ли это будет актуально?
                      read_answer_CREC("AT+CREC=5", "+CREC:", CREC_data);      // STOP. ответы не проверяю
                      break;
                   }   
               
             }
        
     }
      if ( stat == '0' ){ // когда время вышло, то заавершаем звонок
        //Serial.println("=== Отправка: ATH\n");
        delay(400);
        answer = read_answer_CALL("ATH", "", NULL); //end a call
        read_answer_CREC("AT+CREC=5", "+CREC:", CREC_data);      // STOP. ответы не проверяю
//Serial.println("call ath 2");        
        //Serial.println(response); // вывод всего пришедшего ответа
      }
//Serial.println("call 10");    
  return answer;
} //make_a_CALL

// https://stackoverflow.com/questions/21522794/atcmgs-returns-error  
// http://www.2150692.ru/faq/47-at-komandy-a6

Загрука звуковых файлов.

Включаем питание обеих плат( на SIM800c нажимаем кнопку Power).
Прошиваем контроллер скетчем из Файл/Примеры/SoftwareSerial/SoftwareSerialExample с небольшим изменением:

/*
  Software serial multple serial test

 Receives from the hardware serial, sends to software serial.
 Receives from software serial, sends to hardware serial.

 The circuit:
 * RX is digital pin 10 (connect to TX of other device)
 * TX is digital pin 11 (connect to RX of other device)

 Note:
 Not all pins on the Mega and Mega 2560 support change interrupts,
 so only the following can be used for RX:
 10, 11, 12, 13, 50, 51, 52, 53, 62, 63, 64, 65, 66, 67, 68, 69

 Not all pins on the Leonardo and Micro support change interrupts,
 so only the following can be used for RX:
 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI).

 created back in the mists of time
 modified 25 May 2012
 by Tom Igoe
 based on Mikal Hart's example

 This example code is in the public domain.

 */
#include <SoftwareSerial.h>

SoftwareSerial mySerial(7, 8); // RX, TX

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.println("Goodnight moon!");

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  mySerial.println("Hello, world?");
}

void loop() { // run over and over
  if (mySerial.available()) {
    Serial.write(mySerial.read());
  }
  if (Serial.available()) {
    mySerial.write(Serial.read());
  }
}

Закрываем монитор порта.

Далее подготавливаем ARM или WAV файлы. amrplayer
И загружаем программой Sim800 Series ArmFile Download v1.00
Если общий размер загружаемых файлов больше объема памяти, то будет зависание.

sim800arm.jpg

Программа использует команды AT+FSCREATE и AT+FSWRITE чтобы записать данные в память модема.

Список команд для работы с файловой системой - в документе SIM800 Series_FS_Application Note.
Памяти в модеме немного(для загрузки аудио файлов) и в ТОМ же документе коротко написано, что можно использовать также SD карту.
Не все модемы Sim800x поддерживают функции работы с SD картой. SIM800C не поддерживает(
В SDK_1308B03SIM800M32_EAT.rar в файле readMe написано, что "SIM800C does not contain the PWM, and SD card functions.")
В документе SIM800_Series_AT_Command_Manual_V1.09 сказано, что команда AT+SD2PCM поддерживается SIM800H,SIM800L,SIM800, SIM800M64,SIM808

Отправка некоторых команд для работы с файловой системой.

AT+FSMEM

+FSMEM: C:66560bytes

OK
AT+FSLS=C:\User\

14.amr
14w.wav

OK
AT+FSFLSIZE=C:\User\14w.wav

+FSFLSIZE: 41964

OK
AT+FSDEL=C:\User\14w.wav

OK
AT+FSLS=C:\User\

14.amr

OK
AT+FSMEM

+FSMEM: C:108544bytes

OK

https://github.com/martinhol221/SIM800L_DTMF_control/wiki/Loading-ARM-audio-files-in-the-SIM800L-modem
https://github.com/martinhol221/SIM800L_DTMF_control/wiki/Загрузка-аудиофайлов-в-SIM800L,-AT-CREC
https://www.youtube.com/watch?v=01P-Hszpjdc ( SIM800L ЗАПИСЬ ВОСПРОИЗВЕДЕНИЕ И СТИРАНИЕ ЗВУКОВЫХ ФАЙЛОВ )

Комментарии




information