Pull to refresh

Отправка SMS с 3G/GSM модема

Reading time 7 min
Views 211K
Привет Хабр. В данной статье я бы хотел поделиться опытом работы с 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, модем принимает, наша программа считывает, выполняет какие то действия исходя из текста сообщения и отправляет пользователю результата. Или наоборот: на ПК происходит какое то событие, и программа отправляет пользователю сообщение об этом событии. Удачи вам в ваших экспериментах, спасибо!

Исходники тестовой проги тут.
Tags:
Hubs:
+36
Comments 20
Comments Comments 20

Articles