携帯電話経由でSMSを送受信するための簡単なPERLスクリプト

前文





「スマートホーム」(以下UDと呼びます)の自己開発の過程で、何らかの形で、インターネット上で既に作成されて利用可能な、小さいが非常に必要なユーティリティ、スクリプトプログラムを何らかの形で定期的に記述する必要がありました。プロジェクトで使用します。 「なぜふさわしくないのですか?」と経験豊富な開発者が尋ねます。たぶん、「猫を料理する方法がわからないので、猫が好きじゃないですか?」 そうかもしれませんが、それにもかかわらず、タスクがD-Link DIR-320のようなものを好転させることである場合、「脂肪のための時間はありません、私は生きています」。 ここで、おそらく、以前のPERLがUD開発のメイン言語として選択され、ターゲットプラットフォームでの「高速で汚い」開発に最も適していることに注意する必要があります。 なぜ高速で汚れているのですか? すべてが非常に簡単です。友人や親relativeのためのいくつかのデバイスのインストールを除いて、製品の配布は計画されていませんでした。 冬の数か月前に「高速」コンポーネントが普及していることが重要であり、システムは週末のみ宿泊施設のある温かいコテージに設置する準備をしていました。 これは、所有者がいないときに暖房システムをシャットダウンすると、暖房システムの霜取りなど、最も悲惨な結果につながる可能性があることを意味します。

はい、長い間gnokiiCPAN SMS-Server-Tools 、そして実際には多くのものがあることを知っています 。 しかし、いくつかの既製のソリューションを長時間探した後、それでも自転車の発明を開始する必要がありました。 ここで、メインロジックの開発にスクリプトの作成に費やした時間を費やす方が良いことを完全によく理解していますが、それでも事実は残っています:何らかの理由で既成のソリューションと考えられるものが要件のリストに収まっていませんでした。 以下について。



問題の声明



そのため、タスクは、ホストに直接接続された携帯電話を介してSMSを介してDDのメイン制御プロセスと通信するように設定されています。 「バイク」は次の要件を満たす必要があることが決定されました。

-外部ライブラリへの最小限の依存関係。

-「早くて汚い」開発(最初のデバッグで2日間、もう時間がありませんでした)。

-クライアント(UDのメインプロセス)モードに関して非同期で動作する機能。 電話との交換の遅延とハングは、クライアントプロセスをブロックしてはなりません。 まさか。

-コンパイルの必要がないため、主に「embedded-linux」タイプの他のプラットフォームへの移植が簡単になります。

-最小機能:SMSの受信+ SMSの送信。

-UNIXの精神に基づいたシンプルなインターフェイス。

-デーモンモードとシングルランモードの両方で動作する機能。

-キリル文字でのSMSサポート。

-を含む電話モデルの最大数で動作する機能 古いもの。



解決策



実際、問題の発言後、可能な解決策の検索範囲は大幅に狭まりました。 次のことが決定されました。

-Perlはすでに存在し、かなりうまく機能していたため、Perlスクリプトになります。 もちろん、タスクはシェル上で実現可能ですが、「高速」は失われました。

-スクリプトへのインターフェースはファイルであり、ファイルのみです。 これにより絶対的な非同期性が得られますが、信頼性と引き換えになります。

-電話との通信はUDPモードで行われます。 これは、前のリストの最後の2つの要件を実装する唯一の方法です。

-読みやすく再利用するための簡単な手続きが必要な場合を除いて、スクリプトは可能な限り線形で、OOPなし、またはモジュール性です。 一言で言えば、高速で汚い。



どうした



実際には、約300行のスクリプトと、外部CPANライブラリとしてのDevice :: SerialPortモジュールの使用。 後者の状況は、耐性を著しく悪化させます。 このモジュールはコンパイルが必要です(ネイティブにコンパイルされていないため、ほとんどの場合、クロスコンパイル)が、ほとんどのリポジトリで既製で利用できます。 一般に、タスクは実装されており、スクリプトは何年も問題なく機能しています。 シーメンスS65電話機で使用しましたが、他の電話機では不要でした。 スクリプトを機能させるには、さらに2つのデータファイルが必要です。UCS.mapとUCS.unmap。これらは非常に空であり、ここでの公開には膨大ですが、それらを受け取りたい人と喜んで共有します。 Habrを読み取り専用に設定するヒントについては、非常に感謝しています(皮肉ではありません)。



スクリプトについて簡単に説明すると、すべての最も重要な定数(パス、ロギングモードなど)が最初に宣言され、初期化されます。これ以上編集することはほとんどありません。 メッセージファイルを送信するには、$ msgdirディレクトリに置く必要があります。 ファイル名は、慣例に準拠する必要があります$ outgoingfilemask $ Date。$ MSISDN、

ここで、$ outgoingfilemaskはスクリプトヘッダーのマスクの値、$ dateは送信日(悪いオプション)または連続番号、バッチ送信のファイル名の重複を避けるために使用されます、$ MSISDNは国際形式の受信者の電話番号ですが、810などのプレフィックスはありません、 00、+など ファイル自体-文字変換されたメッセージテキスト。 なぜトランスリット-それが起こった(「高速&ダーティ」)。 実際、これは、ほとんどすべてのエンコーディングでUCS.mapおよびUCS.unmapファイルをいっぱいにすることで簡単に修復されます。

受信も同様に機能しますが、ファイル名$ incomingfilemaskのマスクを使用します。 両方のマスクを再定義できますが、ピリオドを含める必要があります(追加の保護)。 このスクリプトは、受信したすべてのメッセージを携帯電話のメモリから削除します。携帯電話は、SIMカードではなくメインメモリでSMSを受信するように設定する必要があります。 メッセージのコピーは、$ backupフラグを設定して$ backupdirに保存できます。 すべてがそうです。



#!/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"};
      
      





便利なリンク:

CPANシリアルポートモジュール

howToReceiveSMSUsingPC



All Articles