Реализация отправки и приёма SMS с помощью Gnokii 3
Администрирование
Статья была опубликована 8 сентября 2010 года в 19:21, а последний раз правилась 10 февраля 2020 года в 03:31.
Постоянная ссылка: http://www.nixp.ru/articles/80.html
Пример реализации отправки и приема SMS-сообщений в Debian GNU/Linux с использованием Gnokii, MySQL и Perl.
В современном мире человек не может представить своё существование без целого вороха разных высокотехнологичных устройств: начиная от электронных термометров и заканчивая сложными сетями связи. Для многих главное место среди всей электроники занимает мобильный телефон. Действительно, сложно представить себе современного человека, который не носит с собой постоянно телефон. Очень удобно: мы привыкли к тому, что нам банки присылают счета, интернет-провайдеры напоминают о необходимости оплаты, да и мелкие мастерские уже зачастую уведомляют о завершенном ремонте электроплиты или даже обуви. И всё это нам приходит с помощью SMS. Почему бы системному администратору не воспользоваться этим, казалось бы, привычным методом общения в своих целях?
Конечно же, в Сети существует много сервисов, позволяющих это сделать — чего только стоят скрипты, отсылающие SMS через сайты сотовых провайдеров. В этой статье я расскажу, как это сделать, используя Debian GNU/Linux, Gnokii и обычный мобильный телефон, подключенный к серверу через USB, Bluetooth или последовательный порт. Кроме Gnokii нам понадобится сервер MySQL (он может находиться и на другой машине), Perl, Perl-модуль DBI для работы с MySQL в скрипте отправки, а также базовые навыки работы с MySQL и GNU/Linux. Отличительная особенность данного решения — полная автономность и независимость от интернет-соединения, что позволяет строить более комплексные системы (например, аварийную сигнализацию в случае сбоя оборудования).
Установка, настройка и проверка Gnokii
Процесс установки необходимого ПО мы опустим — это слишком долго, да и читающие эту статью наверняка смогут выполнить все необходимые действия самостоятельно. Скажу лишь, что в Debian необходимые пакеты — это gnokii-smsd-mysql и libdbi-perl вместе с зависимостями.
Теперь немного о Gnokii. Проект изначально предназначался для синхронизации телефонов Nokia с компьютером, но впоследствии был расширен для работы с другими устройствами, а также получил много новых применений. Более подробно с проектом можно ознакомиться на официальном сайте www.gnokii.org/.
После установки необходимо настроить Gnokii путём редактирования файла /etc/gnokiirc. Процесс настройки очень хорошо описан в wiki.gnokii.org, но самые главные параметры — это port (для USB — скорее всего /dev/ttyACM0; COM-порты — /dev/ttyS0 и /dev/ttyS1 для COM1 и COM2 соответственно) и model («symbian» — для Symbian-устройств Nokia; «6110», «7110», «6510», «3110», «2110», «6160» — для соответствующих моделей; «AT» — для всех остальных AT-совместимых устройств). У меня, например, Motorola ROKR Z6, подключенная к USB, и настроено так:
port = /dev/ttyACM0 model = AT
Все остальные параметры оставлены по умолчанию. Замечу, что в некоторых телефонах нужно выбрать режим соединения «модем» либо «передача данных», так как с телефоном мы будем работать как с GSM-модемом, то есть посредством AT-команд. Теперь проверим работу, выполнив простую команду отправки SMS:
user@machine:~$ echo "Hello from Gnokii!" | gnokii --sendsms номер_телефона GNOKII Version 0.6.26 Send succeeded!
Телефон, на который было отправлено тестовое сообщение, должен сразу же его получить. К слову, я упустил параметр -r, который включает получение отчета о доставке. Проверим работу в обратную сторону:
user@machine:~$ gnokii --smsreader GNOKII Version 0.6.26 Entered sms reader mode...
Отправляем со своего телефона в ответ на тестовое сообщение, которое должен принять и обработать компьютер. Текст сообщения пока что значения не имеет — нам важно лишь проверить получение SMS. Видим, что сообщение получено успешно. Замечу, что на данном этапе сообщение может остаться в памяти телефона — это зависит от каждого конкретного аппарата. При использовании smsd оно будет удаляться из памяти телефона и останется исключительно в базе: этого можно не бояться — даже моя Motorola, часто уличаемая в некорректном поведении в качестве модема, нормально работала в такой связке.
Самый главный зверь — smsd
Следующий этап — настройка и запуск главной части нашей системы, а именно — демона smsd, который будет принимать, отправлять и хранить в базе все сообщения. Создаём на MySQL-сервере базу данных и пользователя для демона. Я привык делать это через phpMyAdmin, хотя можно воспользоваться любым удобным вам способом. В базу нужно загрузить структуру таблиц — SQL-файл находится в системе (/usr/share/doc/gnokii-smsd-mysql/sms.tables.mysql.sql). Обратите внимание, что в этом дампе есть и создание базы, и создание учетной записи пользователя базы, поэтому будет логичным оставить из него лишь нужную часть, включающую в себя только создание таблиц:
CREATE TABLE inbox ( id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, number varchar(20) NOT NULL DEFAULT '', smsdate datetime NOT NULL DEFAULT '0000-00-00 00:00:00', insertdate timestamp NOT NULL, text text, phone tinyint(4), processed tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (id) ); CREATE TABLE outbox ( id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, number varchar(20) NOT NULL DEFAULT '', processed_date timestamp NOT NULL, insertdate timestamp NOT NULL, text varchar(160) DEFAULT NULL, phone tinyint(4), processed tinyint(4) NOT NULL DEFAULT '0', error tinyint(4) NOT NULL DEFAULT '-1', dreport tinyint(4) NOT NULL DEFAULT '0', not_before time NOT NULL DEFAULT '00:00:00', not_after time NOT NULL DEFAULT '23:59:59', PRIMARY KEY (id) ); CREATE TABLE multipartinbox ( id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, number varchar(20) NOT NULL DEFAULT '', smsdate datetime NOT NULL DEFAULT '0000-00-00 00:00:00', insertdate timestamp NOT NULL, text text, phone tinyint(4), processed tinyint(4) NOT NULL DEFAULT '0', refnum int(8) DEFAULT NULL, maxnum int(8) DEFAULT NULL, curnum int(8) DEFAULT NULL, PRIMARY KEY(id) );
У демона нет файла конфигурации — все настройки для работы с базой задаются в командной строке. Работу с телефоном мы уже настроили и проверили — настройки читаются из /etc/gnokiirc. Попробуем запустить:
sudo /usr/sbin/smsd -u db_username -d db_name -p db_password \ -c db_hostname -m mysql -b SM -f /var/log/smsdaemon
Используемые здесь параметры db_username, db_name, db_password, db_hostname — это соответственно имя пользователя, имя базы, пароль и адрес сервера СУБД (у меня это был localhost). SM — это тип памяти, в которой хранятся SMS (в данном случае — Sim Memory); его значение подбирается скорее экспериментальным путём: в каждой конкретной модели телефона они могут быть перепутаны местами либо названы по-другому (полный список возможных значений можно найти на wiki.gnokii.org/index.php/Memory_type_codes). Здесь стоит заметить, что smsd работает немного по принципу, отличающему его от gnokii: gnokii просит телефон передавать ему все входящие SMS (из-за этого и неразбериха: некоторые телефоны сохраняют после этого входящие сообщения, некоторые — нет), а smsd проверяет наличие новых сообщений в памяти телефона, после чего принятые сообщения читаются и удаляются. Такой принцип работы отличается надежностью: например, телефон можно перезагрузить либо временно отключить — все принятые за это время SMS всё равно обработаются сервером.
После запуска мы должны увидеть в консоли сообщения об успешном подключении телефона, получении/приёме SMS и так далее. Если все работает, можно завершать процесс нажатием на Ctrl+c.
Для запуска сервера воспользуемся init-скриптом, приложенным к статье (см. в самом низу) и написанным мною (точнее, отредактированным из примера). Важно не забыть сменить значение DAEMON_ARGS на свои параметры.
Почти готово: осталось записать всё это в /etc/init.d/smsd, сделать файл исполняемым, добавить его в загрузку и запустить. Последнее в Debian делается так:
update-rc.d smsd defaults invoke-rc.d smsd start
Теперь smsd настроен, связь работает — можно начинать пожинать плоды своего труда.
Когда самое сложное позади…
Принцип действия smsd прост: всё, что приходит, — записываем в таблицу inbox (важно: значение processed устанавливается равное нулю), а всё, что есть в таблице outbox с processed, равным нулю (т.е. не обработано), — отсылаем и ставим processed = 1. Не путайте таблицы inbox и outbox — у них обеих есть это поле. Интересно заметить, что в inbox это поле совсем не используется демоном — оно оставлено специально для внешних обработчиков. Мы его используем в обработке входящих SMS для обозначения уже обработанных сообщений. Для отправки SMS мною написан небольшой модуль на Perl. trim($) добавлен сюда же просто ради удобства — мы его всё равно потом будем использовать. Модуль следует обозвать Sms.pm, а в Debian Lenny записать в /usr/local/lib/perl/5.10.0 (в других дистрибутивах — по вкусу). Важно заметить, что здесь используется фильтр номеров для Украины (для России понадобится небольшая доработка).
#!/usr/bin/perl # Sendsms for Gnokii use DBI; # конфигурация для MySQL: $mysqlHost = 'db_hostname'; $mysqlDb = 'db_name'; $mysqlUser = 'db_username'; $mysqlPassword = 'db_password'; $mysqlConnect = "dbi:mysql:$mysqlDb;$mysqlHost"; sub sendsms { my $number = @_[0]; my $text = @_[1]; if ($number =~ m/^\+?380\d{9}$/) { #print "Number is OK, sending SMS...\n"; } else { #print "Number doesn't seem to be in international format.\n"; if ($number =~ m/^80\d{9}$/) { #print "OK, number is in national format, restoring it...\n"; $number = "+3" . $number; #print "The number is now " . $number . " and it seems to be OK.\n"; } else { print "ERROR: Could not understand the number $number. Check the number and try again.\n"; return 1; } } $text = trim($text); if ($text eq '') { print "ERROR: The string is empty!\n"; } else { my $dbh = DBI->connect($mysqlConnect, $mysqlUser, $mysqlPassword); my $query = "INSERT INTO `outbox` (`number`,`text`) VALUES ('$number', '$text');"; my $sth = $dbh->prepare($query); $sth->execute(); $sth->finish(); $dbh->disconnect; #print "OK, message sent. See you later!\n"; } } # Функция trim для устранения пробелов в начале и конце строки sub trim($) { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; }
Пример использования — простая отправка SMS:
#!/usr/bin/perl # Sendsms for Gnokii use Sms; $numArgs = $#ARGV + 1; if ($numArgs != 2) { print "Usage:\n\tsendsms destination text\n"; if ($numArgs > 2) { print "\nError: too many arguments! Looks like you forgot about whitespaces\n"; } exit 1; } else { $number = $ARGV[0]; $text = $ARGV[1]; &sendsms ($number,$text); }
Теперь заставим сервер отвечать на SMS:
#!/usr/bin/perl use DBI; use Sys::Hostname; use Sms; # Конфигурация для MySQL: $mysqlHost = 'db_hostname'; $mysqlDb = 'db_name'; $mysqlUser = 'db_username'; $mysqlPassword = 'db_password'; $mysqlConnect = "dbi:mysql:$mysqlDb;$mysqlHost"; $dbh = DBI->connect($mysqlConnect, $mysqlUser, $mysqlPassword); $query = "SELECT `id`, `number`, `text` FROM `inbox` WHERE `text` REGEXP '^".hostname."*' AND `processed` = 0;"; while (true) { $sth = $dbh->prepare($query); $results = $dbh->selectall_hashref($query, 'id'); foreach my $id (keys %$results) { print "Value of ID $id is $results->{$id}->{text}\n"; $hostname = hostname; $text = $results->{$id}->{text}; if ($text =~ m/^$hostname/) { if ($text =~ m/^$hostname hello/) { sendsms($results->{$id}->{number}, "Hello to you from $hostname!"); } if ($text =~ m/^$hostname uptime/) { $message = "$hostname uptime:".qx ('uptime'); sendsms($results->{$id}->{number},$message); } $dbh->do("UPDATE `inbox` SET `processed` = '1' WHERE `id` = $id LIMIT 1 ;"); } } sleep(1); } $dbh->disconnect();
Запускаем. Если отправить серверу SMS с текстом «имя_сервера uptime», в ответ придет время работы сервера. На запрос же «имя_сервера hello» детище поприветствует своего хозяина. Простора для творчества здесь много. Единственное условие — желательно, чтобы первое слово всё равно было именем сервера — ведь обработчик может быть запущен на нескольких машинах, а значит — SMS можно адресовать на любой из них.
Вместо заключения
Внимательные читатели могли заметить, что нам никто и ничто не мешает запустить несколько обработчиков входящих сообщений на нескольких серверах (и, соответственно, слать SMS тоже с нескольких серверов) — ведь мы работаем уже не с телефоном (с ним работает smsd), а с базой. Также предложенную мной схему работы можно немного улучшить — например, посредством udev запускать smsd при подключении телефона или останавливать после отключения (работать, правда, это будет только с USB). Дело техники — сделано, дальше — лишь полёт фантазии. К примеру, я делаю рассылки с напоминаниями своим клиентам, а мой zabbix-сервер оповещает меня о проблемах. Всё в ваших руках!
Код init-скрипта
#!/bin/sh ### BEGIN INIT INFO # Provides: smsd # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Gnokii SMSD # Description: This is SMSD startup script ### END INIT INFO # Author: Vadim Abramchuk # # Do NOT "set -e" # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="Gnokii SMS daemon" NAME=smsd DAEMON=/usr/sbin/$NAME DAEMON_ARGS="-u db_username -d db_name -p db_password -c db_hostname -m mysql -b SM -f /var/log/smsdaemon" PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Read configuration variable file if it is present [ -r /etc/default/$NAME ] && . /etc/default/$NAME # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh VERBOSE=on # Define LSB log_* functions. # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon -b -m --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon -b -m --start --quiet --pidfile $PIDFILE --exec $DAEMON — \ $DAEMON_ARGS \ || return 2 # Add code here, if necessary, that waits for the process to be ready # to handle requests from services started subsequently which depend # on this one. As a last resort, sleep for some time. } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Wait for children to finish too if this is a daemon that forks # and if the daemon is only ever run from this initscript. # If the above conditions are not satisfied then add some other code # that waits for the process to drop all resources that could be # needed by services started subsequently. A last resort is to # sleep for some time. start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } # # Function that sends a SIGHUP to the daemon/service # do_reload() { # # If the daemon can reload its configuration without # restarting (for example, when it is sent a SIGHUP), # then implement that here. # start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; #reload|force-reload) # # If do_reload() is not implemented then leave this commented out # and leave 'force-reload' as an alias for 'restart'. # #log_daemon_msg "Reloading $DESC" "$NAME" #do_reload #log_end_msg $? #;; restart|force-reload) # # If the "reload" option is implemented then remove the # 'force-reload' alias # log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 exit 3 ;; esac :
Двоеточие в конце файла — это часть init-скрипта, сбрасывающая значение кода возврата.
-
Популярные в этом разделе:
- «Настройка сервера SSH (теория и практика)»,
- «Реализация отправки и приёма SMS с помощью Gnokii»,
- «Настройка сервера OpenLDAP».
Последние комментарии
- OlegL, 17 декабря 2023 года в 15:00 → Перекличка 21
- REDkiy, 8 июня 2023 года в 9:09 → Как «замокать» файл для юниттеста в Python? 2
- fhunter, 29 ноября 2022 года в 2:09 → Проблема с NO_PUBKEY: как получить GPG-ключ и добавить его в базу apt? 6
- Иванн, 9 апреля 2022 года в 8:31 → Ассоциация РАСПО провела первое учредительное собрание 1
- Kiri11.ADV1, 7 марта 2021 года в 12:01 → Логи catalina.out в TomCat 9 в формате JSON 1
Спасибо за статью, ясно и наглядно. А то я со своим телефоном через терминал общался, а тут все уже сделано до нас :)
Супер нужная информация. Давно искал такой пример реализации. Спасибо.
А как быть если выдает следующее сообщение: Getting SMS failed (location 5 from SM memory)! (PIN or PUK code required.)
Уже нашел сам :)
вместo $pin пишите свой реальный код
root@tehservis:~/.config/gnokii# gnokii --identify
GNOKII Version 0.6.30
IMEI : 356499042512073
Manufacturer: Cinterion
No flags section in the config file.
Model : MC52iR3
Product name: MC52iR3
Revision : REVISION 01.301
root@tehservis:~/.config/gnokii#
как избавиться от ошибки: No flags section in the config file.
в данном сообщении ?
в файле ~/.config/.gnokiircнапишите в конце:[flags]Cinterion=MC52iR3
спасибо большое за информацию!!! Уже запустили на сервере, смс-оповещение работает, уходят абонентам.
В чём может быть дело? Подскажите плиз.
«"»
Для запуска сервера воспользуемся init-скриптом, приложенным к статье (см. в самом низу) и написанным мною (точнее, отредактированным из примера). Важно не забыть сменить значение DAEMON_ARGS на свои параметры. Двоеточие в конце файла — это часть init-скрипта, сбрасывающая значение кода возврата.
«"»
Внезапно не нашел скрипта в статье, но нашел на другом сайте.
Спасибо вам большое! Добавил в текст статьи. Очевидно, потерялось при какой-то из миграций данных…
P.S. Из вашего комментария вырезал, чтобы не дублировалось.