01 |
Содержание:
|
Проверено — автор рекомендует: GSM/GPRS-модуль SIM800L Видео-инструкция о покупке со скидками на Aliexpress |
02 |
|
|
03 |
В этой статье цикла будет описана работа с DTMF-сигналами, а именно корректный парсинг DTMF-сигналов, возможности работы с ними и организация управления на базе DTMF. В качестве бонуса в статье будет продемонстрирована возможность внедрения трехуровневой системы безопасности, в которой 2 уровня будет обеспечивать функционал по работе с DTMF. Для полноценной работы с GSM/GPRS-модулем SIM800L понадобится официальный справочник по AT-командам — SIM800 Series_AT Command Manual_V1.10.pdf (4,01 MB).
|
|
04 |
Работа с тональными кодами кнопок — DTMF В ряде задач может возникнуть необходимость осуществлять управление модулем во время отвеченного вызова, при помощи нажатия цифровых клавиш телефона. Аналогичное управление организовано по меню помощи сотовых операторов. |
DTMF (англ. Dual-Tone Multi-Frequency, рус. Тональный набор, тональный сигнал) — двухтональный многочастотный аналоговый сигнал, используемый для набора телефонного номера. Для кодирования символа в DTMF складываются два синусоидальных сигнала, частоты которых определены заранее.
|
05 |
В модуле SIM800L также есть возможность декодирования сигнала DTMF в конкретные коды. Для использования этой возможности необходимо включить режим распознавания DTMF командой AT+DDET=1. Команда и её параметры подробно описаны в таблице: |
|
|
07 |
Теперь, зная, как включить этот режим, можно посмотреть на незапрашиваемые ответы отправляемые модулем в качестве реакции на нажатия разных кнопок: |
|
08 | 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 #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+DDET=1", true); // Включаем DTMF
}
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\n", 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()) // Ожидаем прихода данных (ответа) от модема...
Serial.write(SIM800.read()); // ...и выводим их в Serial
if (Serial.available()) // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
} |
|
10 |
Теперь основной задачей является корректно распарсить нажатия, для их дальнейшей обработки. Для этого изменим процедуру loop() следующим образом: |
|
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 // ...
void loop() {
if (SIM800.available()) { // Ожидаем прихода данных (ответа) от модема...
_response = waitResponse(); // Получаем ответ от модема для анализа
_response.trim();
Serial.println(">" + _response); // ...и дублируем их в Serial с пометкой ">"
// Теперь с данными можно работать
if (_response.startsWith("+DTMF:")) { // Если ответ начинается с "+DTMF:" тогда:
// Парсим полученный ответ...
// Мы знаем, что после этой команды будет пробел и один символ (нажатая клавиша),
// а значит можно сразу его "выдергивать":
String symbol = _response.substring(7, 8); // Выдергиваем символ с 7 позиции длиной 1 (по 8)
Serial.println("Key: " + symbol);
// Добавляем логику для полученных символов
//if (symbol=="") {
// ...
// }
}
else if (_response.startsWith("RING")) { // При входящем звонке...
sendATCommand("ATA", true); // ...отвечаем (поднимаем трубку)
}
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
} |
|
12 |
После запуска скетча, казалось бы все работает, если кнопки нажимать редко. Но если быстро нажать несколько кнопок подряд, то станет видно, что что-то не так — МК обработает только первую из полученных команд, а остальные пропустит: |
|
14 |
Так происходит из-за того, что данные нажатий разных клавиш приходят в порт одной пачкой (нажатия кнопок 4, 5, 2, 3). Об этом говорит символ > (кнопка 4). И получается, что скетч обработал только её, а остальные пропустил. |
|
15 |
Обновлено 03.03.2018
|
Так происходит из-за того, что функция SIM800.readString(), вызываемая в функции waitResponse() завершает свою работу только при наступлении таймаута в 1 секунду (класс Stream — C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\Stream.cpp): |
|
16 | Arduino (C++) |
1 2 3 ...
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
... |
Stream.cpp
|
17 |
Во время этого таймаута, продолжают приходить прочие нажатия, таким образом формируется пачка уведомлений. Существует 2 способа взять под контроль такое поведение. |
|
18 |
Первый способ (предпочтительный) позволяет при помощи функции setTimeout() задать меньшее значение таймаута, например 100 мс. |
|
19 | Arduino (C++) |
1 2 3 4 5 6 7 8 ...
void setup() {
...
SIM800.begin(9600); // Скорость обмена данными с модемом
SIM800.setTimeout(100); // Устанавливаем меньшее значение таймаута
...
}
... |
|
20 |
Конец обновления от 03.03.2018
|
При таком значении таймаута прочие уведомления не будут успевать приходить, и модуль корректно будет отрабатывать каждое уведомление. |
|
21 |
Второй способ предполагает обработку полученного уведомление именно как пачку незапрашиваемых ответов, а не как один ответ. Исходя из этого скорректируем процедуру loop() следующим образом. В скетче для удобства создана дополнительная функция для обработки логики DTMF processingDTMF(): |
|
22 | 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 void loop() {
if (SIM800.available()) { // Ожидаем прихода данных (ответа) от модема...
_response = waitResponse(); // Получаем ответ от модема для анализа
Serial.println(">" + _response); // Выводим полученную пачку сообщений
int index = -1;
do { // Перебираем построчно каждый пришедший ответ
index = _response.indexOf("\r\n"); // Получаем идекс переноса строки
String submsg = "";
if (index > -1) { // Если перенос строки есть, значит
submsg = _response.substring(0, index); // Получаем первую строку
_response = _response.substring(index + 2); // И убираем её из пачки
}
else { // Если больше переносов нет
submsg = _response; // Последняя строка - это все, что осталось от пачки
_response = ""; // Пачку обнуляем
}
submsg.trim(); // Убираем пробельные символы справа и слева
if (submsg != "") { // Если строка значимая (не пустая), то распознаем уже её
Serial.println("submessage: " + submsg);
if (submsg.startsWith("+DTMF:")) { // Если ответ начинается с "+DTMF:" тогда:
String symbol = submsg.substring(7, 8); // Выдергиваем символ с 7 позиции длиной 1 (по 8)
processingDTMF(symbol); // Логику выносим для удобства в отдельную функцию
}
else if (submsg.startsWith("RING")) { // При входящем звонке...
sendATCommand("ATA", true); // ...отвечаем (поднимаем трубку)
}
}
} while (index > -1); // Пока индекс переноса строки действителен
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
// Отдельная функция для обработки логики DTMF
void processingDTMF(String symbol) {
Serial.println("Key: " + symbol); // Выводим в Serial для контроля, что ничего не потерялось
} |
|
23 |
Проверяем и убеждаемся, что все работает отлично: |
|
25 |
Примеры Теперь можно приступать к выполнению каких-либо значимых задач. Самыми очевидными (но не исключительными) видятся 2 варианта использования DTMF:
|
|
26 |
Приведем примеры реализации обоих вариантов. Используемая схема подключения: |
|
27 |
Резисторы для подключения светодиодов 100 Ом, для подключения модуля — 10 КОм
|
О там как правильно подключать светодиоды и каких ошибок следует избегать при их подключении написано в статье Как правильно подключать светодиоды |
28 |
В первом примере, светодиоды будут использоваться в качестве индикатора отпирания. В случае набора заданной комбинации цифр, все светодиоды включатся на 1 секунду, обозначив отпирание замка. |
|
29 | На заметку: |
В качестве цифрового пароля можно указывать не только знакомую комбинацию цифр, но и любое слово. Так, например, в скетче используется пароль 263487, получаемый путем набора на телефонной клавиатуре слова codius.
|
|
30 |
Скетч: |
|
31 | 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 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
int pins[3] = {5, 6, 7}; // Пины с подключенными светодиодами
String _response = ""; // Переменная для хранения ответов модуля
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(pins[i], OUTPUT);
}
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
_response = sendATCommand("AT", true); // Проверка общего статуса
_response = sendATCommand("AT+DDET=1,0,0", true); // Включаем DTMF
}
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\n", 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; // ... возвращаем результат. Пусто, если проблема
}
int countTry = 0;
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
Serial.println(">" + _response); // Выводим поученную пачку сообщений
int index = -1;
do { // Перебираем построчно каждый пришедший ответ
index = _response.indexOf("\r\n"); // Получаем идекс переноса строки
String submsg = "";
if (index > -1) { // Если перенос строки есть, значит
submsg = _response.substring(0, index); // Получаем первую строку
_response = _response.substring(index + 2); // И убираем её из пачки
}
else { // Если больше переносов нет
submsg = _response; // Последняя строка - это все, что осталось от пачки
_response = ""; // Пачку обнуляем
}
submsg.trim(); // Убираем пробельные символы справа и слева
if (submsg != "") { // Если строка значимая (не пустая), то распознаем уже её
Serial.println("submessage: " + submsg);
if (submsg.startsWith("+DTMF:")) { // Если ответ начинается с "+DTMF:" тогда:
String symbol = submsg.substring(7, 8); // Выдергиваем символ с 7 позиции длиной 1 (по 8)
processingDTMF(symbol); // Логику выносим для удобства в отдельную функцию
}
else if (submsg.startsWith("RING")) { // При входящем звонке...
sendATCommand("ATA", true); // ...отвечаем (поднимаем трубку)
countTry = 0; //При каждом звонке, сбрасываем счетчик попыток
}
}
} while (index > -1); // Пока индекс переноса строки действителен
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
// Отдельная функция для логики DTMF
String result = ""; // Переменная для хранения вводимых данных
String pass = "263487"; // Пароль для отпирания, на телефонной клавиатуре слово codius
void processingDTMF(String symbol) {
Serial.println("Key: " + symbol); // Выводим в Serial для контроля, что ничего не потерялось
if (countTry < 3) { // Если 3 неудачных попытки, перестаем реагировать на нажатия
if (symbol == "#") {
if (result == pass) { // Введенная строка совпадает с заданным паролем
Serial.println("The correct password is entered: " + result); // Информируем о корректном вводе пароля
countTry = 0; // Обнуляем счетчик неудачных попыток ввода
setLEDState(HIGH); // Включили светодиоды - замок открыли
delay(1000); // на 1 секунду
setLEDState(LOW); // и выключили светодиоды - замок закрыли
}
else {
countTry += 1; // Увеличиваем счетчик неудачных попыток на 1
Serial.println("Incorrect password"); // Неверный пароль
Serial.println("Counter:" + (String)countTry);// Количество неверных попыток
}
result = ""; // После каждой решетки сбрасываем вводимую комбинацию
}
else {
result += symbol; // Все, что приходит, собираем в одну строку
}
}
}
void setLEDState(bool state) { // Функция изменения состояния всех светодиодов
for (int i = 0; i < 3; i++) {
digitalWrite(pins[i], state);
}
} |
|
32 |
В следующем примере, для демонстрации управления устройствами будем использовать ту же схему с 3 светодиодами. Команды будут состоять из двух цифр — первая цифра будет обозначать номер светодиода (1-3), вторая — его состояние (1 — включен, 0 — выключен). Для разделения команд, и для того, чтобы микроконтроллер понял что команда введена, каждая команда будет завершаться решеткой #. Получается, что допустимых команд (сочетаний цифр) всего 6: 11#, 10#, 21#, 20#, 31#, 30#. Прочие команды будут игнорироваться. Если перед решеткой было введено больше чем 2 цифры, будут использованы только 2 последних. Если введено менее 2 цифр или введена некорректная команда, сообщение об этом будет выведено в монитор. Скетч: |
|
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 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
int pins[3] = {5, 6, 7}; // Пины с подключенными светодиодами
String _response = ""; // Переменная для хранения ответов модуля
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(pins[i], OUTPUT);
}
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
_response = sendATCommand("AT", true); // Проверка общего статуса
_response = sendATCommand("AT+DDET=1,0,0", true); // Включаем DTMF
}
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\n", 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(); // Получаем ответ от модема для анализа
Serial.println(">" + _response); // Выводим поученную пачку сообщений
int index = -1;
do { // Перебираем построчно каждый пришедший ответ
index = _response.indexOf("\r\n"); // Получаем идекс переноса строки
String submsg = "";
if (index > -1) { // Если перенос строки есть, значит
submsg = _response.substring(0, index); // Получаем первую строку
_response = _response.substring(index + 2); // И убираем её из пачки
}
else { // Если больше переносов нет
submsg = _response; // Последняя строка - это все, что осталось от пачки
_response = ""; // Пачку обнуляем
}
submsg.trim(); // Убираем пробельные символы справа и слева
if (submsg != "") { // Если строка значимая (не пустая), то распознаем уже её
Serial.println("submessage: " + submsg);
if (submsg.startsWith("+DTMF:")) { // Если ответ начинается с "+DTMF:" тогда:
String symbol = submsg.substring(7, 8); // Выдергиваем символ с 7 позиции длиной 1 (по 8)
processingDTMF(symbol); // Логику выносим для удобства в отдельную функцию
}
else if (submsg.startsWith("RING")) { // При входящем звонке...
sendATCommand("ATA", true); // ...отвечаем (поднимаем трубку)
}
}
} while (index > -1); // Пока индекс переноса строки действителен
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
// Отдельная функция для логики DTMF
String result = ""; // Переменная для хранения вводимых данных
void processingDTMF(String symbol) {
Serial.println("Key: " + symbol); // Выводим в Serial для контроля, что ничего не потерялось
if (symbol == "#") {
bool correct = false; // Для оптимизации кода, переменная корректности команды
if (result.length() == 2) {
int ledIndex = ((String)result[0]).toInt(); // Получаем первую цифру команды - адрес устройства (1-3)
int ledState = ((String)result[1]).toInt(); // Получаем вторую цифру команды - состояние (0 - выкл, 1 - вкл)
if (ledIndex >= 1 && ledIndex <= 3 && (ledState == 0 or ledState == 1)) { // Если все нормально, исполняем команду
Serial.println("LED:" + (String)ledIndex + " set to " + (ledState == 0 ? "OFF" : "ON"));
digitalWrite(pins[ledIndex - 1], ledState); // Исполняем команду
correct = true; // Флаг корректности команды
}
}
if (!correct) Serial.println("Incorrect command: " + result); // Если команда некорректна, выводим сообщение
result = ""; // После каждой решетки сбрасываем вводимую комбинацию
}
else if ( symbol != "*") { // Игнорируем звездочки
if (result.length() >= 2) { // Если в переменной result уже есть цифры, вытесняем первую и добавляем вновь прибывшую
result = result.substring(1, 2) + symbol;
}
else {
result += symbol; // Если нет, добавляем в конец
}
}
} |
|
34 |
Использование DTMF — уровень МАГ Дополнительные параметры позволяют превратить такую простую вещь как DTMF в достаточно мощный инструмент управления. В совокупности с получением данных о длительности нажатия каждой клавиши, можно реализовывать механизмы с повышенной степенью защищенности. Например, кодовый замок, помимо цифровой комбинации, можно запрограммировать на, специфичные по длительности, удержания каждой из цифр. Таким образом получится удвоить уровень безопасности. Для этого необходимо включить режим DTMF с параметром reportMode=1 — AT+DDET=1,0,1. Теперь, помимо декодированного значения нажатой кнопки, ответ будет содержать через запятую, длительность нажатия в миллисекундах. |
|
36 |
Именно это второе значение длительности нажатия, и будет использоваться в следующем примере. |
|
37 |
Рассмотрим предыдущий пример отпирания кодового замка, скорректированный с учетом длительности нажатий каждой из цифр. |
|
38 | 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 #include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу
SoftwareSerial SIM800(8, 9); // RX, TX
int pins[3] = {5, 6, 7}; // Пины с подключенными светодиодами
String _response = ""; // Переменная для хранения ответов модуля
void setup() {
for (int i = 0; i < 3; i++) {
pinMode(pins[i], OUTPUT);
}
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
Serial.println("Start!");
_response = sendATCommand("AT", true); // Проверка общего статуса
_response = sendATCommand("AT+DDET=1,0,1", true); // Включаем DTMF
}
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\n", 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; // ... возвращаем результат. Пусто, если проблема
}
int countTry = 0;
void loop() {
if (SIM800.available()) { // Если модем, что-то отправил...
_response = waitResponse(); // Получаем ответ от модема для анализа
Serial.println(">" + _response); // Выводим поученную пачку сообщений
int index = -1;
do { // Перебираем построчно каждый пришедший ответ
index = _response.indexOf("\r\n"); // Получаем идекс переноса строки
String submsg = "";
if (index > -1) { // Если перенос строки есть, значит
submsg = _response.substring(0, index); // Получаем первую строку
_response = _response.substring(index + 2); // И убираем её из пачки
}
else { // Если больше переносов нет
submsg = _response; // Последняя строка - это все, что осталось от пачки
_response = ""; // Пачку обнуляем
}
submsg.trim(); // Убираем пробельные символы справа и слева
if (submsg != "") { // Если строка значимая (не пустая), то распознаем уже её
Serial.println("submessage: " + submsg);
if (submsg.startsWith("+DTMF:")) { // Если ответ начинается с "+DTMF:" тогда:
String symbol = submsg.substring(7, 8); // Выдергиваем код нажатой кнопки с 7 позиции длиной 1 (по 8)
String duration = submsg.substring(9, submsg.length()); // Выдергиваем продолжительность нажатия
processingDTMF(symbol, duration); // Логику выносим для удобства в отдельную функцию
}
else if (submsg.startsWith("RING")) { // При входящем звонке...
sendATCommand("ATA", true); // ...отвечаем (поднимаем трубку)
countTry = 0; //При каждом звонке, сбрасываем счетчик попыток
}
}
} while (index > -1); // Пока индекс переноса строки действителен
}
if (Serial.available()) { // Ожидаем команды по Serial...
SIM800.write(Serial.read()); // ...и отправляем полученную команду модему
};
}
// Отдельная функция для логики DTMF
String result = ""; // Переменная для хранения вводимых данных
String pass = "263487"; // Пароль для отпирания, на телефонной клавиатуре слово codius
int dur[6] = {1000, 2000, 3000, 200, 200, 200}; // Время удержания каждой клавиши, в мс: c - 1 сек, o - 2 сек, d - 3 сек и т.д.
// Для тренировки можно просто включить монитор без обработки и посмотреть на получаемое время
int error = 500; // Погрешность в мс, в обе стороны, т.е. 1 сек +/-0.5 сек
void processingDTMF(String symbol, String duration) {
Serial.println("Key: " + symbol + ", Duration:" + duration); // Выводим в Serial для контроля, что ничего не потерялось
if (countTry < 3) { // Если 3 неудачных попытки, перестаем реагировать на нажатия
if (symbol == "#") {
if (result == pass) { // Введенная строка совпадает с заданным паролем
Serial.println("The correct password is entered: " + result); // Информируем о корректном вводе пароля
countTry = 0; // Обнуляем счетчик неудачных попыток ввода
setLEDState(HIGH); // Включили светодиоды - замок открыли
delay(1000); // на 1 секунду
setLEDState(LOW); // и выключили светодиоды - замок закрыли
}
else {
countTry += 1; // Увеличиваем счетчик неудачных попыток на 1
Serial.println("Incorrect password"); // Неверный пароль
Serial.println("Counter:" + (String)countTry);// Количество неверных попыток
}
result = ""; // После каждой решетки сбрасываем вводимую комбинацию
}
else {
// Вся магия будет происходить здесь
// Самое главное более-менее попасть в первые 3 цифры, остальные нужно просто быстро нажать
int vardur = duration.toInt();
if (vardur > dur[result.length()] - error && // Если длительность удержания попадает в заданный диапазон
vardur < dur[result.length()] + error) {
result += symbol; // Собираем пароль в одну строку
}
else {
result = ""; // Что-то не так - сбросили комбинацию, но набирающий этого не знает...
Serial.println("Invalid duration: " + symbol + ", Duration:" + duration); // А мы знаем
}
}
}
}
void setLEDState(bool state) { // Функция изменения состояния всех светодиодов
for (int i = 0; i < 3; i++) {
digitalWrite(pins[i], state);
}
} |
|
39 |
Теперь пароль снабжен дополнительным уровнем безопасности. |
|
40 |
Отправка DTMF-сигналов Иногда может возникнуть необходимость отправки DTMF-сигналов (например, программная навигация по DTMF-меню). Для этого спецификация также предоставляет ряд команд: |
|
|
42 | На заметку: |
Необходимо помнить, что при генерации DTMF-сигналов, между тонами вставляется пауза в 100 мс. Таким образом, общее время генерации заданной последовательности увеличивается на 100 мс × количество тонов.
|
|
44 |
Похожие запросы:
|
|