Cross-platform SMS sending in PDU mode using Lazarus

SMS remains a tool that is used by millions of people everyday since 1993,recently I was asked to develop a cross platform application that will send sms to people on their bills due date. I had an old 3G USB stick lying around just like the one in the picture below :

3g modem

3g modem

I thought I might use it to send sms.I have played before with sms using a set of Delphi components ( NrComm Lib ) but Delphi as you know is not cross platform , neither the components are free. Buying components is just not my style … I live in Romania if you lived here it probably wasn’t your style too. So I decided to write my own sms unit to achieve this purpose.I found some examples on sending sms using Lazarus and I have decided to test if the device works properly. I had the surprise of the device giving me an error every time I tried to send sms. But researching I saw that are two ways that the modem accepts sms messages ( text mode and pdu mode ) . Tried to send sms using pdu mode using the components I mentioned before and worked flawless, then I took the decision to write my unit to send sms in pdu mode.

The result is down bellow , use it how you wish if you got time improve it to receive sms,send mass sms etc.It uses the SynaSer library for interacting with the COM port :D

 Delphi |  copy code |? 
01
unit usms_sender;
02
 
03
{$mode objfpc}{$H+}
04
 
05
interface
06
 
07
uses
08
  Classes, SysUtils,SynaSer;
09
 
10
type
11
  TByteArray = array of Byte;
12
 
13
  ESMSException = class (Exception);
14
 
15
  TSMS = class (TObject)
16
  private
17
  sms : String;
18
  phone : String;
19
  pdu_string : String;
20
  phone_len : Integer;
21
  serial : TBlockSerial;
22
  connected : Boolean;
23
 
24
  function  IsNumeric(const S : String) : Boolean;
25
  procedure SetPhone(pho : String);
26
  procedure EncodeToGsmAlphabet(const ascii : String;out tmp : TByteArray);
27
  procedure Digest;
28
  function  PackPhoneNumber : String;
29
  function  AsciiToPdu(const ascii : String) : String;
30
  procedure SetSms(S : String);
31
 
32
 
33
  public
34
  constructor Create(const smsx : String; phonex : String);overload;
35
  constructor Create();
36
  destructor Destroy;override;
37
 
38
  procedure Send;
39
 
40
  procedure ConnectModem(const port : String; baud : Integer = 115200);
41
  procedure DisconnectModem;
42
 
43
  published
44
  property txtSMS : String read sms write SetSms;
45
  property txtPhone : String read phone write SetPhone;
46
  end;
47
 
48
implementation
49
 
50
procedure TSMS.Digest;
51
var
52
  tmp : String;
53
begin
54
    tmp := AsciiToPdu(sms);
55
    pdu_string:='001100' + IntToHex(phone_len,2) +'91' + PackPhoneNumber + '0000AA' + IntToHex((Length(sms)),2) + tmp ;
56
end;
57
 
58
destructor TSMS.Destroy;
59
begin
60
    DisconnectModem;
61
    inherited;
62
end;
63
 
64
procedure TSMS.DisconnectModem;
65
begin
66
    if connected then
67
    begin
68
      if Assigned(serial) then
69
      begin
70
      serial.CloseSocket;
71
      serial.Free;
72
      end;
73
      connected := false;
74
    end;
75
end;
76
 
77
procedure TSMS.ConnectModem(const port : String; baud : Integer = 115200);
78
begin
79
    serial := TBlockSerial.Create;
80
    serial.Config(baud,8,'N',1,false,true);
81
    serial.Connect(port);
82
    serial.ATCommand('AT');

 Delphi |  copy code |? 
1
     Sleep(100); // I am not a fan of hardcoded sleep but it takes a while until the modem responds

 Delphi |  copy code |? 
001
   <!--DVFMTSC-->connected := serial.ATResult;
002
    if not connected then
003
    begin
004
    serial.Free;
005
    raise ESMSException.Create('Modem is not responding!');
006
    end;
007
end;
008
 
009
procedure TSMS.SetSms(S : String);
010
begin
011
    if Length(S) > 160 then raise ESMSException.Create('Message is to large!');
012
    sms := S;
013
end;
014
 
015
function PackBytes(a,b : Byte) : Byte;
016
begin
017
    Result := (b shl 4) or a;
018
end;
019
 
020
procedure TSms.Send;
021
begin
022
    if connected then
023
    begin
024
    Digest;
025
    serial.ATCommand('AT+CMGF=0');
026
    serial.ATCommand('AT+CMGS=' + IntToStr(Length(pdu_string) div 2 - 1));
027
    serial.SendString(pdu_string + #$1A);
028
    end
029
    else raise ESMSException.Create('No modem connected!');
030
end;
031
 
032
function TSms.PackPhoneNumber() : String;
033
var
034
  i : Integer;
035
begin
036
  Result := '';
037
  for i := 1 to (Length(phone) div 2)  do
038
      Result := Result + IntToHex(PackBytes(StrToInt('$'  + phone[(i * 2) - 1]),StrToInt('$' + phone[(i * 2)])),2);
039
end;
040
 
041
procedure TSMS.EncodeToGsmAlphabet(const ascii : String;out tmp : TByteArray);
042
var
043
  i : Integer;
044
  j : Byte;
045
const
046
  gsm7Bit: array [0 .. 127] of Byte = (64,163,36,165,232,223,249,236,242,199,10,216,248,13,197,229,0,95,0,0,0,0,0,0,0,0,0,0,198,230,223,201,32,33,34,35,164,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,161,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,196,204,209,220,167,191,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,228,246,241,252,224);
047
begin
048
  SetLength(tmp,0);
049
  for i := 1 to Length(ascii) do
050
  begin
051
      for j := 0 to 127 do
052
          if (gsm7Bit[j] = Ord(ascii[i])) then
053
          begin
054
            SetLength(tmp,Length(tmp) + 1);
055
            tmp[High(tmp)] := j;
056
            break;
057
          end;
058
  end;
059
end;
060
 
061
function ByteToBin(Value: Byte): string;
062
var
063
  i: Integer;
064
begin
065
  Result := '';
066
  for i := 6 downto 0 do
067
    if Value and (1 shl i) <> 0 then
068
      Result := Result + '1'
069
  else
070
    Result := Result + '0';
071
end;
072
 
073
function BinToInt(Value: string): Byte;
074
var
075
  i : Integer;
076
begin
077
  Result := 0;
078
  for i := 8 downto 1 do
079
    if Value[i] = '1' then Result := Result + (1 shl (8 - i));
080
end;
081
 
082
function TSMS.AsciiToPdu(const ascii : String) : String;
083
var
084
  bts : TByteArray;
085
  bin,tmp: string;
086
  total : TStringList;
087
  i,len : Integer;
088
begin
089
  Result := '';
090
  EncodeToGsmAlphabet(ascii,bts);
091
  total := TStringList.Create;
092
  for i:=Low(bts) to High(bts)do
093
  begin
094
    bin := ByteToBin(bts[i]);
095
    total.Add(bin);
096
  end;
097
  for i:=0 to total.Count -2  do
098
  begin
099
      len := 8 - Length(total[i]);
100
      if len = 8 then
101
      begin
102
          total[i] := '11111111';
103
          Continue;
104
      end;
105
      if len >0 then
106
      begin
107
      tmp := total[i+1];
108
      bin := Copy(tmp,Length(total[i + 1]) - len+1,len);
109
      tmp := total[i];
110
      Insert(bin,tmp,1);
111
      total[i] := tmp;
112
      bin := Copy(total[i+1],1,7 - len);
113
      total[i+1] := bin;
114
      end;
115
  end;
116
  for i := 0 to total.Count - 1 do
117
  begin
118
    tmp := total[i];
119
    if tmp = '' then Continue
120
    else if Length(tmp) < 8 then
121
      tmp := IntToHex(0,8-Length(tmp)) + tmp
122
    else if (BinToInt(tmp) > 254) then Continue;
123
    Result := Result + IntToHex(BinToInt(tmp),2);
124
  end;
125
  total.Free;
126
end;
127
 
128
procedure TSMS.SetPhone(pho : String);
129
begin
130
    if not (IsNumeric(pho)) then raise ESMSException.Create('Invalid phone number. Must be in international format eg 40741966242');
131
    phone_len:= Length(pho);
132
    if odd(Length(pho)) then
133
    phone := pho + 'F'
134
    else
135
    phone := pho;
136
end;
137
 
138
function TSMS.IsNumeric(const S: String) : Boolean;
139
var
140
  c : Char;
141
begin
142
    Result := True;
143
    for c in S do
144
        if not (c in ['0'..'9']) then
145
        begin
146
          Result := False;
147
          break;
148
        end;
149
end;
150
 
151
constructor TSMS.Create(const smsx : String; phonex : String);
152
begin
153
    inherited Create;
154
 
155
    SetPhone(phonex);
156
    SetSms(smsx);
157
end;
158
 
159
constructor TSMS.Create;
160
begin
161
    inherited Create;
162
end;
163
 
164
end.
165

2 comments

  1. lucky says:

    Fantastic goods from you, man. I have have in mind your stuff prior to and you are simply extremely great. I actually like what you’ve received here, certainly like what you are stating and the way in which through which you assert it. You are making it enjoyable and you still take care of to keep it wise. I cant wait to learn far more from you. That is actually a tremendous site.

  2. Chery says:

    Really liked what you had to say in your post, Sms Sending with Lazarus!

Leave a Reply

Your email address will not be published. Required fields are marked *