//#define DEBUG // Для режима отладки нужно раскомментировать эту строку
#ifdef DEBUG // В случае существования DEBUG
#define DEBUG_PRINT(x) Serial.print (x) // Создаем "переадресацию" на стандартную функцию
#define DEBUG_PRINTLN(x) Serial.println (x)
#else // Если DEBUG не существует - игнорируем упоминания функций
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
#include <SoftwareSerial.h> // Библиотека програмной реализации обмена по UART-протоколу на пинах отличных от 0 и 1
SoftwareSerial SIM800(9, 8); // RX, TX - Создаем класс
// Аппаратные настройки ===============================
int pins[4] = {5, 4, 3, 2}; // Пины управления реле
// Программные настройки ==============================
String phones = "+7928xxxxxxx;+7918xxxxxxx;+7920xxxxxxx"; // Список телефонов, которые могут управлять устройством. Первый номер - админ
int actualStatus = 0; // Переменная для хранения состояния устройства (0 - все закрыто, 4 - все открыто)
float balance = 0.0; // Переменная для хранения данных о балансе SIM-карты
float lowLevelBalance = 50.0; // Переменная порогового значения, при превышении - информирование о балансе
String tasks[10]; // Переменная для хранения списка задач к исполнению
bool executingTask = false; // Флаг исполнения отложенной задачи
long lastUpdate = 0; // Переменная хранящая время последней проверки
long updatePeriod = 90000; // 90 сек - Период автоматической проверки наличия сообщений (в миллисекундах, 1000 - 1 сек)
//=====================================================
void setup() {
for (int i = 0; i < 4; i++) { // Инициализируем пины реле
digitalWrite(pins[i], HIGH); // Так как эта модель реле управляется LOW-сигналом, чтобы не было ложного срабатывания ...
pinMode(pins[i], OUTPUT); // ...во время каждого включения, перед установкой режима пина, устанавливаем его значение
}
Serial.begin(9600); // Скорость обмена данными с компьютером
SIM800.begin(9600); // Скорость обмена данными с модемом
DEBUG_PRINTLN("Start!");
delay(5000); // Даем время модему завершить инициализацию
// Отправляем команды инициализации, если все в порядке мигает зеленый индикатор, если нет - красный
if (sendATCommand("AT", true).indexOf("OK") > -1); // Команда готовности GSM-модуля
if (sendATCommand("AT+CLIP=1", true).indexOf("OK") > -1); // Установка АОН
if (sendATCommand("AT+CMGF=1", true).indexOf("OK") > -1); // Установка текстового режима SMS (Text mode)
sendATCommand("AT+CMGDA=\"DEL ALL\"", true); // Удаляем все сообщения, чтобы не забивали память МК
addTask("getBalance"); // Добавляем задачу - "Запрос баланса"
// Добавляем задачу - "Отправить SMS админу о включении устройства"
addTask("sendSMS;" + phones.substring(0, phones.indexOf(";")) + ";Init - OK.\r\nStatus: " + statusToString(actualStatus));
lastUpdate = millis() + 10000; // Ближайшая проверка через 10 сек
}
String sendATCommand(String cmd, bool waiting) { // Функция отправки AT-команды
// blinkLed(pinGreen, 10); // Мигаем зеленым индикатором об отправке данных GSM-модулю
String _response = "";
DEBUG_PRINTLN(cmd); // Дублируем в Serial отправляемую команду
SIM800.println(cmd); // Отправляем команду модулю
if (waiting) { // Если нужно ждать ответ от модема...
_response = waitResponse(); // Результат ответа сохраняем в переменную
if (_response.startsWith(cmd)) { // Если ответ начинается с отправленной команды, убираем её, чтобы не дублировать
_response = _response.substring(_response.indexOf("\r\n", cmd.length()) + 2);
}
DEBUG_PRINTLN(_response); // Выводим ответ в Serial
return _response; // Возвращаем ответ
}
return ""; // Еси ждать ответа не нужно, возвращаем пустую строку
}
String waitResponse() { // Функция ожидания ответа от GSM-модуля
String _buffer; // Переменная для хранения ответа
long _timeout = millis() + 10000; // Таймаут наступит через 10 секунд
while (!SIM800.available() && millis() < _timeout) {}; //Ждем...
if (SIM800.available()) { // Если есть что принимать...
_buffer = SIM800.readString(); // ...принимаем
//DEBUG_PRINTLN("Ok - response");
return _buffer; // и возвращаем полученные данные
}
else { // Если таймаут вышел...
//DEBUG_PRINTLN("Timeout...");
}
return ""; // и возвращаем пустую строку
}
bool hasmsg = false; // Флаг наличия сообщений к удалению
void loop() {
String _buffer = ""; // Переменная хранения ответов от GSM-модуля
if (millis() > lastUpdate && !executingTask) { // Цикл автоматической проверки SMS, повторяется каждые updatePeriod (90 сек)
do {
_buffer = sendATCommand("AT+CMGL=\"REC UNREAD\",1", true); // Отправляем запрос чтения непрочитанных сообщений
if (_buffer.indexOf("+CMGL: ") > -1) { // Если есть хоть одно, получаем его индекс
int msgIndex = _buffer.substring(_buffer.indexOf("+CMGL: ") + 7, _buffer.indexOf("\"REC UNREAD\"", _buffer.indexOf("+CMGL: "))).toInt();
char i = 0; // Объявляем счетчик попыток
do {
i++; // Увеличиваем счетчик
_buffer = sendATCommand("AT+CMGR=" + (String)msgIndex + ",1", true); // Пробуем получить текст SMS по индексу
_buffer.trim(); // Убираем пробелы в начале/конце
if (_buffer.endsWith("OK")) { // Если ответ заканчивается на "ОК"
getActionBySMS(_buffer); // Отправляем текст сообщения на обработку
if (!hasmsg) hasmsg = true; // Ставим флаг наличия сообщений для удаления
sendATCommand("AT+CMGR=" + (String)msgIndex, true); // Делаем сообщение прочитанным
break; // Выход из do{}
}
else { // Если сообщение не заканчивается на OK
//Serial.println ("Error answer");
}
sendATCommand("\n", true); // Перестраховка - вывод новой строки
} while (i < 10); // Пока попыток меньше 10
break;
}
else {
lastUpdate = millis() + updatePeriod; // Если все обработано, обновляем время последнего обновления
if (hasmsg) { // Если были сообщения для обработки
addTask("clearSMS"); // Добавляем задание для удаления сообщений
hasmsg = false; // Сбрасываем флаг наличия сообщений
}
break; // Выходим из цикла
}
} while (1);
}
if (millis() > lastUpdate + 180000 && executingTask) { // Таймаут на выполнение задачи - 3 минуты
//DEBUG_PRINTLN("ExTask-true!");
sendATCommand("\n", true);
executingTask = false; // Если флаг не был сброшен по исполению задачи, сбрасываем его принудительно через 3 минуты
}
if (SIM800.available()) { // Ожидаем прихода данных (ответа) от модема...
// blinkLed(pinGreen, 50); // Данные пришли - мигаем зеленым светодиодом
String msg = waitResponse(); // Получаем ответ от модема для анализа
msg.trim(); // Убираем ненужные пробелы в начале/конце
DEBUG_PRINTLN(".. " + msg); // ...и выводим их в Serial
// blinkLed(pinGreen, 50); // Мигаем зеленым светодиодом о приходе данных
if (msg.startsWith("+CUSD:")) { // Если USSD-ответ о балансе
String msgBalance = msg.substring(msg.indexOf("\"") + 2); // Парсим ответ
msgBalance = msgBalance.substring(0, msgBalance.indexOf("\n"));
balance = getDigitsFromString(msgBalance); // Сохраняем баланс
deleteFirstTask(); // Удаляем задачу
executingTask = false; // Сбрасываем флаг исполнения
DEBUG_PRINTLN("Balance: " + (String)balance); // Отчитываемся в Serial
}
else if (msg.startsWith("+CMGS:")) { // Результат отправки сообщения
deleteFirstTask(); // Удаляем задачу
executingTask = false; // Сбрасываем флаг исполнения
DEBUG_PRINTLN("SMS sending - task removed."); // Отчитываемся в Serial
addTask("getBalance"); // Добавляем задачу запроса баланса
}
else if (msg.startsWith("RING")) { // При входящем вызове
sendATCommand("ATH", true); // Всегда сбрасываем
}
else if (msg.startsWith("+CMTI:")) { // Незапрашиваемый ответ - приход сообщения
lastUpdate = millis(); // Сбрасываем таймер автопроверки наличия сообщений
}
else if (msg.startsWith("ERROR")) { // Ошибка исполнения команды
//DEBUG_PRINTLN("Error executing last command.");
executingTask = false; // Сбрасываем флаг исполнения, но задачу не удаляем - на повторное исполнение
}
}
if (!executingTask && tasks[0] != "") { // Если никакая задача не исполняется, и список задач не пуст, то запускаем выполнение.
showAllTasks(); // Показать список задач
String task = tasks[0];
if (tasks[0].startsWith("sendSMS")) { // Если задача - отправка SMS - отправляем
task = task.substring(task.indexOf(";") + 1);
executingTask = true; // Флаг исполнения в true
sendSMS(task.substring(0, task.indexOf(";")),
task.substring(task.indexOf(";") + 1));
}
else if ((tasks[0].startsWith("getBalance"))) { // Задача - запрос баланса
executingTask = true; // Флаг исполнения в true
sendATCommand("AT+CUSD=1,\"#102#\"", true); // Отправка запроса баланса
}
else if ((tasks[0].startsWith("clearSMS"))) { // Задача - удалить все прочитанные SMS
sendATCommand("AT+CMGDA=\"DEL READ\"", true); // Флаг исполнения не устанавливаем - здесь не нужно.
deleteFirstTask(); // Удаляем задачу, сразу после исполнения
}
else {
//DEBUG_PRINTLN("Error: unknown task - " + task);
}
}
}
void getActionBySMS(String msg) { // Функция получения действия из SMS
String msgheader = "";
String msgbody = "";
String msgphone = "";
// Парсим SMS, получаем телефон и текст
msg = msg.substring(msg.indexOf("+CMGR: "));
msgheader = msg.substring(0, msg.indexOf("\r"));
msgbody = msg.substring(msgheader.length() + 2);
msgbody = msgbody.substring(0, msgbody.lastIndexOf("OK"));
msgbody.trim();
int firstIndex = msgheader.indexOf("\",\"") + 3;
int secondIndex = msgheader.indexOf("\",\"", firstIndex);
msgphone = msgheader.substring(firstIndex, secondIndex);
msgbody.toLowerCase();
if (msgphone.length() > 10 && phones.indexOf(msgphone) > -1) { // Если номер присутствует в списке номеров
String result = ""; // Обрабатываем это сообщение
if (msgbody.startsWith("open")) { // Если команда "открыть ячейку",
int blockNum = 0; // Открываем с использованием защитных механизмов
if (msgbody.length() > 4) {
blockNum = msgbody.substring(4).toInt();
if (blockNum > 4 || blockNum < 1) blockNum = -1;
}
int blockToOpen = -1;
int block = blockNum;
// actualStatus = getStatus(); // Получаем актуальный статус
if (blockNum == -1) { // Ошибочно указан номер ячейки
result = "X - Err: " + msgbody.substring(4, 7);
}
else if (blockNum == 0) { // Номер блока не указан - автоматическое открывание
if (actualStatus >= 4) { // Если уже все открыто, то сообщаем об этом
result = "X";
}
else {
blockToOpen = actualStatus;
result = "OK auto: " + (String)(actualStatus + 1);
}
}
else { // Открывание по номеру ячейки
if (blockNum != actualStatus + 1) {
if (blockNum < actualStatus + 1) {
result = "X:" + (String)block;
}
else {
result = "X:" + (String)block;
}
}
else {
blockToOpen = actualStatus;
result = "OK: " + (String)block;
}
}
//DEBUG_PRINTLN("blockToOpen: " + (String)blockToOpen);
if (blockToOpen >= 0 && blockToOpen < 4) { // Если номер ячейки в допустимых пределах
digitalWrite(pins[blockToOpen], LOW); // Включаем электромагнитный переключатель на 1 секунду
delay(1000);
digitalWrite(pins[blockToOpen], HIGH);
}
// actualStatus = getStatus(); // Получаем актуальный статус
result += "\r\nStatus: " + statusToString(actualStatus); // Записываем его в переменную с результатом
if (balance < lowLevelBalance) { // Если баланс ниже заданного предела, добавляем информацию в результат
result += "\r\nBalance: " + (String)balance;
}
//DEBUG_PRINTLN("result: " + result);
addTask(getSendSMSTaskString(msgphone, result)); // Добавляем задачу отправки SMS о статусе
addTask("getBalance"); // Добавляем задачу запроса баланса
showAllTasks(); // Выводим список задач
}
else if (msgbody.startsWith("force")) { // Команда грубого открытия ячейки
int blockNum = 0;
if (msgbody.length() > 5) {
blockNum = msgbody.substring(5).toInt();
if (blockNum > 4 || blockNum < 1) blockNum = -1;
}
if (blockNum > 0) {
for (int i = 0; i < 4; i++) {
digitalWrite(pins[blockNum - 1], LOW);
delay(200);
digitalWrite(pins[blockNum - 1], HIGH);
delay(700);
}
}
}
else if (msgbody.startsWith("status")) { // Команда запроса статуса
result = sendATCommand("AT+CSQ", true); // Добавляем информацию о качестве сигнала
result = result.substring(0, result.indexOf("\n"));
//DEBUG_PRINTLN("Status: " + statusToString(actualStatus));
addTask(getSendSMSTaskString(msgphone, "Status: " + statusToString(actualStatus) + "\n" + result)); // Добавляем задачу об отправке SMS со статусом
addTask("getBalance"); // Добавляем задачу о запросе баланса
showAllTasks(); // Выводим все задачи
}
else if (msgbody.startsWith("balance")) { // Команда запроса баланса
addTask(getSendSMSTaskString(msgphone, "Balance: " + String(balance)));// Добавляем задачу об отправке SMS с балансом
addTask("getBalance"); // Добавляем задачу о запросе баланса
showAllTasks(); // Выводим все задачи
}
else if (msgbody.startsWith("callme")) { // Команда осуществить исходящий вызов
sendATCommand("ATD" + msgphone + ";", true);
}
else if (msgbody.startsWith("test")) { // Команда тест - открываем поочереди все ячейки
for (int i = 0; i < 4; i++) {
digitalWrite(pins[i], LOW);
delay(500);
digitalWrite(pins[i], HIGH);
delay(1000);
}
}
else if (msgbody == "?" || msgbody == "help") { // Команда получения помощи по командам
// String task=getSendSMSTaskString(msgphone, getHelpSMS());
// DEBUG_PRINTLN("task: " + task);
// addTask(task);
addTask(getSendSMSTaskString(msgphone, getHelpSMS()));
addTask("getBalance");
showAllTasks();
}
else if (msgbody.startsWith("checknow")) { // Обнулить таймер периодической проверки - проверить сразу
lastUpdate = millis();
}
}
else {
//DEBUG_PRINTLN("Unknown phonenumber");
}
}
String getHelpSMS() { // Текст сообщения с помощью по камандам
return "Balance, CallMe, Open, Open[1-4], Status, Test, Force";
}
String getSendSMSTaskString( String phone, String msg) { // Формируем строку задачи отправки SMS
return "sendSMS;" + phone + ";" + msg;
}
// =================================== Tasks =========================================
void showAllTasks() { // Показать все задачи
DEBUG_PRINTLN("All Tasks:");
for (int i = 0; i < 10; i++) {
if (tasks[i] == "") break;
DEBUG_PRINTLN("Task " + (String)(i + 1) + ": " + tasks[i]);
}
}
void deleteFirstTask() { // Удалить первую задачу, остальные передвинуть вверх на 1
for (int i = 0; i < 10 - 1; i++) {
tasks[i] = tasks[i + 1];
if (tasks[i + 1] == "") break;
}
}
void addTask(String task) { // Добавить задачу в конец очереди
for (int i = 0; i < 10; i++) {
if (tasks[i] == task && (task == "clearSMS" || task == "getBalance")) {
DEBUG_PRINTLN("Task already exists " + (String)(i + 1) + ": " + task);
return;
}
if (tasks[i] == "") {
tasks[i] = task;
DEBUG_PRINTLN("Task " + (String)(i + 1) + " added: " + task);
return;
}
}
DEBUG_PRINTLN("Error!!! Task NOT added: " + task);
}
void sendSMS(String phone, String message) // Функция отправки SMS
{
sendATCommand("AT+CMGS=\"" + phone + "\"", true); // Переходим в режим ввода текстового сообщения
sendATCommand(message + "\r\n" + (String)((char)26), true); // После текста отправляем перенос строки и Ctrl+Z+
}
float getDigitsFromString(String str) { // Функция выбора цифр из сообщения - для парсинга баланса из USSD-запроса
bool flag = false;
String digits = "0123456789.";
String result = "";
for (int i = 0; i < str.length(); i++) {
char symbol = char(str.substring(i, i + 1)[0]);
if (digits.indexOf( symbol) > -1) {
result += symbol;
if (!flag) flag = true;
}
else {
if (flag) break;
}
}
return result.toFloat();
}
// ================================== Status ============================================
String statusToString(int stat) { // Статус в строку для отправки в SMS ("2" -> "OO--")
String result = "";
for (int i = 0; i < 4; i++) {
result += i < stat ? "O" : " -";
}
return result;
}