ISP`s IT Аутсорсинг
Быстрый переход: Главная блога Главная сайта Форум
Если Вы чего то недопоняли или не нашли - задайте
вопрос на нашем форуме и мы попробуем Вам помочь.
Subnets.ru Регистрация IP и Автономных систем mega-net.ru

Предыстория

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

Для реализации этого проекта были куплены GSM модем Siemens MC35i и 1-портовый асинхронный сервер Moxa NPort-5110, обеспечивающий преобразование интерфейса RS-232 в Ethernet и конечно же сервер под FreeBSD 🙂

Moxa NPort-5110 был приобретен для цели отсылки SMS-сообщений из любого сегмента сети, а не только с машины, к которой подключен GSM-шлюз.

Теория с примерами

Итак, конфигурация схемы следующая:

GSM модем включен в Moxa NPort-5110 кабелем RS-232.

Moxa имеет адрес 10.100.0.2:4001. Для отправки сообщения на латинице нужно выполнить следующую последовательность команд на модеме:

telnet 10.100.0.2 4001
AT+CPIN=7256
OK
AT+CMGF=1
OK
AT+CMGS="+79101234567"
>test,test,testCTRL+Z
+CMGS 

где CTRL+Z — комбинация клавиш, нажатие которой означает конец сообщения.

С отправкой сообщения «Ахтунг!» на кириллице на номер +79101234567 все сильно сложнее, т.к. сообщение должно уходить пакетом в формате PDU в кодировке UCS2 (юникодная кодировка, поддерживающая в том числе кириллицу), а потому рассмотрим этот случай подробнее.

Пример:

telnet 10.100.0.2 4001
AT+CPIN=7256
OK
AT+CMGF=0
OK
AT+CMGS=28
>0011000B919701214365F70018C10E0410044504420443043D04330021CTRL+Z
+CMGS

Разберем поподробнее эту «кашу»:

AT+CMGF=0 - установка модема в PDU-режим
AT+CMGS=28 - длина строки пакета /2 -1
0011000B919701214365F70018C10E0410044504420443043D04330021 - сам PDU пакет

Формат PDU-пакета представляет собой 16-ричную последовательность, передающуюся человекочитаемой строкой (не ASCII-представления самих 16-ричных чисел). Оригинал описания (сохраненная копия) формата PDU пакета (на английском).

Перевод и коментарии:
00 Длина и номер SMS-центра провайдера. у нас — дефолтный из настроек GSM
11 Сообщение SMS-Submit (сохраненная копия)
0B Длина телефонного номера получателя (количество цифр в нем — в нашем случае 11)
91 Тип телефонного номера получателя (у нас получается «международный тип с планом нумерации Е.164/E.163) (сохраненная копия)
6 байт телефонный номер получателя (кодировка описана ниже)
00 Протокол (00 — SMS) (сохраненная копия)
18 Кодирование данных (08=UCS2,00-latin 7 bit etc. Старший байт — не сохранять в истории получателя) (сохраненная копия)
С1 Доставка актуальна 1 неделю (как рассчитывается) (сохраненная копия)
0E Длина сообщения («байт». длина символов кодированной UCS2-строки/2)
xx байт Кодированное сообщение (текст в UCS2, 16-ричное представление)

по-порядку подробней:

  • первый байт на некоторых телефонах передавать не следует (работа возможна только через SMS-центр провайдера, телефон другого не умеет)
  • номер получателя (алгоритм создавали наркаманы). кодируется «как есть» путем переставления местами соседних цифр, «добивается» до длины в 12 символов символами «F». например номер доблестной советской милиции (02 кто забыл) кодируется как 20FFFFFFFFFF, а номер из нашего примера (+79101234567) — как 9701214365F7. З.Ы. Не забывайте что длина SMS-сообщения ограничена, в кодировке UCS2 70-ю символами, на латинице — 160-ю символами.

Кодирование отправки SMS сообщений на PHP

<?

//Массив с параметрами GSM шлюза
$init=array('ip'=>"10.100.0.2",'port'=>'4001','pin'=>'7256'); 
// Отправка СМС :)
send_sms("Это тест","9101234567");
//Функция отсылки СМС
//$mess - сообщение
//$mob - номер мобильного в формате ХХХУУУУУУУ
//$debug - 1 - выводить отладочную инфу, 0 - нет
function send_sms($mess,$mob,$debug=0){
    global $init; 
    //Если сообщение и номер мобильника не пустые.
    //Проверку правильности формата мобильника возлагается на вас ;-) 
    if ($mess && $mob){
           $len=strlen($mess);
           $mob="7".$mob;
           $ooo="";
           if (preg_match('/^([x0Ax0Dx20-x7F]+)$/',$mess,$tmp)){
                if ($len<=160){
                      $len=0;
                      print "8-bit encoded SMS<br>";
                }else{
                      print "Длина сообщения в 8-битной кодировке [$len] больше 160 символов!<br>n";
                      return -1;
                }
           }else{
                if ($len<=70){
                     $mess=cp1251_2ucs2($mess);
                     $ret.="00";//it is only an indicator of the length of the SMSC information supplied (0)
                     $ret.="11"; //First octet of the SMS-SUBMIT message.
                     $ret.="00"; //TP-Message-Reference. The "00" value here lets the phone set the message reference number itself.
                     $ret.="0B"; // Address-Length. Length of phone number (11)
                     $ret.="91"; //Type-of-Address. (91 indicates international format of the phone number).
                     // Начало кодирования номера мобильного
                     if ((strlen($mob)/2)%2){
                           $mob.="F";
                     }
                     for ($i=0;$i<strlen($mob);$i+=2){
                              $ret.=$mob[$i+1].$mob[$i];
                     }
                     // Закончили взрывать мозг :)
                     $ret.="00"; //TP-PID. Protocol identifier
                     $ret.="18"; //TP-DCS. Data coding scheme. 18 - don't save at history, 08 - save
                     $ret.="C1"; //TP-Validity-Period. C1 means 1 week
                     $ret.=sprintf("%02X",strlen($mess)/2); //TP-User-Data-Length. Length of message.
                     $ret.=$mess; //TP-User-Data $ret.=chr(26); //end of TP-User-Data
                     $len=sprintf ("%s",(strlen($ret)-3)/2);
                     $mess=$ret;
                     print "16-bit encoded SMS<br>";
                }else{
                     print "Длина ссобщения в 16-битной кодировке [$len] больше 70 символов!<br>n";
                     return -1;
                }
           }
           // Создаем сокет
           $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
           if (!$socket) {
                err($socket);
           }else{
                // Устанавливаем опцию таймаута в 5 секунд для сокета.
                // Проверка на удачную установку лежит на вас :)
                socket_set_option($socket,SOL_SOCKET,SO_SNDTIMEO,array("sec"=>5,"usec"=>0));
                // Устанавливаем соединение на сокет
                $result = @socket_connect($socket, $init['ip'], $init['port']);
                if (!$result) {
                     err($socket);
                } else {
                     // Инициализируем GSM шлюз
                     $out=sms_init($socket,$len,$debug);
                     if ($len){
                          // Сообщение на кириллице
                          $out.=raw($socket,"AT+CMGS=$len",">");
                     }else{
                          // Сообщение на латинице
                          $out.=raw($socket,sprintf("AT+CMGS="+%s"",$mob),">");
                          $mess.=chr(26);
                     }
                     $out.=raw($socket,$mess,"CMGS",$debug);
                     if ($debug){
                          printf ("<pre>[ %s ]</pre>",$out);
                     }
                }
                @socket_close($socket);
           }
     }
     return $out;
} 
// Функция начальной инициализации модема
// $socket - ресурс сокета
// $type - 0 - сообщение на латинице, 1 - на кириллице
// $debug - 1 - выводить отладочную инфу, 0 - нет
function sms_init($socket,$type,$debug=0){
    global $init;
    $out=raw($socket,"AT","OK",$debug);
    $out.=raw($socket,sprintf("AT+CPIN=%s",$init['pin']),'(ERROR)|(OK)',$debug);
    $out.=raw($socket,sprintf("AT+CMGF=%d",$type?0:1),"OK$",$debug);
    if ($debug){
         print "<pre><font color="green">$out</font></pre><br>";
    }
    return $out;
}

// Функция посылки комманды на GSM шлюз
// $socket - ресурс сокета
// $write - команда, передаваемая GSM шлюзу
// $delim - последовательность, означающая окончание чтения ответа с GSM шлюза
// $debug - 1 - выводить отладочную инфу, 0 - нет
function raw($socket,$write,$delim,$debug=0){
    $ooo='';
    $write.="\r\n";
    if (@socket_write($socket,$write,strlen($write))===false){
        err($socket);
    }else{
        while ($out = @socket_read($socket, 2, PHP_NORMAL_READ)) {
           if ($out===false){
               err($socket);
           }else{
               if ($debug){
                   print $out;
                   flush();
               }
               $ooo=sprintf("%s%s",$ooo,$out);
           }
           if (preg_match("/".$delim."/i",$ooo,$tmp)){
               if ($debug){
                   print_r($tmp);
                   flush();
               }
               break;
           }
        }
     }
     usleep(100000);
     return $ooo;
} 
// Функция вывода ошибки при работе с сокетом
// $soc - ресурс сокета
function err($soc){
    printf ("<script>alert ('Ошибка: %s');</script><font color=red><b>%s: [%s]</b></font><br>",
    socket_strerror(socket_last_error($soc)),"Ошибка",socket_strerror(socket_last_error($soc)));
} 
// Функция кодирования кириллицы из CP1251 в UCS2
// $str - строка для перекодирования
function cp1251_2ucs2($str){
    for ($i=0;$i<strlen($str);$i++){
        if (ord($str[$i]) < 127){
            $results = sprintf("%04X",ord($str[$i]));
        }elseif (ord($str[$i])==184){ //ё
            $results="0451";
        }elseif (ord($str[$i])==168){ //Ё
            $results="0401";
        }else{
            $results = sprintf("%04X",(ord($str[$i])-192+1040));
        }
        $ucs2 .= $results;
    }
    return $ucs2;
}
// Функция вывода отладочной инфы
function debug($array){
    ob_start("get_vars");
    print_r($array);
    ob_end_flush();
} 
// Функция output_callback для функции debug()
function get_vars($buffer){
    return sprintf ("<div style="padding: 3px; border: 2px #88bbbb solid; color: #00dd00; background-color: #bbffbb; font-weight: bold; text-align: left;"><pre>%s</pre></div>",
    htmlspecialchars($buffer));
}

?>

Декодирование принятых SMS сообщений на PHP

 Отлично, с отсылкой СМС разобрались, осталось чтение сообщений (как обычно, с 8-битной кодировкой проблем нет, есть только с нашей кириллицей).

Команды для чтения SMS с GSM модемов:

at+cmgf=1 — выводить сообщения в неупакованном (читабельном) виде
at+cmgl=»all» — вывести сообщения
at+cmgr=n — прочитать отдельное сообщение
at+cmgd=n — удалить сообщение с SIM-карты
Сам прием СМС сообщений с GSM шлюза не сильно отличается от передачи (на основе вышеприведенной отправки предлагается вам самостоятельно реализовать это. Опишу только функцию для преобразования сообщения с «тарабарского» на русский:
<?
function ucs2_2cp1251($str){
    for ($i=0;$i<strlen($str);$i+=4){
        $char=hexdec($str[$i].$str[$i+1].$str[$i+2].$str[$i+3]);
        if ($char>126){
            if ($char==1105){
                $char=184; //ё
            }elseif($char==1025){
                $char=168; //Ё
            }elseif ($char>=848){
                $char-=848;
            }
        }elseif(!$char){
            $char=32;
        }
        $ret.=chr($char);
    }
    return $ret;
}
?>
Источники:
1. О виджетах и гаджетах
2. SMS and PDU format
3. IXBT.ru
4. Связывание мобильника и FreeBSD через bluetooth (c п.1 по п.6 включительно)

Ссылки:
З.Ы. Этот материал можно использовать и при отсылке СМС через обычный мобильник. Разница в том,
что вместо сетевого сокета нужно использовать устройство FreeBSD /dev/ttyU0 (при подключении через USB
кабель) либо /dev/ttypf (выполнив перед этим команду rfcomm_sppd -a MAC_адрес_телефона -t /dev/ttypf &).
О связывании мобильника и FreeBSD через bluetooth смотри выше "Источники".

З.З.Ы. При копировании статьи ссылка на источник ОБЯЗАТЕЛЬНА !
Автор: Панфилов Алексей (lehis (at) subnets.ru)

Похожие статьи:

    Не найдено

Прочитано: 77 779 раз(а)
Ничего не понялТак себе...Не плохоДовольно интересноОтлично ! То что нужно ! (голосов: 10, среднее: 4,60 из 5)
Загрузка...
Отправить на почту Отправить на почту

комментариев 15

  1. plastilin сказал:

    А по поводу вот такого устройства что можно сказать

    http://gprs-modem.ru/TELEOFIS_RX101_USB_GPRS.htm

    Нужно сделать веб страничку через которую пользователи из корп. сети смогут отправлять смс и получать результат о доставке с возможностью выбора кириллицы, англ. яз. или транслита. Сталкивался?

  2. lehisnoe сказал:

    Сказать можно следующее: Если фря его нормально видит, скажем, как /dev/ttyU0, и на команду ATI девайс что-то отвечает, то это значит, что все хорошо 🙂
    Да, мы делали как отправку СМС, так и их прием (скрипт сам определяет, преобразовывать ли мессагу в/из UCS2). Единственное, чем не заморачивались — это «составными» СМС (у нас «составная» принятая СМС выглядит как две независимых СМС, но глазами «склеиваются» нормально :), на отправку просто не даем возможности юзеру ввести длину мессаги больше, чем на одну СМС).

  3. stramilov сказал:

    как можно сделать так чтоб используя телефон gsm можем, можно было через веб интерфейс удаленно делать рассылку?

    если не сложно давай поговорим в скайпе. мой ник stramilov или аське 145800415

  4. lehisnoe сказал:

    Не вижу проблем: на основе статьи это можно сделать (и у нас есть такая схема в работе: GSM-модем подключен com-портом к серверу с FreeBSD, с которого шлются SMS либо введенные через вэб интерфейс (аналогичный сотовым операторам) либо извещающие о произошедших событиях в сети).
    Если вопросы остались, то добро пожаловать на наш форум

    P.S. По аське вопрос, конечно, решить можно, но нужно позаботиться и о других людях, ищущих ответ на этот вопрос: если все будут только «читателями», то какие результаты будет выдавать гугл?

  5. phil_qwerty сказал:

    очень хорошая статья!!!
    у меня вопрос: у меня ОС Linux Ubuntu, GSM модем Siemens MC35i терминальный с com портом. когда я отправляю смс с помощью AT команд через утилиту minicom, то все проходит на УРА. Но вот наладить работу скрипта для отправки смс никак не получается. Сначала пробовал написать сам на php, в принципе удачно, но все время создается ощущуние, что AT команда зацикливается в порту тем самым мешая модему корректно отработать задачу. Брал другие готовые скрипты написанные на питоне и php, история та же самая. модем не может отработать команды, или отрабатывает и присылает в смс «лишнюю» информацию, которая повисла в порту.
    Вопрос: в чем может быть проблема? не требуется ли какая нибудь настройка ком порта или модема?
    буду благодарен отозвавшимся помочь

  6. phil_qwerty сказал:

    требуется ли какая нибудь дополнительная настройка ком порта или модема? не то у меня некоторые проблемы при отправке смс с помощью скрипта

  7. admin сказал:

    нет, дополнительных настроек не требуется
    ты пробовал отправлять СМСки через AT команды вводя их руками в консоли ?

  8. phil_qwerty сказал:

    да, пробовал!!! замечательно отправляются без всяких проблем.
    но вот когда дело доходит до скрипта, то тут начинаются танцы с бубнами. Не знаю в чем проблема. Модем даже простую команду ATZ успевает отработать 1,5 — 2,5 раза. это примерно выглядит так:
    ATZ
    A
    OK
    TZATZ
    OK

  9. freeneutron сказал:

    Познавательная статья, но вот что мне так и не удалось понять. Если я хочу наладить прием прием SMS сразу на 100 разных номеров, то мне придется использовать 100 GSM шлюзов или можно найти более экономичное решение?

  10. admin сказал:

    1 GSM-шлюз = 1 сим карта, 100 номеров = 100 сим-карт

    Не совсем понятно зачем тебе 100 номеров, но если нужно, то протокол SMPP для тебя.
    Заключаешь с GSM оператором договор, берешь 100 номеров, настраиваешь у себя kannel (он поддерживает протокол SMPP) и вперед.

  11. pwn сказал:

    Мне пришлось решать аналогичную задачу, только средствами perl-а а не php и кроме того нужно было отправлять фрагментированные СМС. Труды автора сильно сэкономили мне время жизни, так что считаю своим долгом поделится своими наработками. Так как у меня отправка в основном смешанного текста (кирилица+латынь) то с текстовым режимом решено было не морочиться, что в разы упростило модуль. Итак кодировка СМС оформлена в виде пелового модуля SMSCode.pm, так как у меня все отправляемые сообщения в koi8r кодировке, то присуствует перекодирование текста в cp1251 (кому не надо — достаточно убрать пару строк)

    SMSCode.pm

    #
    package SMSCode;
    require Exporter;
    #
    our @ISA = qw(Exporter);
    our @EXPORT = qw(smscode ucs2encode);
    our $VERSION = 1.00;
    #
    use strict; # отменить халяву c переменнымми
    use warnings; # включить вывод предупреждений
    use Text::Iconv; # Функции перекодировки текста
    #
    #######################################################################
    # Кодировка СМС сообщения в UDH формате #
    # #
    # $Phone — телефон, на который отправить СМС #
    # $Txt — текст в кодировке koi8r #
    # return -> массив кодированных строк, одна строка на фрагмент СМС #
    #######################################################################
    sub smscode($$) {
    #
    # —- Объявляем переменные
    my ($t,$NumPart,$IdGroup,$Head,$Part,$NumSMS,$Udh);
    my (@Parts,@UDH);
    #
    # —- Разбор параметров
    my ($Phone,$Txt) = @_ ;
    #
    # —- Кодируем заголовок СМС
    $Head =»00″; # Номер СМС центра
    $Head.= length($Txt)>70 ? «51» : «11»; # Первый октет SMS-SUBMIT message. Для рагментированных СМС надо устанавливать бит TP-UDHI (наличие заголовка)
    # и поэтому для фрагментированных — 51h дла простых — 11h
    $Head.=»00″; # количество успешно переданых (0..FFh) сообщений с телефона
    $Head.=»0B»; # Длина номера телефона (11 цифр)
    $Head.=»91″; # Тип номера телефона. 91h — международный формат
    #
    # —- Рихтуем переданный номер
    $Phone = «7».$Phone.»F»;
    #
    # —- Если в итоге получилось не 12 символов — лесом
    unless (length($Phone)==12) {return ();}
    #
    # —- Цикл кодирования номера телефона
    foreach $t ($Phone=~m/(.{2})/g) {
    #
    # —- Дописываем очередную пару символов номера меняя местами первую и вторую цифры
    $Head.=substr($t,1,1).substr($t,0,1);
    }
    #
    # —- Дописываем хвост заголовка
    $Head.=»00″; # Идентификатор протокола. 00h -> SMS
    $Head.=»08″; # Кодировка. 08h — юникод+сохранять СМС в истории телефона 18 — тоже но не сохранять историю
    $Head.=»C1″; # Период доставки СМС. C1 -> 1 неделя, полсе чего если юзер свой телефон не включит, то эту СМС он уже
    # не увидит никогда
    #
    # —- Создаем объект для перекодировки кои8 в ср1251
    my $KOI2UTF = Text::Iconv->new(«koi8-r», «cp1251»);
    #
    # —- Перекодируем текст в ср1251
    $Txt = $KOI2UTF->convert($Txt);
    #
    # —- Если длинна сообщения больше чем 70 символов
    if (length($Txt)>70) {
    #
    # —- Разрезаем сообщение на фрагменты не более 67 символов
    @Parts = $Txt=~m/(.{1,67})/g;
    #
    # —- Извлекаем число частей
    $NumPart = scalar(@Parts);
    #
    # —- Генерим уникальный номер группы для отправляемых СМС
    $IdGroup = int(rand(255));
    #
    # —- Цикл перебора фрагментов и формирования фрагментированной СМС
    $NumSMS = 1;
    foreach $Part (@Parts) {
    #
    # —- Формируем заголовок фрагментированной СМС
    $Udh = «050003»; # Признак фрагментированной СМС
    $Udh.= sprintf(«%02X»,$IdGroup); # Уникальный для данной группы СМС номер
    $Udh.= sprintf(«%02X»,$NumPart); # Количество СМС для склейки
    $Udh.= sprintf(«%02X»,$NumSMS); # порядковый номер СМС
    #
    # —— Дописываем закодированный текст фрагмента
    $Udh.= ucs2encode($Part);
    #
    # —- Рассчитываем длину строки и дописываем ее в начало
    $Udh = sprintf(«%02X»,length($Udh)/2).$Udh;
    #
    # —- Добавляем очередной элемент к возвращаемому массиву
    $UDH[$NumSMS-1] = $Head.$Udh;
    #
    # —- Наращиваем порядковый номер СМС в группе
    $NumSMS++;
    }
    }
    else {
    #
    # —- Кодируем текст ссобщения
    $Txt = ucs2encode($Txt);
    #
    # —- Рассчитываем длину строки и дописываем ее в начало
    $Txt = sprintf(«%02X»,length($Txt)/2).$Txt;
    #
    # —- Добавляем единственный элемент к возвращаемому массиву
    $UDH[0] = $Head.$Txt;
    }
    #
    # —- Возвращаем массив закодированных СМС
    return @UDH;
    }
    #
    # End of sub smscode
    #
    #
    #
    # —- Кодирование строки ср1251 в UCS2
    sub ucs2encode ($) {
    #
    # —- Объявляем переменные
    my ($UCS,$Chr);
    #
    # —- Разбор параметров
    my ($Str) = @_ ;
    #
    # —- Цикл кодирования символов
    $UCS = »;
    foreach $Chr (split(//,$Str)) {
    #
    # —- Если код символа меньше 127
    if (ord($Chr) < 127) {
    #
    # —- Английские символы кодируем как есть
    $UCS.= sprintf("%04X",ord($Chr));
    }
    #
    # —- С буковкой ё морока отдельная
    elsif (ord($Chr)==184) { #ё
    $UCS.="0451";
    }
    elsif (ord($Chr)==168) { #Ё
    $UCS.="0401";
    }
    #
    # —- Остальные русские буковки кодируются линейно
    else {
    $UCS.= sprintf("%04X",ord($Chr)-192+1040);
    }
    }
    #
    # —- Возвращаем закодированную строку
    return $UCS;
    }
    #
    # End of sub ucs2encode
    # End of module SMSCode.pm
    1;

  12. pwn сказал:

    Пример использования. (Кодирует заданное сообщение и выводит массив строк которые достаточно скормить модему через буфер обмена чтобы СМС было отправлено.)

    test.pl

    #
    # —- Подключаем модули
    use FindBin; # для того, чтобы модули используемые этим скриптом были доступны
    use lib $FindBin::Bin; # независимо от того, откуда запущен этот скрипт
    use SMSCode; # функции кодирования СМС
    #
    # —- Тест отправки СМС
    $Txt = «текст который отправляем»;
    $Tel = ‘1111111111’; # телефон (без +7 и 8 в начале)
    #
    # —- Кодируем СМС
    @SMS = smscode($Tel,$Txt);
    #
    # —- Отображаем фрагменты как есть, готовые чтоб их слить в GSM модуль
    foreach $SMS (@SMS) {
    $l = length($SMS)/2-1;
    print «AT+CMGS=$l\n»;
    print «$SMS\n»;
    }

  13. lehisnoe сказал:

    2pwn: благодарю за обмен опытом!

  14. pwn сказал:

    не за что 🙂
    файлики скриптов только надо как-то по другому залить, а то и кавычки заколбасились и отступы съехали. Тому кто их попробует передрать потом надо будет после еще html чудачества руками рихтовать.

  15. lehisnoe сказал:

    Ждем архив с файлами на мыло root[гав-гав]subnets.ru, после чего выложим их.

Добавить комментарий

Вам следует авторизоваться для размещения комментария.