Будучи независимым подрядчиком столкнулся в одной организации с охранной системой. В её основе лежала достаточно несложная железка с GSM-контроллером, это позволяло использовать её там, где нельзя или нецелесообразно тянуть витую пару. Но была и своя ложка дегтя — управление контроллером происходило исключительно посредством SMS-сообщений, что, конечно, не самый удобный вариант. И если объект один и железка одна, то худо-бедно такой формат работы годится, но если таких объектов (и, соответственно, контроллеров) десятки, то управление нужно уже как-то автоматизировать. Собственно, меня попросили заняться этим вопросом.

Для примера можно ограничиться условными функциями постановки на охрану и снятия с охраны. В штатном режиме работы оператор должен с телефона (номер которого указан администраторским в базе GSM-контроллера) отправить SMS со словом «Enable» для постановки на охрану и «Disable» для снятия с охраны. Очевидно, что перед установкой на объект контроллер привязывается к определенному номеру телефона, поэтому первая отправная точка — это телефон и отправка SMS сообщений.

Сначала я подумал — почему бы не использовать имеющиеся GSM-шлюзы? Есть такие сервисы что позволяют отправять SMS из браузера, да еще и достаточно удобным образом — через обыкновенный GET-запрос, в теле которого содержится номер получателя и текст сообщения. Некоторые из таких сервисов даже не берут плату если количество отправленных сообщений за сутки укладывается в лимит, скажем, 10 шт. Однако, такие сервисы осуществляют подстановку текста вместо номера отправителя — а GSM-контроллер просто-напросто не воспринимает сообщения, отправленные с подменой, ибо безопасность. Тупик.

Едем дальше, другой из таких сервисов позволяет использовать Android-смартфон в качестве SMS-шлюза. Логика работы такая: на смартфон устанавливается приложение которое в установленный интервал времени стучится на сайт сервиса и проверяет, есть ли новые сообщения для отправки и, если есть, то отправляет SMS со смартфона. Достаточно интересный вариант, однако, у меня он не заработал — для корректной работы этого приложения требуются Google Apps (а именно, Google Play), что я не могу предоставить т.к. использовал кастомную прошивку на своем смартфоне без вышеописанного ПО. Раз готовых решений нет, то сделаем его сами.

Перво-наперво нам нужен телефон. Лучше обычную звонилку, я взял китайца на базе чипа MT6260, установил драйвера на PC и подключил телефон в режиме COM-порта через USB кабель. Вы можете взять любой телефон который сможете подключить в режиме COM-порта к компьютеру, подробнее расписывать я не буду т.к. для каждой трубки свои драйвера и свои нюансы. Главное, чтобы в диспетчере устройств вы видели ваш телефон в разделе «Порты (COM и LPT)». У меня он определился как два устройства — MTK USB Debug Port (COM8) и MTK USB Modem Port (COM7). Если у вас будет схожая ситуация, то вам нужно именно устройство модема, в моем случае, это COM7. Чтобы удостовериться в том, что это тот самый порт, я отправляю через PuTTY в COM7 команду «AT» — и мгновенно получаю ответ от модема «OK» — значит, все в порядке, это и есть искомый порт.

Как нам теперь отправить SMS через COM-порт? Мы можем управлять модемом посредством AT-команд, которые будем отправлять в COM-порт. А как отправлять данные в COM-порт? В Windows можно рассматривать COM-порт как «файл», соответственно, отправлять AT-команды модему мы будем просто записью их в определенный «файл». У меня под рукой есть древняя версия C++ Builder 6, я до сих пор им пользуюсь когда нужно быстро собрать «нативное» приложение для Windows и когда лень заморачиваться с отрисовкой окон через WinAPI. Вы, конечно, можете использовать любую IDE на свой выбор.

Первым делом нужно подключить заголовочный файл:

#include <windows.h>

После этого я объявляю глобальную переменную, предполагая, что в текущем экземпляре программы мы будем работать с одним-единственным COM-портом:

HANDLE ComPort;

Дальше, напишем функцию открытия порта:

bool OpenPort()

{

char CP[12]=″\\\\.\\COM7″;
ComPort = CreateFile(CP, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
ComPort != INVALID_HANDLE_VALUE ? return true : return false;

}

Очевидно, что порт может быть занят, поэтому функция возвращает булево значение в зависимости от того, получилось ли открыть порт. Отдельная процедура закрытия порта не имеет особого смысла т.к. осуществляется самым простым образом:

CloseHandle(ComPort);

Теперь напишем процедуру отправки SMS сообщения:

void SendSMS(String PhoneNumber, String Text)
{

DWORD lpdwBytesWritten; // Указатель на переменную, которая примет количество записанных байт в устройство

 

char S1[]=″AT+CMGF=1\r″;
WriteFile(ComPort, S1, strlen(S1), &lpdwBytesWritten, NULL);
Sleep(500);

 

char *S2=(″AT+CMGS=\″″+PhoneNumber+″\″\r).c_str();
WriteFile(ComPort, S2, strlen(S2), &lpdwBytesWritten, NULL);
Sleep(500);

 

char *S3=(Text+″\x1a\r\n″).c_str();
WriteFile(ComPort, S3, strlen(S3), &lpdwBytesWritten, NULL);

}

Дам некоторые пояснения по вышеуказанному. Первая команда AT+CMGF=1 задает режим работы модема, 0 — цифровой режим, 1 — текстовый. Сразу скажу, что такой режим не годится для отправки кириллицы, но в решаемой задаче по автоматизации взаимодействия с GSM-контроллером такого вопроса не стоит, мы имеем дело исключительно с ASCII. Вторая команда AT+CMGS= задает номер телефона получателя, очевидно, что после знака равенства мы должны его указать (он будет подставляться из переменной PhoneNumber, передаваемой в качестве аргумента функции SendSMS). Обратите внимание, что после каждой из первых двух AT-команд модему мы подставляем символ \r — разрыв строки, без этого символа команды восприняты не будет. Дальше, после того как мы отправили вторую AT-команду с номером телефона получателя модем будет ждать строку с текстом уже без всякой команды, остается только записать нужный нам текст в «файл» и завершить его тремя символами: \x1a, \r и \n. Запись \x1a в HEX-виде эквивалентна символу \z — он обозначает конец текстового файла, без этого символа модем просто-напросто не воспримет указанное нами сообщение как текст. К слову, если вы будете отправлять SMS сообщение через AT-команды модема в PuTTY, то завершить ввод текста сообщения можно через сочетание клавиш Ctrl+Z — так вы сможете передать управляющую последовательность символов, обозначающих конец текста (нажатие Enter будет просто переводить курсор на следующую строку). Между командами я поставил задержку в полсекунды — это необходимо для корректного взаимодействия с модемом, бывает так, что модем просто не успевает отработать команду как тут же ему приходит другая.

Дело осталось за малым, напишем функцию main,

int main(int argc, char* argv[])
{

if(OpenPort()) SendSMS(argv[1], argv[2]);
CloseHandle(ComPort);
return 0;

}

Разумеется, здесь нужно добавить проверки на количество принятых программой аргументов и их содержимое, можно добавить вывод в лог и т.д., подключить конфигурационный файл где указывать номер COM-порта или же вовсе передавать его в качестве одного из параметров запуска. В общем, есть еще над чем поработать. Сейчас на выходе должна получится программа, принимающая два аргумента запуска (номер телефона и текст), отправляющая SMS через AT-команды на заранее известный COM-порт. Уже сейчас можно делать батники, подвязывать их в планировщик задач да и вообще использовать по своему усмотрению.

В качестве финального штриха работы по автоматизации GSM-контроллера я написал еще небольшую обвязвку — веб-сервер который из GET-запроса вычленяет номер и текст, после чего передает программе SMS-шлюза, та, в свою очередь, по указанным реквизитам отправляет SMS. Оператору (пользователю) охранной системы, в свою очередь, достаточно иметь HTML-страничку с заранее прописанными URL на элементах — логика взаимодействий, я думаю, понятна. Забрать бинарник можно здесь.