28 сентября 2019 г
Arduino. Исходящий звонок с воспроизведением звукового файла. Отладка
Плата Arduino Uno и SIM800c GPRS/GSM Shield
драйвер для USB-UART CH340/CH341 (Windows)
Блок питания (например, такой)
код скетча.
/*
При отправке '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
Если общий размер загружаемых файлов больше объема памяти, то будет зависание.
Программа использует команды 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 ЗАПИСЬ ВОСПРОИЗВЕДЕНИЕ И СТИРАНИЕ ЗВУКОВЫХ ФАЙЛОВ )
Комментарии
Тест