Иногда случается так, что надо отправлять откуда-нибудь SMS-сообщения. При этом неважно — откуда именно. Речь может идти о домашней системе, объединяющей кучу устройств (эти системы называют «homelab»), из которой надо отправлять уведомления. Это может быть система сигнализации, информирующая своего владельца о разных событиях, требующих его внимания. Возможно это — программный комплекс, которому нужно подтверждать правильность телефонных номеров, вводимых его пользователями. Во всех этих случаях, да и во многих других, SMS-сообщения были и остаются наилучшим способом передачи неких уведомлений по инициативе отправителя.
Но в большинстве вышеописанных сценариев использования SMS нельзя положиться на внешние SMS-шлюзы или API, так как система отправки уведомлений должна работать даже тогда, когда пропадает доступ в интернет. Особенно это важно для охранных систем, которые должны функционировать независимо от внешних источников питания.
Очевидное решение всех этих проблем заключается в поддержке собственного SMS-шлюза.
Результат этого проекта: устройство, поддерживающее полнофункциональный REST API, способное отправлять и получать сообщения
Как, с минимальными затратами, самостоятельно сделать такой шлюз?
Если в двух словах описать решение этой задачи, то оно заключается в использовании пакета
Одноплатные компьютеры Raspberry Pi
На самом деле — неважно, какой именно одноплатник Raspberry Pi выбрать. Можно даже использовать самый первый — тот, что появился в 2007 году.
Если говорить об операционной системе, то тут подойдёт и Raspbian, и Alpine Linux (да и, в общем-то, любая ОС, в которой, без особых сложностей, можно установить необходимые нам пакеты). Я выбрал Alpine из-за того, что она работает с RAM-диска, то есть, внезапное отключение питания не повредит данные на SD-карте. Но я тут приведу и инструкции по настройке Raspbian.
USB-модем Huawei E303
В моём проекте используется USB-модем Huawei E303. Не все USB-модемы подойдут для подобных проектов, но практически все модемы Huawei этой серии недороги и их легко достать. Если у вас уже есть USB-модем, можно заранее поискать в интернете сведения о том, работает ли он с Raspberry Pi, чтобы не оказалось так, что он вам не подойдёт.
Понятно, что понадобится ещё и SIM-карта с тарифным планом, поддерживающим работу с SMS-сообщениями.
Я исхожу из предположения о том, что у вас уже имеется работающий одноплатный компьютер Raspberry Pi, на котором вы можете выполнять команды — либо по ssh, либо подключив к нему мышь, клавиатуру и монитор.
Нам понадобятся некоторые пакеты.
Вот команда для их установки в Apline Linux:
Вот — команда для Raspbian:
Большинство USB-модемов при подключении к компьютеру, по умолчанию, работают в режиме устройства хранения данных (storage mode), а нам нужен режим модема (modem mode). Для того чтобы понять, в каком именно режиме работает ваш модем — подключите его к настольному компьютеру. Если система монтирует его в виде USB-диска (обычно содержащего установщики разных программ и драйверов) — вам надо переключить его в режим модема.
Переключение режима устройства в различных модемах выполняется немного по-разному (тут, опять же — Google вам в помощь). В моём случае всё чудесным образом переключилось с помощью такой команды:
Команда
Вы, чтобы изменить режим работы своего модема, можете обратиться к Arch Wiki или к Ubuntuusers Wiki. Там должно быть всё необходимое.
Если система, после подключения модема, создаст
После этого аппаратная часть нашего проекта — Raspberry Pi и модем — готова к дальнейшей работе.
Что такое Gammu? Процитирую разработчиков проекта:
В этой утилите уже реализованы сложные механизмы, ответственные за разбор сообщений и за их отправку по мобильным сетям. А нам нужно лишь сообщить утилите о том, как ей общаться с модемом, который мы настроили на предыдущем шаге.
Для того чтобы узнать о том, видит ли утилита модем, можно выполнить команду
Пока всё нормально. Создадим теперь конфигурационный файл со следующим содержимым и сохраним его в папке
Имя (поле
Если для работы с вашей SIM-картой нужен PIN-код — его можно задать в соответствующем поле раздела
Для того чтобы проверить, что всё, что мы до сих пор настраивали, правильно работает, и то, что мы можем отправлять SMS-сообщения — достаточно выполнить такую команду:
Ясно, что вам надо будет, как минимум, подставить сюда свой номер телефона.
Попробуем:
Заглянем в телефон.
SMS успешно получено
Работает! На телефон пришло сообщение.
На этом можно и остановиться, автоматизировав отправку сообщений средствами командной строки. Но мне хотелось бы отправлять и получать сообщения с помощью API, к которому можно обращаться с любого устройства, находящегося в моей домашней сети, поэтому в описании этого проекта присутствует ещё два шага.
Мы, для отправки сообщений, использовали команду
На вопрос о том, что такое
Получается, что
Так как в вышеприведённом конфигурационном файле для Gammu уже содержится всё, что нужно для
Запустим
Так как теперь у нас имеется демон, который общается с модемом, пользоваться ранее рассмотренным способом отправки сообщений с помощью
Пример команды для отправки сообщения будет выглядеть так:
Заглянем в телефон.
Привет от демона получен
Мы, как и прежде, получили сообщение. В этот раз его доставка может занять на несколько секунд дольше, чем прежде. Такое ощущение, что демон проверяет очередь исходящих сообщений с некоторой периодичностью.
Пришло время отправить SMS нашей системе и выяснить, дойдёт ли оно до получателя. Для этого достаточно ответить на одно из полученных от системы сообщений и посмотреть, появится ли оно в виде файла в папке
Отправляем сообщение, ждём несколько секунд и заглядываем в папку.
Каждое из полученных сообщений сохраняется в отдельном файле. Можно догадаться, что в имени файла вида
Нам нужен простой механизм для отправки и получения сообщений с использованием API, такой, для применения которого не нужно устанавливать множество плагинов и пакетов. Я написал пару весьма компактных PHP-скриптов, которые заточены именно под эту задачу. Найти их можно здесь.
Для того чтобы они функционировали, нужен работающий демон
В директории, где лежат соответствующие .php-файлы (
Теперь всё делается до крайности просто и понятно. Для отправки сообщения достаточно обратиться по адресу такого вида:
В ответ придёт JSON-объект, в котором будут сведения о том, удалась ли отправка сообщения (
Получить сообщение с помощью нашего API тоже очень просто. Достаточно обратиться по адресу такого вида:
В ответ, в виде JSON-объекта, придут все полученные сообщения:
Теперь можно интегрировать систему отправки и получения SMS-сообщений в любые проекты. Если вы это сделаете — сообщите мне. Если захотите — я могу об этом рассказать в своём блоге.
Мне хотелось, чтобы код, обеспечивающий работу API, занимал бы как можно меньше места и потреблял бы как можно меньше ресурсов. Я попробовал несколько Python-реализаций подобного механизма, и ни один из них не выглядел достаточно компактным и простым. Для их использования нужно было компилировать код с множеством подпакетов. Кроме того, моему Raspberry Pi не хватало памяти на компиляцию некоторых из этих пакетов. Да и оказалось, что быстрее написать небольшой PHP-скрипт, чем заниматься оптимизацией Python-пакетов.
API может обрабатывать сообщения, разбиваемые при отправке на части, даже в том случае, если между частями одного сообщения, находящегося в очереди, находятся другие сообщения. Поэтому данная возможность должна работать без дополнительных настроек, так как API самостоятельно собирает сообщения, разбитые на части.
Gammu поддерживает MMS (Multimedia Messaging Service, служба мультимедийных сообщений). Но такие сообщения хранятся в бинарном формате, и я пока не нашёл документацию, посвящённую тому, как с ними работать. Буду весьма благодарен за любые сведения об этом.
Пригодится ли SMS-шлюз в каком-нибудь из ваших проектов?
Но в большинстве вышеописанных сценариев использования SMS нельзя положиться на внешние SMS-шлюзы или API, так как система отправки уведомлений должна работать даже тогда, когда пропадает доступ в интернет. Особенно это важно для охранных систем, которые должны функционировать независимо от внешних источников питания.
Очевидное решение всех этих проблем заключается в поддержке собственного SMS-шлюза.
Результат этого проекта: устройство, поддерживающее полнофункциональный REST API, способное отправлять и получать сообщения
Как, с минимальными затратами, самостоятельно сделать такой шлюз?
Если в двух словах описать решение этой задачи, то оно заключается в использовании пакета
gammu-smsd
и кода API с GitHub. А подробный ответ на этот вопрос приведён ниже.Компоненты
▍Raspberry Pi
Одноплатные компьютеры Raspberry Pi
На самом деле — неважно, какой именно одноплатник Raspberry Pi выбрать. Можно даже использовать самый первый — тот, что появился в 2007 году.
Если говорить об операционной системе, то тут подойдёт и Raspbian, и Alpine Linux (да и, в общем-то, любая ОС, в которой, без особых сложностей, можно установить необходимые нам пакеты). Я выбрал Alpine из-за того, что она работает с RAM-диска, то есть, внезапное отключение питания не повредит данные на SD-карте. Но я тут приведу и инструкции по настройке Raspbian.
▍3G/4G USB-модем
USB-модем Huawei E303
В моём проекте используется USB-модем Huawei E303. Не все USB-модемы подойдут для подобных проектов, но практически все модемы Huawei этой серии недороги и их легко достать. Если у вас уже есть USB-модем, можно заранее поискать в интернете сведения о том, работает ли он с Raspberry Pi, чтобы не оказалось так, что он вам не подойдёт.
Понятно, что понадобится ещё и SIM-карта с тарифным планом, поддерживающим работу с SMS-сообщениями.
Шаг 1: подготовка Raspberry Pi
Я исхожу из предположения о том, что у вас уже имеется работающий одноплатный компьютер Raspberry Pi, на котором вы можете выполнять команды — либо по ssh, либо подключив к нему мышь, клавиатуру и монитор.
Нам понадобятся некоторые пакеты.
Вот команда для их установки в Apline Linux:
apk add gammu gammu-smsd php php-json usb-modeswitch usbutils git
Вот — команда для Raspbian:
apt install gammu gammu-smsd php php-json usb-modeswitch git
Большинство USB-модемов при подключении к компьютеру, по умолчанию, работают в режиме устройства хранения данных (storage mode), а нам нужен режим модема (modem mode). Для того чтобы понять, в каком именно режиме работает ваш модем — подключите его к настольному компьютеру. Если система монтирует его в виде USB-диска (обычно содержащего установщики разных программ и драйверов) — вам надо переключить его в режим модема.
Переключение режима устройства в различных модемах выполняется немного по-разному (тут, опять же — Google вам в помощь). В моём случае всё чудесным образом переключилось с помощью такой команды:
usb_modeswitch -W -v 12d1 -p 14fe -K -P 14ac -M "55534243000000000000000000000011060000000000000000000000000000"
Команда
lsusb
возвращает Bus 001 Device 005: ID 12d1:1c05 HUAWEI HUAWEI Mobile
. 12d1
— это код поставщика для Huawei, а строка, идущая за этим кодом (в моём случае — 1c05
) — это ID продукта. Мы используем данные, полученные с помощью lsusb
, при вызове usb_modeswitch
.Вы, чтобы изменить режим работы своего модема, можете обратиться к Arch Wiki или к Ubuntuusers Wiki. Там должно быть всё необходимое.
Если система, после подключения модема, создаст
/dev/ttyUSB0
, это значит, что всё сделано правильно.pi:~# ls -al /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Dec 3 12:10 /dev/ttyUSB0
После этого аппаратная часть нашего проекта — Raspberry Pi и модем — готова к дальнейшей работе.
Шаг 2: установка Gammu
Что такое Gammu? Процитирую разработчиков проекта:
Утилита командной строки Gammu — это средство, дающее доступ к широкому набору телефонных возможностей.
В этой утилите уже реализованы сложные механизмы, ответственные за разбор сообщений и за их отправку по мобильным сетям. А нам нужно лишь сообщить утилите о том, как ей общаться с модемом, который мы настроили на предыдущем шаге.
Для того чтобы узнать о том, видит ли утилита модем, можно выполнить команду
gammu identify
. Если всё нормально — в ответ вы получите примерно следующее:vpnpi:~# gammu identify
Device : /dev/ttyUSB0
Manufacturer : Huawei
Model : E303 (E303)
Firmware : 21.157.01.00.199
IMEI : 860000000000619
SIM IMSI : 230000000000006
Пока всё нормально. Создадим теперь конфигурационный файл со следующим содержимым и сохраним его в папке
/etc/gammurc
:[gammu]
device = /dev/ttyUSB0
name = Bob
connection = at
logfile = /var/log/gammu.log
[smsd]
service = files
logfile = syslog
#PIN = 1234
# Увеличьте значение для получения отладочной информации
debuglevel = 0
# Пути, по которым хранятся сообщения
inboxpath = /var/spool/gammu/inbox/
outboxpath = /var/spool/gammu/outbox/
sentsmspath = /var/spool/gammu/sent/
errorsmspath = /var/spool/gammu/error/
Имя (поле
Name
) может быть любым. Я выбрал Bob
— так зовут «абонента», от имени которого отправляются сообщения.Если для работы с вашей SIM-картой нужен PIN-код — его можно задать в соответствующем поле раздела
[smsd]
.▍Быстрая проверка возможности отправки SMS
Для того чтобы проверить, что всё, что мы до сих пор настраивали, правильно работает, и то, что мы можем отправлять SMS-сообщения — достаточно выполнить такую команду:
echo "some message" | gammu --sendsms TEXT 0664xxxxxxx
Ясно, что вам надо будет, как минимум, подставить сюда свой номер телефона.
Попробуем:
vpnpi:~# echo "Hello from your Pi" | gammu --sendsms TEXT 0664xxxxxxx
If you want break, press Ctrl+C…
Sending SMS 1/1…waiting for network answer..OK, message reference=22
Заглянем в телефон.
SMS успешно получено
Работает! На телефон пришло сообщение.
На этом можно и остановиться, автоматизировав отправку сообщений средствами командной строки. Но мне хотелось бы отправлять и получать сообщения с помощью API, к которому можно обращаться с любого устройства, находящегося в моей домашней сети, поэтому в описании этого проекта присутствует ещё два шага.
Шаг 3: подготовка к созданию API для отправки и получения SMS-сообщений
Мы, для отправки сообщений, использовали команду
gammu
. Но получение сообщений — операция более сложная, её непросто автоматизировать. Правда — её можно было бы назвать сложной в том случае, если бы создатели Gammu не написали бы ещё и утилиту gammu-smsd
.▍Утилита gammu-smsd
На вопрос о том, что такое
gammu-smsd
, как и прежде, лучше всего способны ответить разработчики проекта:Gammu SMS Daemon — это программа, которая периодически сканирует SMS-модем на предмет полученных сообщений, сохраняет их в заданном месте и, кроме того, отправляет сообщения, которые находятся в хранилище и ждут отправки. Это — программа, которая отлично подходит для автоматизации управления большими объёмами принятых или отправленных сообщений.
Получается, что
gammu-smsd
— это демон, который ожидает появления новых входящих или исходящих SMS-сообщений.Так как в вышеприведённом конфигурационном файле для Gammu уже содержится всё, что нужно для
gammu-smsd
, нам осталось лишь создать папки, где gammu
будет хранить данные:mkdir -p /var/spool/gammu/inbox/
mkdir -p /var/spool/gammu/outbox/
mkdir -p /var/spool/gammu/sent/
mkdir -p /var/spool/gammu/error/
▍Отправка сообщений
Запустим
gammu-smsd
в режиме демона (то есть — позволим программе выполняться в фоновом режиме) и скажем ей о том, где находится созданный нами ранее конфигурационный файл:gammu-smsd -d -c /etc/gammurc
Так как теперь у нас имеется демон, который общается с модемом, пользоваться ранее рассмотренным способом отправки сообщений с помощью
gammu
мы уже не сможем. Правда, мы, всё же, можем их отправлять самостоятельно, но для этого нам понадобится команда gammu-smsd-inject
, созданная для использования совместно с gammu-smsd
. Теперь отправка сообщений выглядит как постановка их в локальную очередь, а демон будет брать сообщения из этой очереди и отправлять их.Пример команды для отправки сообщения будет выглядеть так:
gammu-smsd-inject TEXT 0664xxxxxxx -unicode -text "hello world from the daemon!"
Заглянем в телефон.
Привет от демона получен
Мы, как и прежде, получили сообщение. В этот раз его доставка может занять на несколько секунд дольше, чем прежде. Такое ощущение, что демон проверяет очередь исходящих сообщений с некоторой периодичностью.
pi:~# gammu-smsd-inject TEXT 0664xxxxxxx -unicode -text "hello world from the daemon!"
gammu-smsd-inject[2964]: Warning: No PIN code in /etc/gammu-smsdrc file
gammu-smsd-inject[2964]: Created outbox message OUTC20211203_193330_00_0664xxxxxxx_sms0.smsbackup
Written message with ID /var/spool/gammu/outbox/OUTC20211203_193330_00_0664xxxxxxx_sms0.smsbackup
▍Получение сообщений
Пришло время отправить SMS нашей системе и выяснить, дойдёт ли оно до получателя. Для этого достаточно ответить на одно из полученных от системы сообщений и посмотреть, появится ли оно в виде файла в папке
/var/spool/gammu/inbox/
.Отправляем сообщение, ждём несколько секунд и заглядываем в папку.
pi:~# ls /var/spool/gammu/inbox/
IN20211203_194458_00_+43664xxxxxxx_00.txt
pi:~# cat /var/spool/gammu/inbox/IN20211203_194458_00_+43664xxxxxxx_00.txt
Hello also from the outside world!
Каждое из полученных сообщений сохраняется в отдельном файле. Можно догадаться, что в имени файла вида
IN20211203_194458_00_+43664xxxxxxx_00.txt
закодированы дата, время, телефон отправителя и номер части сообщения (для SMS, длина которых превышает 140 символов, так как они разбиваются на части).Шаг 4: API для работы с сообщениями
Нам нужен простой механизм для отправки и получения сообщений с использованием API, такой, для применения которого не нужно устанавливать множество плагинов и пакетов. Я написал пару весьма компактных PHP-скриптов, которые заточены именно под эту задачу. Найти их можно здесь.
Для того чтобы они функционировали, нужен работающий демон
gammu-smsd
.В директории, где лежат соответствующие .php-файлы (
send.php
и get.php
) нужно выполнить команду php -S 0.0.0.0:8080
. Благодаря этому обратиться к ним из сети сможет любая нуждающаяся в них сущность.▍Отправка SMS с помощью API
Теперь всё делается до крайности просто и понятно. Для отправки сообщения достаточно обратиться по адресу такого вида:
http://ip.of.your.pi/send.php?phone=0664xxxxxxx&text=Testmessage
В ответ придёт JSON-объект, в котором будут сведения о том, удалась ли отправка сообщения (
status:ok
) или нет (status:error
).{
"status": "ok",
"log": "2021-12-04 15:43:39\ngammu-smsd-inject TEXT 0664xxxxxxx -unicode -text 'Testmessage'\ngammu-smsd-inject[2669]: Warning: No PIN code in /etc/gammu-smsdrc file\ngammu-smsd-inject[2669]: Created outbox message OUTC20211204_164340_00_0664xxxxxxx_sms0.smsbackup\nWritten message with ID /var/spool/gammu/outbox/OUTC20211204_164340_00_0664xxxxxxx_sms0.smsbackup\n\n\n"
}
▍Получение SMS с помощью API
Получить сообщение с помощью нашего API тоже очень просто. Достаточно обратиться по адресу такого вида:
http://ip.of.your.pi/get.php
В ответ, в виде JSON-объекта, придут все полученные сообщения:
curl -s http://ip.of.your.pi/get.php | jq .
[
{
"id": "f0a7789a657bb34eddd17c8e64609c48",
"timestamp": 1638636342,
"year": "2021",
"month": "12",
"day": "04",
"time": "16:45",
"test": "04.12.2021 16:45:42",
"sender": "+43664xxxxxxx",
"message": "Hello bob!"
},
{
"id": "c358d0a4ca868c1d7d2eedab181eddd6",
"timestamp": 1638636414,
"year": "2021",
"month": "12",
"day": "04",
"time": "16:46",
"test": "04.12.2021 16:46:54",
"sender": "+43664xxxxxxx",
"message": "Hello "
}
]
Теперь можно интегрировать систему отправки и получения SMS-сообщений в любые проекты. Если вы это сделаете — сообщите мне. Если захотите — я могу об этом рассказать в своём блоге.
Вопросы и ответы
▍Почему тут используются PHP-скрипты собственной разработки, а не gammu-python?
Мне хотелось, чтобы код, обеспечивающий работу API, занимал бы как можно меньше места и потреблял бы как можно меньше ресурсов. Я попробовал несколько Python-реализаций подобного механизма, и ни один из них не выглядел достаточно компактным и простым. Для их использования нужно было компилировать код с множеством подпакетов. Кроме того, моему Raspberry Pi не хватало памяти на компиляцию некоторых из этих пакетов. Да и оказалось, что быстрее написать небольшой PHP-скрипт, чем заниматься оптимизацией Python-пакетов.
▍Что если пользователь отправит длинное SMS-сообщение?
API может обрабатывать сообщения, разбиваемые при отправке на части, даже в том случае, если между частями одного сообщения, находящегося в очереди, находятся другие сообщения. Поэтому данная возможность должна работать без дополнительных настроек, так как API самостоятельно собирает сообщения, разбитые на части.
▍Можно ли работать с MMS?
Gammu поддерживает MMS (Multimedia Messaging Service, служба мультимедийных сообщений). Но такие сообщения хранятся в бинарном формате, и я пока не нашёл документацию, посвящённую тому, как с ними работать. Буду весьма благодарен за любые сведения об этом.
Пригодится ли SMS-шлюз в каком-нибудь из ваших проектов?