Пользователь
0,0
рейтинг
21 ноября 2011 в 18:01

Разработка → Отправка SMS с 3G/GSM модема из песочницы

Привет Хабр. В данной статье я бы хотел поделиться опытом работы с GSM модемом, а точнее опытом отправки SMS сообщений. Ниже будет описана реализация программы на Delphi для отправки SMS сообщений, а так же чтение и удаление входящих/исходящих сообщений с модема. В моём случае это был модем HUAWEI от MTS. Всех кого заинтересовал, прошу под кат.

Рождение идеи


Всё началось с того, что в одной небольшой сети магазинов возникла необходимость ежедневно информировать начальство о выручке по каждому магазину за день. В центральном офисе скапливается информация о продажах, её то и нужно донести до начальника. Ген.дир. (заказчик) — человек очень занятой, и как директору ему необходимо своевременно получать данные о выручке магазинов, интернет порой недоступен, а мобильник всегда под рукой, вот и было решено отправлять ему на телефон SMS с данными о выручке по каждому магазину, в простом и читабельном виде.

Пример SMS:

08.11.2011
1.Магазин A, 123045 р.
2.Магазин B, 134520 р.
3.Магазин C, 215403 р.
...
;
Как выяснилось в последующем это ещё не всё что требуется, но об этом чуть позже.

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

С чего начать


С COM портом конечно работать приходилось, а вот общаться с модемом по средствам AT команд, до этого как то не доводилось. AT команд конечно довольно много, но всё оказалось гораздо проще чем я ожидал, т.к. для нашей цели потребовались всего 5 команд:

AT+CMGF — задаёт режим работы: 0-цифровой или 1-текстовый. Эта команда будет вызываться первой, от этого зависит формат последующих команд и ответов модема.

AT+CMGS — отправка сообщения, формат параметра сильно зависти от режима (т.е. от прошлой команды).

AT+CMGL — чтение сообщений с модема, в качестве параметра можно передать одно из пяти значений, стоит обратить внимание что в зависимости от режима (AT+CMGF) следует передавать цифровые или строковые значения:
image

AT+CMGD — удаление одного сообщения с модема, в качестве параметра передаём номер сообщения.

AT+CMGR — чтение одного сообщения с модема, так же передаём номер сообщения.

Первые результаты


После нескольких часов (проб/ошибок) выяснил, что отправить SMS сообщение с модема ненамного сложнее чем сделать это с обычного мобильного телефона. Как уже упоминалось выше, для отправки SMS следует использовать команду «AT+CMGS». И так, открыл hyperterm, подключился к модему (через COM порт), и настрочил в порт следующие команды:
AT+CMGF=1 [Enter]
AT+CMGS=+79261234567 [Enter]
hello habr, this is test message [Ctrl+Z]

Пример того же самого на Delphi:

procedure SendSMS(AComPort: integer; AMsg: String; ANumTel: String); 
var
  hFile: THandle;

  procedure WriteStr(AStr: String); //пишет в порт переданную строку
  var
    LWrited: Cardinal;
  begin
    //Пишем в порт
    WriteFile(hFile, PAnsiChar(AStr)^, Length(AStr), LWrited, nil);
  end;

begin
  //открываем порт
  hFile := Windows.CreateFile(PChar('\\.\COM' + IntToStr(AComPort)), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0); 

  //если открылся
  if (hFile <> INVALID_HANDLE_VALUE) then
  begin
    try
      //устанавливаем текстовый режим
      WriteStr('AT+CMGF=1' + #$D#$A);
      //вводим номер в формате "+79xxxxxxxxx"
      WriteStr('AT+CMGS="'+ANumTel+'"' + #$D#$A);
      //вводим текст сообщения, только латиница
      WriteStr(AMsg + #$D#$A#$1A);
    finally
      //закрываем порт
      Windows.CloseHandle(hFile);
    end;
  end;
end;

Вуаля, и карманный девайс сообщил о ожидаемом событии.
Но увы положительные эмоции возникшие у меня в момент этого успеха продлились недолго, а если точнее до того момента когда было обнаружено что с русским текстом всё гораздо сложнее. Во первых для отправки сообщений на русском нужно переключить режим с текстового на цифровой (AT+CMGF=0), а во вторых само сообщение должно быть отправлено в кодировке UCS2. И если с первым проблем минимум, то со вторым пришлось повозиться.

Кодировка текста в UCS и обратно (опять таки на Delphi):

function UCSToAnsi(AStr: AnsiString): AnsiString;

  function Convert(ACnvStr: AnsiString): AnsiChar;
  var
    j: integer;
  begin
    j := StrToIntDef('$'+ACnvStr, 0);
    case j of
      1040..1103: j := j - 848;
      1105: j := 184;
    end;
    Result := Chr(j);
  end;

var
  c, i: integer;
begin
  Result := '';
  c := Length(AStr) div 4;
  for i := 0 to c - 1 do
    Result := Result + Convert(Copy(AStr, i*4+1, 4)); end;

function AnsiToUCS(AStr: AnsiString): AnsiString;

  function Convert(AChar: AnsiChar): AnsiString;
  var
    j: integer;
  begin
    Result := '';
    j := ord(AChar);
    case j of
      192..255: j := j + 848;
      184: j := 1105;
    end;
    Result := IntToHex(j, 4)
  end;

var
  c, i: integer;
begin
  Result := '';
  c := Length(AStr);
  for i := 1 to c do
    Result := Result + Convert(AStr[i]); 
end;

Не скажу что всё получилось сразу и легко, но всё же получилось. Если раньше я отправлял в модем:
AT+CMGF=1 [Enter]
AT+CMGS=+79261234567 [Enter]
hello habr, this is test message [Ctrl+Z]

то для того чтобы отправить сообщение на русском нужно будет отправить:
AT+CMGF=0 [Enter]
AT+CMGS=84 [Enter]
0011000B919762214365F70008C146043F04400438043204350442002004450430043
10440002C0020044D0442043E00200442043504410442043E0432043E043500200441
043E043E043104490435043D04380435 [Ctrl+Z]

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

Пример на Delphi:

procedure SendSMSMessage(AComPort: integer; AMsg: String; ANumTel: String); 
var
  Lng, i:  Integer;
  LRead, LText, LMes, LTel, ANum: String;
  hFile: THandle;
  
  procedure WriteStr(AStr: String);
  var
    LWrited: Cardinal;
  begin
    //Пишем в порт
    WriteFile(hFile, PAnsiChar(AStr)^, Length(AStr), LWrited, nil);
  end;
  
begin
  //открываем порт
  hFile := Windows.CreateFile(PChar('\\.\COM' + IntToStr(AComPort)), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0); 

  //если открылся
  if (hFile <> INVALID_HANDLE_VALUE) then
  begin
    ANum := ANumTel;
    if (Length(ANum) mod 2) = 1 then
      ANum := ANum + 'F';

    for i := 1 to Length(ANum) do
      if i mod 2 = 0 then
        LTel := LTel + ANum[i] + ANum[i-1];

    LText := AnsiToUCS(AMsg);
    // Длина и номер SMS центра. 0 - означает, что будет использоваться дефолтный номер.
    LMes := '00'; 
    // SMS-SUBMIT
    LMes := LMes + '11'; 
    // Длина и номер отправителя. 0 - означает что будет использоваться дефолтный номер.
    LMes := LMes + '00'; 
    // Длина номера получателя
    LMes := LMes + IntToHex(Length(ANumTel), 2); 
    // Тип-адреса. (91 указывает международный формат телефонного номера, 81 - местный формат).
    LMes := LMes + '91'; 
    // Телефонный номер получателя в международном формате.
    LMes := LMes + LTel; 
    // Идентификатор протокола
    LMes := LMes + '00'; 
    // Старший полубайт означает сохранять SMS у получателя или нет (Flash SMS),  Младший полубайт - кодировка(0-латиница 8-кирилица).
    LMes := LMes + '08'; 
    // Срок доставки сообщения. С1 - неделя
    LMes := LMes + 'C1'; 
    // Длина текста сообщения.
    LMes := LMes + IntToHex(Trunc(Length(LText)/2),2); 
    LMes := LMes + LText; 
    Lng := Round((Length(LMes)-2)/2);
    WriteStr('AT+CMGF=0' + #$D#$A);
    WriteStr('AT+CMGS=' + StrToInt(Lng) + #$D#$A);
    WriteStr(LMes + #$D#$A#$1A);

    Windows.CloseHandle(hFile);
  end;
end;

Развитие идеи


Сказать что я был счастлив когда на мобилу пришёл долгожданные русский текст, вместо «кракозябр», значит не сказать ничего. На следующий день дописал основную часть программы, и вроде бы всё. Сообщения с данными о выручке отправляются на телефон заказчику, вроде бы и жизнь то удалась, но не тут то было. Где то через неделю заказчик попросил доработать приложение, а именно сделать так чтобы после того как ему пришло SMS с текстом:
08.11.2011
1.Магазин A, 123045 р.
2.Магазин B, 134520 р.
3.Магазин C, 215403 р.
...

он бы мог в ответ на это сообщении отправить номер магазина и пришло бы новое сообщение, но уже с более детальной информацией по указанному магазину.
Ну в общем то логика простая: программа должна хранить последней отправленный «отчёт» по всем магазинам и читать входящие сообщения, как только появляется сообщение с условным текстом (например «магазин=12»), вытаскивать от туда номер магазина, смотреть в последнем отправленном сообщении что было под этим номером и отправлять детальную информацию по этому магазину (к сожалению на данный момент заказчик так и не определился с форматом и содержанием «подробного отчёта», так что в качестве примера привести нечего). Для реализации вышеупомянутой логики от модема требуется: прочитать SMS из памяти, удалить SMS из памяти (чтобы не скапливались). Для чтения сообщений использовал команды AT+CMGR и AT+CMGL (их краткое описание приводилось ранее). Чтение всех сообщений будет выглядеть как:
AT+CMGF=1 [Enter]
AT+CMGL="ALL" [Enter]
+CMGL: 6,"REC READ","778467",,"11/09/03,18:49:40+16"
007700770077002E006D00740073002E00720075
+CMGL: 7,"REC READ","+79261234567",,"11/10/18,18:38:00+16"
04220435044104420020043F044004380435043C043000200073006D0073002004410
43E043E043104490435043D04380439002100200421043C04410020043D043D043D04
3004340430003F0021003F00210028002D005F002D00290020 [Enter]

Здесь всё проще чем было раньше. Каждое сообщение состоит из 2х строк, в первой содержатся данные о сообщении (такие как: от кого, когда, номер сообщения), а во второй сам текст сообщения (опять таки в кодировке UCS, функция UCSToAnsi приводилась выше). Чтение одного сообщение осуществляется как:
AT+CMGF=1 [Enter]
AT+CMGR=7 [Enter]
+CMGR: "REC READ","+79261234567",,"11/10/18,18:38:00+16"
04220435044104420020043F044004380435043C043000200073006D0073002004410
43E043E043104490435043D04380439002100200421043C04410020043D043D043D04
3004340430003F0021003F00210028002D005F002D00290020 [Enter]

Аналогичным образом происходит и удаление сообщений. Если например в моём случае отправить команду AT+CMGD=7, то при следующем AT+CMGL=«ALL» я уже не увижу сообщение номер 7, т.к. оно будет удалено.

Заключение


И так, были разобраны основные команды для работы с SMS сообщениями через GSM модем, была рассмотрена отправка, чтение, удаление сообщений. На последок хотелось бы отметить что область применения такого использования SMS сообщений довольно широка (особенно потому что можно организовать двухстороннюю связь). Например так: пользователь отправляет SMS, модем принимает, наша программа считывает, выполняет какие то действия исходя из текста сообщения и отправляет пользователю результата. Или наоборот: на ПК происходит какое то событие, и программа отправляет пользователю сообщение об этом событии. Удачи вам в ваших экспериментах, спасибо!

Исходники тестовой проги тут.
@dos999
карма
11,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (20)

  • 0
    Хорошая вещь. Сделать бы еще такое на PHP, к примеру. Можно было бы отправлять СМС сообщения зарегистрировавшимся пользователям, например, не прибегая к услугам операторов.
    • +1
      установите себе asterisk, подключите к нему 3g модем и будет вам счастье по отправке sms при помощи php скрипта, да и mysql легко подключить к нему.
      • +1
        Спасибо, на досуге попробую
        • +1
          Либо gnokii.
  • 0
    А вообще, по-моему, провайдерами (а-ля AlphaSMS/TurboSMS) отсылать дешевле.
    • +3
      Существуют «псевдо безлимитные» тарифы на SMS сообщения. например у MTS есть тариф 3р./сутки — до 100 sms. Выходит 0.30 р. за сообщение, но это конечно при условии что все 100 будут истрачены. А вообще решение о пользовании услугами того или иного провайдера/оператора сильно зависит от самой задачи (дешевизна, безопасность, надёжность).
      • –3
        Добавьте цену 3g модему, поделенную на кол-во смс за предполагаемый строк окупаемости.
        Получится, что у провайдера дешевле.
        • +1
          Возможно вы правы, но заказчик отказался от варианта с провайдерами из соображений безопасности.
          • 0
            Т.е. отсылать просто GSM-ом безопаснее? Юморист.
            Тогда уже надо было взять смартфон и на почту отсылать.
    • +1
      Как эмбеддер могу предположить, что автору статьи гораздо интереснее было разобраться именно со связкой GSM-модем + ПК (тем более, что смс-шлюзы были отвергнуты заказчиком в самом начале, и для такого количества смс стоимость не играет особой роли).
    • 0
      Билайн Go — 1000 sms в день всего за 5 рублей — выбор экономных спамеров.
  • +7
    Крылом орла мне в пятку, да это же Delphi!
  • +1
    Я вам может скажу очевидное, но после 300 смс на РАЗНЫЕ номера в течении месяца, провайдер задумается и вам позвонит. Можете попасть в чёрный список. :) Так, же он будет понимать что все сообщения отправлены с модема. Они кстати не против, но рекомендуют с ними согласовать. А если пользоваться их шлюзом, то можно договориться на цене 10 коп. Подключение выходит дешевле модема. Но точно не знаю, я просто делал авторизацию с подтверждением по СМС.
  • 0
    Дешевле это сколько? Ведь модемы сейчас налево и направо за ~200-400 руб раздают.
    • 0
      Извиняюсь, промахнулся, вопрос webmaks'у
  • 0
    Вот чел на Delphi, чтото подобное создал, мож кому будет полезно GSM-Modem Control
  • 0
    Небольшая ошибочка:

    Вместо этого:

    WriteStr('AT+CMGF=0' + #$D#$A);
    WriteStr('AT+CMGS=' + StrToInt(Lng) + #$D#$A);
    WriteStr(LMes + #$D#$A#$1A);

    Надо вот это:

    WriteStr('AT+CMGF=0' + #$D#$A);
    WriteStr('AT+CMGS=' + IntToStr(Lng) + #$D#$A);
    WriteStr(LMes + #$D#$A#$1A);

    Опечаточка видимо))

    И вопрос: зачем кодировка текста в UCS и обратно? Для этой задачи ведь достаточно только преобразования в UCS.
  • 0
    У самого похожая система, поэтому добавлю следующее
    1) Чтобы было дешевле — у ОПСОСов есть спец пакеты с безлимитными СМС по цене около 100р в месяц (на корпоративных тарифах дешевле)
    2) Если модемов используется несколько, то на каждом должен быть парсер входящих смс (даже если модем исключительно для отправки СМС). Один такой модем перестал СМСить — когда разобрались оказалось, что там масса СПАМ-СМС (вида «Распродажа в ХХХ» или «Вас ожидает такси»).
  • 0
    Спасибо, интересное решение! Как раз искал подобный пример с исходниками. А кто-нибудь знает как реализовать звонилку для 3G модема, чтоб можно было совершать голосовые вызовы, как это сделано в стандартной оболочке для модемов у МТС?
  • 0
    Столкнулся с проблемой чтения сообщений в текстовом режиме. Если СМС вам отправят латиницей, оно отобразится простым текстом. Получается, если пошлют кучу цифр, то вы будете парсить их как UCS-2 и получите кракозяблы.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.