Отправка и прием SMS через GSM шлюз

Предыстория

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

Путем нехитрых размышлений был найден единственный выход: отсылка 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)

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

http://subnets.ru/blog/?p=33;