📜 ⬆️ ⬇️

Easy PERL script for sending and receiving SMS via mobile phone

Preamble



In the process of self-development of “Smart Home” (hereinafter referred to as UD), periodically there was a need to write small, but very necessary utility scripts, in one form or another, already written and available in the Internet, but for some reason or other unsuitable for use in a project. “Why unsuitable?”, An experienced Developer asks, maybe I just “I don’t like cats because I don’t know how to cook them?” Maybe it is so, but, nevertheless, when the task is to turn around on something like D-Link DIR-320 , here it is “not up to fat, I would live.” Here, probably, it should be noted that earlier, PERL was chosen as the main development language of the DD, as the most suitable for “fast-and-dirty” development on the target platform. Why fast-and-dirty? It's very simple: no distribution of the product was planned, except that the installation on a couple of devices for friends and relatives. It is important that the “fast” component prevailed, as there were a couple of months left until winter, and the system was getting ready for installation in a heated summer house with accommodation only on weekends. This means that stopping the heating system in the absence of the owners could have the most disastrous consequences, such as defrosting the heating system.
Yes, I know that gnokii , CPAN SMS-Server-Tools and, indeed, a lot of things have been around for a long time. However, after a long search of several ready-made solutions, I had to start the invention of the bicycle all over again. Here I understand perfectly that it would be better to invest the time spent writing the script in the development of the basic logic, but nevertheless the fact remains: the considered ready-made solutions for one reason or another did not fit into the list of requirements. About this below.

Formulation of the problem


So, the task has been set to communicate with the chief manager of the UD process via SMS via a mobile phone directly connected to the host. It was decided that the “bicycle” should meet the following requirements:
- minimum of dependencies on external libraries;
- “fast-and-dirty” development (2 days with primary debugging, there was no more time);
- the ability to work in asynchronous with respect to the client (main process UD) mode. Delays and hangs in exchange with the phone should not block the client process. No
- lack of need for compilation, which simplifies the transfer to other platforms, primarily the type of "embedded-linux";
- minimum functionality: SMS acceptance + SMS sending;
- simple interface in the spirit of unix;
- the ability to work in both daemon mode and single-run start mode;
- Support SMS Cyrillic.
- the ability to work with the largest number of phone models, incl. old ones.
')

Decision


Actually, after the statement of the problem, the circle of searching for possible solutions narrowed considerably. It was decided that:
- it will be a Perl script, since perl already existed and worked tolerably well. Of course, the task is also realizable on the shell, but then “fast” was lost.
- The interface to the script is file, and only file. This gives absolute asynchrony, though in exchange for reliability.
- communication with the phone will be in UDP mode. This is the only way to implement the last two requirements from the previous list.
- the script will be as linear as possible, no OOP or even modularity, except perhaps easy procedurality for the sake of readability and reuse, if such is required. One word "fast-and-dirty".

What happened


Actually, about three hundred lines of the script and the use of the Device :: SerialPort module as an external CPAN library. The latter circumstance significantly affects tolerability, since This module requires compilation (most likely, cross-compilation, as it is not natively compiled), but it is available ready in most repositories. In general, the task is implemented, the script has worked for a couple of years without any problems. Used with the phone Siemens S65, with others not accounted for. The script requires two more data files: UCS.map and UCS.unmap, which are rather empty and voluminous for publication here, but I’m happy to share with those who want to receive them. For the hint how to put them on Habr being read-only I will be very grateful (not irony).

Briefly about the script: all the most important constants (paths, logging mode, etc.) are announced and initialized at the beginning, there is practically nothing to edit further. To send a message file you need to put it in the $ msgdir directory. The file name must comply with the convention $ outgoingfilemask. $ Date. $ MSISDN,
where $ outgoingfilemask is the mask value from the script header, $ date is the send date (bad option) or sequential number, is used to avoid overlapping file names during batch sending, $ MSISDN is the recipient's phone number in international format, but without any prefix type 810, 00, +, etc. In the file itself - the text of the message in transliteration. Why translit - it happened (“fast & dirty”). In fact, this can be easily repaired by refilling the UCS.map and UCS.unmap files under almost any encoding.
Reception works in a similar way, but with the file name mask $ incomingfilemask. Both masks can be redefined, but they must contain a period (optional protection). The script deletes all received messages from the phone’s memory, the phone must be configured to receive SMS in the main memory (not to the SIM card). Copies of messages can be saved to $ backupdir with the $ backup flag set. It seems everything.

#!/opt/bin/perl use Device::SerialPort; #script global setting values my $port_path = "/dev/usb/tts/0"; my $basedir = '/opt/files/'; my $msgdir = '/opt/files/msg/'; my $logdir = '/opt/files/log/'; my $backupdir = '/opt/files/msg_backup/'; #my $backup = 1; my $log = 1; my $incomingfilemask = "in.msg"; my $outgoingfilemask = "out.msg"; my $sleeptime = 10; # intercycle sleep time in seconds if no new messages my $runcycles = 100; #if runcycles set more than 1000, script will run endless if($log ==1){ open (LOG,">>$logdir".'UDP.log') }else{ open (LOG,">/dev/null"); } my $run = 1; # serial port init section $port = new Device::SerialPort($port_path) or die "cannot open serial port:$!\n"; $port->baudrate(115200); #for siemens #$port->baudrate(921600); $port->parity("none"); $port->databits(8); $port->stopbits(1); $port->read_char_time(0); $port->read_const_time(1000); my %map; my %unmap; chdir ($basedir); loadMAP(); # init mobile phone send_at('AT+CMGF=0'); #set to receive unread messages send_at('AT+CPMS="ME"'); #set default storage to mobile # main cycle section while($run){ my @msg2del; my $rcvd_id = 0; my @mobile_out = split("\r",talk_mobile("AT+CMGL=4\r\n")); #print join ("\n",@mobile_out)."\n"; my $totalsize = 0; foreach $line ( @mobile_out){ $line =~ s/\n//g; if($line eq ''){ next}; if($line =~ /\+CMGL:/){ #print "AT response: $line\n"; ($header,$param) = split(/:/,$line); (@id) = split(/,/,$param); #print "msg id's:".join(':',@id)."\n"; $msg2del[$rcvd_id]= $id[0]; $rcvd_id ++; next; } $totalsize +=length($line); if($line =~/07/){ #print LOG $line."\n"; my $parserpos = 0; my $LoSMSC = hex(substr($line,$parserpos,2)); $parserpos = $LoSMSC*2+4; # jump over SMSC address my $LoMSISDN = hex(substr($line,$parserpos,2)); $parserpos += 2; my $toMSISDN = substr($line,$parserpos,2); $parserpos += 2; unless(int($LoMSISDN/2)*2 == $LoMSISDN){ $LoMSISDN ++; } $senderMSISDN = unpack_number(substr($line,$parserpos,$LoMSISDN)); $parserpos += $LoMSISDN+2; ## jump protocol identifier my $TP_DCS = hex(substr($line,$parserpos,2)); $parserpos +=2; my $TP_SCTS = swap_number(substr($line,$parserpos,14)); $parserpos +=14; my @TS = split('',$TP_SCTS); my $rcvd_date = $TS[4].$TS[5].$TS[2].$TS[3].'20'.$TS[0].$TS[1]; my $rcvd_time = $TS[6].$TS[7].$TS[8].$TS[9].$TS[10].$TS[11]; my $TP_UDL = hex(substr($line,$parserpos,2)); $parserpos +=2; my $msg_text = hex2ascii(substr($line,$parserpos,$TP_UDL*2)); my $msgfilename = "$msgdir/$incomingfilemask.$rcvd_date$rcvd_time.$senderMSISDN"; open (OUT,">$msgfilename") || print "cannot create $msgfilename"; #print "LoMSISDN=$LoMSISDN,MSISDN=$senderMSISDN,DCS=$TP_DCS,date=$rcvd_date $rcvd_time, msglen=$TP_UDL, pos=$parserpos\n"; print OUT $msg_text; close (OUT); }else{ #print "tag not found for $line\n"; } } while($rcvd_id >0){ my $msgid = $msg2del[$rcvd_id-1]; send_at('AT+CMGD='.$msgid)."\n"; $rcvd_id --; } #send outgong messages section if(opendir(MSGDIR,$msgdir)){ my @files = readdir(MSGDIR); foreach $msgfile (@files){ if($msgfile =~ /$outgoingfilemask/){ #print "processing outgoing message $msgfile..."; ($mask,$mask2,$date,$MSISDN) = split(/\./,$msgfile); if(open(MSGIN,$msgdir.$msgfile)){ while(<MSGIN>){ chomp; #reading text of the message if(defined($_)){send_SMS($MSISDN,$_)}; #print "to $MSISDN,text <$_>\n"; } close(MSGIN); system "rm $msgdir$msgfile"; }else {print "cannot open msg file $msgfile"}; } } closedir(MSGDIR); }else{ print LOG "cannot open outgoing message directory $msg_input_dir\n"; } if($runcycles ==0 ){ undef $run; }else{ unless($runcycles >= 1000){$runcycles --;}; sleep($sleeptime); } } close(LOG); sub send_SMS{ my ($MSISDN,$text) = @_; $TP_LOA = sprintf("%02X",length($MSISDN)); $TP_MSISDN = unpack_number($MSISDN); $TP_UD = ascii2hex($text); $TP_UDL = sprintf("%02X",length($TP_UD)/2); $TP_TOA = '91'; #international number $outline = $TP_SMSC.'1100'.$TP_LOA.$TP_TOA.$TP_MSISDN.'0008AA'.$TP_UDL.$TP_UD; $lout = length($outline)/2; $outline = '00'.$outline; if(defined($backup)){ if(open(BF,">$backupdir/$MSISDN.".int(1000*rand()))){ print BF $outline; close(BF); } } my @mobile_out = split("\r",talk_mobile("AT+CMGS=$lout\r\n")); my $totalsize = 0; my $prompt; foreach $line ( @mobile_out){ $line =~ s/\n//g; if($line =~ /\>/){$prompt = 1}; }; if($prompt ==1){ my @mobile_out = split("\r",talk_mobile($outline.chr(26))); my $totalsize = 0; foreach $line ( @mobile_out){ $line =~ s/\n//g; }; }else{ #no prompt from mobile - error } } sub hex2ascii{ my $inline = shift; my $lol = length($inline); #print "inline=<".$inline.">\n"; my $result = ''; my $seek = 0; while(defined($quad = substr($inline,$seek*4,4))){ $result .= $map{$quad}; $seek ++; } #print "length =$lol,$seek characters converted\n"; return $result; } sub ascii2hex{ my $inline = shift; my $result = ''; my $seek = 0; while(defined($char = substr($inline,$seek,1))){ unless($char eq ''){$result .= $unmap{$char}}; $seek ++; } return $result; } sub unpack_number{ my $result = ''; my $inline = shift; my $seek =0; $num_len = length($inline); if(($num_len - 2 * int($num_len / 2)) >0 ){ $inline .='F'; } while($pair = substr($inline,$seek*2,2)){ $result .= reverse($pair); $seek ++; } return ($result); } sub swap_number{ my $result = ''; my $inline = shift; my $seek =0; while($pair = substr($inline,$seek*2,2)){ $result .= reverse($pair); $seek ++; } return ($result); } sub send_at{ my $cmd = shift; my $result; my @mobile_out = split("\r",talk_mobile($cmd."\r\n")); my $totalsize = 0; foreach $line ( @mobile_out){ $line =~ s/\n//g; if($line eq 'OK'){ $result = 1; }elsif($line eq 'ERROR'){ $result = -1; } } } sub talk_mobile{ my $cmd = shift; $port->lookclear; $port->write("$cmd"); my $read_chars = 0; my $buffer = ""; my $eol =1; while($eol){ my ($count,$saw) = $port->read(255); if($count > 0){ $buffer.= $saw; if(($saw =~ /OK/)or($saw =~/ERROR/)or($saw =~/\>/)) {undef $eol} } } return $buffer; } sub loadMAP{ if(open(UCS,"UCS.map")){ $loaded = 0; while(<UCS>){ chomp; my ($code,$value)=split(/,/,$_,2); $map{$code} = $value; $loaded ++; } #print $loaded ." patterns loaded\n"; close(UCS); }else{ print "cannot open UCS.map file\n"}; if(open(UUCS,"UCS.unmap")){ $loaded = 0; while(<UUCS>){ chomp; my ($code,$value)=split(/,/,$_,2); $unmap{$code} = $value; $loaded ++; } close(UUCS); }else{ print "cannot open UCS.unmap file\n"}; 

Useful links:
CPAN Serial Port Module
howToReceiveSMSUsingPC

Source: https://habr.com/ru/post/130391/


All Articles