Обновлено 19 марта 2018
Кравченко Виктор

GSM-модуль SIM800L: часть 4 — DTMF, управление при помощи DTMF

Цифровые устройства Arduino Arduino Lang Умный дом Датчики, модули Протоколы Микроконтроллеры и мини ПК
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. Команда и её параметры подробно описаны в таблице:

06
Описание Команда, описание параметров Ответы Незапрашиваемый ответ
Включение режима DTMF AT+DDET=<mode>[,<interval>][,<reportMode>][,<ssdet>]
<mode> — управление режимом:
0 — выключен
1 — включен
<interval> — минимальный интервал в миллисекундах между двумя нажатиями одной и той же клавиши (диапазон допустимых значений 0-10000). По умолчанию — 0.
<reportMode> — режим предоставления информации:
0 — только код нажатой кнопки
1 — код нажатой кнопки и время удержания нажатия, в мс
<ssdet> — управление функцией определения одночастотного звука (не используется):
0 — выключена
1 — включена
OK,
Error
Если <reportMode>=0, то:
+DTMF: <key>

Если <reportMode>=1, то:
+DTMF: <key>,<last time>

<key> — идентификатор нажатой кнопки (0-9, *, #, A, B, C, D)
<last time> — продолжительность удержания нажатой кнопки, в мс
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()); // ...и отправляем полученную команду модему }
09
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

После запуска скетча, казалось бы все работает, если кнопки нажимать редко. Но если быстро нажать несколько кнопок подряд, то станет видно, что что-то не так — МК обработает только первую из полученных команд, а остальные пропустит:

13
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

Проверяем и убеждаемся, что все работает отлично:

24
25

Примеры

Теперь можно приступать к выполнению каких-либо значимых задач. Самыми очевидными (но не исключительными) видятся 2 варианта использования DTMF:

  • отправка цифровых комбинаций для проверки безопасности, например, кодовых замков;
  • отправка адресных команд в цифровом виде.

26

Приведем примеры реализации обоих вариантов. Используемая схема подключения:

27
Резисторы для подключения светодиодов 100 Ом, для подключения модуля — 10 КОм
Резисторы для подключения светодиодов 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=1AT+DDET=1,0,1. Теперь, помимо декодированного значения нажатой кнопки, ответ будет содержать через запятую, длительность нажатия в миллисекундах.

35
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-меню). Для этого спецификация также предоставляет ряд команд:

41
Описание Команда, описание параметров Пример
Установка длительности тонового сигнала AT+VTD=<n>
<n> — длительность сигнала, значение из диапазона 1...255 (в 1/10 секунды)
AT+VTD=5 — длительность сигнала 0,5 секунды

OK
Генератор DTMF-сигналов (отправка) AT+VTS=<dtmf-string>
<dtmf-string> — последовательность из максимум 20 символов (0-9,A,B,C,D,*,#), разделенных запятыми; вся строка заключена в кавычки
AT+VTS="1,4,#,5,6,*,A"

OK
Локальный генератор DTMF-сигналов (воспроизведение через динамик) AT+CLDTMF=<n>,<DTMF string>[<timeBase>]
<n> — коэффициент продолжительности DTMF-сигнала, значение из диапазона 1...100
<DTMF string> — последовательность из максимум 20 символов (0-9,A,B,C,D,E,F,*,#), разделенных запятыми; вся строка заключена в кавычки
<timeBase> — базовое время звучания (по умолчанию 100 мс); продолжительность звучания каждого тона <n>*<timeBase>
AT+CLDTMF=5,"1,4,#,5,6,*,A" — каждый тон по 0,5 сек
или
AT+CLDTMF=5,"1,4,#,5,6,*,A", 400 — каждый тон по 2 сек

OK
42 На заметку:
Необходимо помнить, что при генерации DTMF-сигналов, между тонами вставляется пауза в 100 мс. Таким образом, общее время генерации заданной последовательности увеличивается на 100 мс × количество тонов.
44

Похожие запросы:

  • GSM модуль SIM800L — самое полное руководство на русском языке
  • Correct parsing DTMF
  • Как правильно обработать DTMF
  • SMS PDU Mode
  • Декодирование SMS-сообщений в формате PDU
  • Как расшифровать PDU-формат
  • Использование PDU формата для оповещения через SMS сообщения
  • Remote control by Arduino phone DTMF (with SIM800)
comments powered by HyperComments