[Перевод] Простой SMS-шлюз на Raspberry Pi

Иногда случается так, что надо отправлять откуда-нибудь SMS-сообщения. При этом неважно — откуда именно. Речь может идти о домашней системе, объединяющей кучу устройств (эти системы называют «homelab»), из которой надо отправлять уведомления. Это может быть система сигнализации, информирующая своего владельца о разных событиях, требующих его внимания. Возможно это — программный комплекс, которому нужно подтверждать правильность телефонных номеров, вводимых его пользователями. Во всех этих случаях, да и во многих других, SMS-сообщения были и остаются наилучшим способом передачи неких уведомлений по инициативе отправителя.

Но в большинстве вышеописанных сценариев использования SMS нельзя положиться на внешние SMS-шлюзы или API, так как система отправки уведомлений должна работать даже тогда, когда пропадает доступ в интернет. Особенно это важно для охранных систем, которые должны функционировать независимо от внешних источников питания.

Очевидное решение всех этих проблем заключается в поддержке собственного SMS-шлюза.

image-loader.svg

Результат этого проекта: устройство, поддерживающее полнофункциональный REST API, способное отправлять и получать сообщения

Как, с минимальными затратами, самостоятельно сделать такой шлюз?
Если в двух словах описать решение этой задачи, то оно заключается в использовании пакета gammu-smsd и кода API с GitHub. А подробный ответ на этот вопрос приведён ниже.

Компоненты


▍Raspberry Pi


image-loader.svg

Одноплатные компьютеры Raspberry Pi

На самом деле — неважно, какой именно одноплатник Raspberry Pi выбрать. Можно даже использовать самый первый — тот, что появился в 2007 году.

Если говорить об операционной системе, то тут подойдёт и Raspbian, и Alpine Linux (да и, в общем-то, любая ОС, в которой, без особых сложностей, можно установить необходимые нам пакеты). Я выбрал Alpine из-за того, что она работает с RAM-диска, то есть, внезапное отключение питания не повредит данные на SD-карте. Но я тут приведу и инструкции по настройке Raspbian.

▍3G/4G USB-модем


lh6.googleusercontent.com/5n85NqWuLeMp_Sfr4EaZOSsLJke_EYSKXrM4uo5CtI-akICGMvfX1Wj3TePfi2PZujsm1EUis7whpqu9afqqqa3wI70HgbmSp1JBp-7bNaOSOUAPpS_LeQ1PqeIaUQzEKQyLtz8j» »>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


Заглянем в телефон.

image-loader.svg

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!"


Заглянем в телефон.

image-loader.svg

Привет от демона получен

Мы, как и прежде, получили сообщение. В этот раз его доставка может занять на несколько секунд дольше, чем прежде. Такое ощущение, что демон проверяет очередь исходящих сообщений с некоторой периодичностью.

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-шлюз в каком-нибудь из ваших проектов?

image-loader.svg

© Habrahabr.ru