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
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
| 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 |
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.
Really liked what you had to say in your post, Sms Sending with Lazarus!