File Coverage

blib/lib/Device/Modem/SMSModem.pm
Criterion Covered Total %
statement 20 175 11.4
branch 0 48 0.0
condition 0 3 0.0
subroutine 7 22 31.8
pod 13 14 92.8
total 40 262 15.2


line stmt bran cond sub pod time code
1             package Device::Modem::SMSModem;
2              
3 1     1   19054 use 5.014002;
  1         3  
4 1     1   5 use strict;
  1         2  
  1         20  
5 1     1   4 use warnings;
  1         6  
  1         36  
6              
7 1     1   4 use strict;
  1         1  
  1         15  
8 1     1   4 use warnings;
  1         2  
  1         26  
9              
10 1     1   5 use Carp;
  1         1  
  1         89  
11 1     1   1062 use Device::Modem;
  1         50717  
  1         1881  
12              
13             our $VERSION = '0.1';
14             our @ISA = ("Device::Modem");
15              
16             =head1 NAME
17              
18             Device::Modem::SMSModem - Perl extension for blah blah blah
19              
20             =head1 SYNOPSIS
21              
22             use Device::Modem::SMSModem;
23             blah blah blah
24              
25             =head1 DESCRIPTION
26              
27             This is an extension of Device::Modem intended to be be used as high level
28             API to handle SMS in USB dongles
29              
30             Example:
31              
32             #! /usr/bin/perl
33              
34             use Device::Modem::SMSModem;
35              
36             my $modem = new Device::Modem::SMSModem(
37             port => '/dev/ttyUSB0',
38             log => 'file,smstest.log',
39             loglevel => 'info');
40            
41             if ($modem->connect(baudrate => 38400)) {
42             print "Modem connected\n";
43             }
44             else {
45             die "Couldn't connect $!, stopped\n";
46             }
47              
48             # get operator MCC+MNC
49             my $op= $modem->get_operator_info();
50             print "Operator name: ".$op->{"long_name"}." MCC ".$op->{"mcc"}." MNC:".$op->{"mnc"}."\n";
51             # LAC+BTS ID
52             my $loc= $modem->get_lac_dec();
53             print "LAC: ".$loc->{"lac"}." CELL ID ".$loc->{"cell_id"}."\n";
54              
55             print "IMSI: ".$modem->get_imsi()."\n";
56              
57             # SMSC addr
58             print "SMSC address: ".$modem->get_smsc_address()."\n";
59              
60             print "Setting up SM storage...\n";
61             $modem->init_sms_storage("SM");
62              
63             print "Cleaning up storage...\n";
64             $modem->clean_sms_storage();
65              
66             print "Getting number of messages...\n";
67              
68             print "Number of messages in the storage: ".$modem->read_sms_count()."\n";
69              
70             print "Looking for new messages...\n";
71              
72             while(1)
73             {
74            
75             my $n= $modem->new_sms_count();
76             if($n)
77             {
78             print "Got $n new messages...\n";
79             my $last= $modem->sms_count()-1;
80             my $sms= $modem->read_sms($last);
81             print $sms->{"status"}." ".$sms->{"from"}." ".$sms->{"date_time"}." ".$sms->{"smsc"}." ".$sms->{"text"}."\n";
82             $modem->delete_sms($last);
83             }
84             else
85             {
86             print "No new messages...\n";
87             }
88             sleep(10);
89             }
90              
91              
92             =cut
93              
94             =head2 get_imsi
95              
96             =over 4
97              
98             This method returns IMSI
99              
100             Example:
101              
102             my $imsi= $modem->get_imsi();
103              
104             =back
105              
106             =cut
107              
108             sub get_imsi {
109 0     0 1   my ($self) = @_;
110             #get imsi
111 0 0         if(! $self->_at_send("AT+CIMI".Device::Modem::CR))
112             {
113 0           carp("Failed to send CIMI command $!");
114 0           return undef;
115             }
116              
117 0           my $reply= $self->answer("OK", 1000); # expect smth like 123456778855434
118 0 0         if($reply =~ /(\d+)/)
119             {
120 0           return $1;
121             }
122             else
123             {
124 0           carp("Could not match CIMI reply");
125 0           return undef;
126             }
127              
128             }
129              
130             =head2 get_smsc_address
131              
132             =over 4
133              
134             This method returns Serving SMSC address
135              
136             Example:
137              
138             my $imsi= $modem->get_smsc_address();
139              
140             =back
141              
142             =cut
143              
144             sub get_smsc_address {
145 0     0 1   my ($self) = @_;
146              
147             # SMSC addr
148 0 0         if(! $self->_at_send("AT+CSCA?".Device::Modem::CR))
149             {
150 0           carp("Failed to send CSCA command $!");
151 0           return undef;
152             }
153              
154 0           my $reply= $self->answer("CSCA\:", 5000); # expect smth like +CSCA: "+79202909090",145
155 0 0         if($reply =~ /CSCA\:.\s*\"\+?(\d+)\"\,/)
156             {
157 0           return $1;
158             }
159             else
160             {
161 0           carp("Could not match CSCA reply");
162 0           return undef;
163             }
164              
165             }
166              
167             =head2 get_operator_info
168              
169             =over 4
170              
171             This method returns Serving Operator and registration status. Works only for registered dongle, returns undef otherwise.
172              
173             Example:
174              
175             my $loc= $modem->get_operator_info();
176             print $loc->{"mcc"};
177             print $loc->{"mnc"};
178             print $loc->{"short_name"};
179             print $loc->{"long_name"};
180             print $loc->{"reg_status"}; # opStatus. works only for registered operators, always return 2
181              
182             =back
183              
184             =cut
185              
186             sub get_operator_info {
187 0     0 1   my ($self) = @_;
188 0           my %data= (
189             );
190              
191              
192             # get operator MCC+MNC
193 0 0         if(! $self->_at_send("AT+COPS=?".Device::Modem::CR))
194             {
195 0           carp("Failed to send COPS command $!");
196 0           return undef;
197             }
198              
199 0           my $reply= $self->answer("COPS\:", 20000); # expect smth like +COPS: 0,2,"25002",2 OR +COPS: 0,0,"MegaFon",0
200             # or +COPS: (2,"MegaFon RUS","MegaFon","25002",0),(3,"MTS-RUS","MTS","25001",0),(3,")
201 0 0         if($reply =~ /COPS\:.\s*\(2\,\s*\"(.*?)\"\,\s*\"(.*?)\"\,\s*\"(\d+)\"/)
202              
203             {
204 0           $data{"long_name"}= $1;
205 0           $data{"short_name"}= $2;
206 0           $data{"reg_status"}= 2;
207 0           $data{"mcc"}= substr($3, 0, 3);
208 0           $data{"mnc"}= substr($3, 3, 2);
209 0           return \%data;
210             }
211             else
212             {
213 0           carp("Could not match COPS reply");
214 0           return undef;
215             }
216              
217             }
218              
219             =head2 get_lac_hex
220              
221             =over 4
222              
223             This method returns vireless location info- ie LAC and CELL ID. The identifiers are returned as a reference to hash.
224             The values are in hex format.
225              
226             Example:
227              
228             my $loc= $modem->get_lac_hex();
229             print $loc->{"lac"};
230             print $loc->{"cell_id"};
231              
232             =back
233              
234             =cut
235              
236             sub get_lac_hex {
237 0     0 1   my ($self) = @_;
238 0           my %data= (
239             "lac"=>5245,
240             "cell_id"=>20012
241             );
242              
243              
244             # LAC+BTS ID
245             # force reportin first
246 0 0         if(! $self->_at_send("AT+CREG=2".Device::Modem::CR))
247             {
248 0           carp("Failed to send CREG command $!");
249 0           return undef;
250             }
251 0           my ($reply, @lines) = $self->answer("OK", 1000);
252              
253 0 0         if ($reply ne 'OK') {
254 0           carp('Failed to set CREG to report location');
255 0           return undef;
256             }
257              
258 0 0         if(! $self->_at_send("AT+CREG?".Device::Modem::CR))
259             {
260 0           carp("Failed to send CREG command $!");
261 0           return undef;
262             }
263              
264 0           $reply= $self->answer("CREG", 1000); # expect smth like +CREG: 2,1, 147D, B3BA
265             # +CREG: 2,1,"147D","599E"^M^MOK
266 0 0         if($reply =~ /CREG\:\s*2\,\s*[0-5]\,\s*\"?([0-9A-F]+)\"?\,\s*\"?([0-9A-F]+)\"?/)
267             {
268 0           $data{"lac"}= $1;
269 0           $data{"cell_id"}= $2;
270 0           return \%data;
271              
272             }
273             else
274             {
275 0           carp("Could not match CREG reply");
276 0           return undef;
277             }
278              
279            
280             }
281            
282            
283             =head2 get_lac_dec
284            
285             =over 4
286            
287             This method returns vireless location info- ie LAC and CELL ID. The identifiers are returned as a reference to hash.
288             The values are in decimal format.
289            
290             Example:
291            
292             my $loc= $modem->get_lac_dec();
293             print $loc->{"lac"};
294             print $loc->{"cell_id"};
295            
296            
297             =back
298            
299             =cut
300            
301             sub get_lac_dec {
302 0     0 1   my $data= get_lac_hex(@_);
303 0           $data->{"lac"}= hex($data->{"lac"});
304 0           $data->{"cell_id"}= hex($data->{"cell_id"});
305 0           return $data;
306            
307             }
308            
309             =head2 sms_send
310            
311             =over 4
312            
313             This method sends SMS to the specified phone number. The SMS is sent in text mode (not PDU).
314             Phone number is likely to be i the format your network is able to accept.
315            
316            
317             Example:
318            
319             $gsm->send_sms("+33123456", "Message to send as an SMS");
320            
321             =back
322            
323             =cut
324            
325             sub send_sms {
326 0     0 0   my ($self, $number, $sms) = @_;
327            
328            
329 0           my $atcmd = "AT+CMGS=\"".$number."\"".Device::Modem::CR;
330 0           $self->_at_send($atcmd);
331 0           my $result = $self->answer; # to collect the > sign
332 0           $atcmd = $sms . chr(26); # ^Z terminated string
333 0           $self->_at_send($atcmd);
334 0           my @lines;
335 0           ($result, @lines) = $self->parse_answer(qr/OK|ERROR/, 10000);
336 0 0         if ($result ne "OK") {
337 0           carp('Unable to send SMS');
338 0           return undef;
339             }
340 0           return 1;
341             }
342            
343            
344             =head2 clean_sms_storage
345            
346             =over 4
347            
348             This method removes all SMS in the storage
349            
350             Example:
351            
352             $gsm->clean_sms_storage();
353            
354             =back
355            
356             =cut
357            
358             sub clean_sms_storage {
359 0     0 1   my ($self) = @_;
360            
361 0           $self->delete_sms(0, 4);
362            
363             }
364            
365            
366             =head2 init_sms_storage
367            
368             =over 4
369            
370             This method sets sms memory being used. Possible values:
371             SM. It refers to the message storage area on the SIM card.
372             ME. It refers to the message storage area on the GSM/GPRS modem or mobile phone. Usually its storage space is larger than that of the message storage area on the SIM card.
373             MT. It refers to all message storage areas associated with the GSM/GPRS modem or mobile phone. For example, suppose a mobile phone can access two message storage areas: "SM" and "ME". The "MT" message storage area refers to the "SM" message storage area and the "ME" message storage area combined together.
374             BM. It refers to the broadcast message storage area. It is used to store cell broadcast messages.
375             SR. It refers to the status report message storage area. It is used to store status reports.
376             TA. It refers to the terminal adaptor message storage area.
377            
378             SM or ME are recommended. The same value is used for all type of messages
379            
380             Example:
381            
382             $gsm->init_sms_storage("SM");
383            
384             =back
385            
386             =cut
387            
388             sub init_sms_storage {
389 0     0 1   my ($self, $name) = @_;
390            
391            
392 0           my $command="AT+CPMS= \"".$name."\", \"".$name."\", \"".$name."\"".Device::Modem::CR;;
393            
394 0           $self->_at_send($command);
395 0           my $result= $self->answer("CPMS", 1000);
396 0           my @lines;
397            
398             #expect something like
399             # +CPMS: 8,30,8,30,8,30
400             # OK
401 0 0         if (! ($result =~ /OK/)) {
402 0           carp('Failed to set storage');
403             }
404 0 0         if($result =~ /CPMS\:\s*(\d+)\,\s*(\d+)/)
405             {
406 0           $self->{"capacity"}= $2;
407 0           $self->{"sms_in_storage"}= $1;
408            
409             }
410             else
411             {
412 0           carp("Unable to parse CPMS output");
413             }
414 0           $self->{"storage_name"}= $name;
415            
416             # sets the SMS format to TEXT instead of default PDU
417 0           my $atcmd = "AT+CMGF=1" . Device::Modem::CR;
418 0           $self->_at_send($atcmd);
419 0           ($result, @lines) = $self->parse_answer;
420            
421 0 0         if ($result ne 'OK') {
422 0           carp('Failed to set SMS format to text');
423 0           return undef;
424             }
425            
426             # sets sms detlais output to extended mode
427 0           $atcmd = "AT+CSDH=1" . Device::Modem::CR;
428 0           $self->_at_send($atcmd);
429 0           ($result, @lines) = $self->parse_answer;
430            
431 0 0         if ($result ne 'OK') {
432 0           carp('Failed to set SMS format to text');
433 0           return undef;
434             }
435            
436            
437            
438             }
439            
440             =head2 delete_sms
441            
442             =over 4
443            
444             This method delete sms rom choosen storage.
445             By default, removes a message from given index.
446             Optionally it accepts a flag what says what to remove:
447             0. Meaning: Delete only the SMS message stored at the location index from the message storage area. This is the default value.
448             1. Meaning: Ignore the value of index and delete all SMS messages whose status is "received read" from the message storage area.
449             2. Meaning: Ignore the value of index and delete all SMS messages whose status is "received read" or "stored sent" from the message storage area.
450             3. Meaning: Ignore the value of index and delete all SMS messages whose status is "received read", "stored unsent" or "stored sent" from the message storage area.
451             4. Meaning: Ignore the value of index and delete all SMS messages from the message storage area.
452            
453            
454             Returns: 1 if success, 0 otherwise
455            
456             Example:
457            
458             $gsm->delete_sms(0); #delete SMS at index 0
459             $gsm->delete_sms(0, 1); # delete all READ SMS
460            
461             =back
462            
463             =cut
464            
465             sub delete_sms {
466 0     0 1   my ($self, $index, $flag) = @_;
467 0           my $command="AT+CMGD=".$index.Device::Modem::CR;;
468 0 0         if(defined($flag))
469             {
470 0           $command = "AT+CMGD=".$index.", ".$flag.Device::Modem::CR;
471             }
472 0           $self->_at_send($command);
473 0           my ($result, @lines) = $self->parse_answer(qr/OK|ERROR/, 2000);;
474            
475 0 0         if ($result ne 'OK') {
476 0           carp('Failed to delete SMS');
477 0           return 0;
478             }
479            
480 0           $self->read_sms_count();
481 0           return 1;
482            
483            
484             }
485            
486             =head2 read_sms_count
487            
488             =over 4
489            
490             This method re-reads number of SMS available in pre-defined storage
491             Returns: number of SMS, -1 in case of errors
492            
493             Example:
494            
495             print $gsm->read_sms_count();
496            
497             =back
498            
499             =cut
500            
501             sub read_sms_count {
502 0     0 1   my ($self) = @_;
503 0           my $storage_name= $self->{"storage_name"};
504 0           my $command="AT+CPMS?".Device::Modem::CR;
505 0           $self->_at_send($command);
506            
507 0           my $result= $self->answer("CPMS", 1000);
508            
509 0 0         if (! ($result =~ /OK/)) {
510 0           carp('Failed to get storage status');
511 0           return -1;
512             }
513            
514 0 0         if ($result =~ /CPMS:\s*\"?$storage_name\"?\,(\d+)/)
515             {
516 0           $self->{"sms_in_storage"}= $1;
517 0           return $1;
518             }
519             else
520             {
521 0           carp("Failed to parse CPMS");
522 0           return -1;
523            
524             }
525            
526             }
527            
528             =head2 sms_count
529            
530             =over 4
531            
532             This method returns number of SMS available in pre-defined storage read during last read_sms_count() call.
533             Note- this method does not re-read actual sms count in the storage
534             Returns: number of SMS, -1 in case of errors
535            
536             Example:
537            
538             print $gsm->sms_count();
539            
540             =back
541            
542             =cut
543            
544             sub sms_count {
545 0     0 1   my ($self) = @_;
546 0           return $self->{"sms_in_storage"};
547             }
548            
549             =head2 capacity
550            
551             =over 4
552            
553             This method returns capacity of message stirage being used
554             Returns: capacity of the storagenumber of SMS, undef is storage has not been initialized
555            
556             Example:
557            
558             print $gsm->capacity();
559            
560             =back
561            
562             =cut
563            
564             sub capacity {
565 0     0 1   my ($self) = @_;
566 0           return $self->{"capacity"};
567             }
568            
569             =head2 read_sms
570            
571             =over 4
572            
573             This method returns capacity of message stirage being used
574             Returns: SMS strucure, undef if unable to read or index is not valid
575            
576             Example:
577             my $last= $modem->sms_count()-1;
578             my $sms= $modem->read_sms($last);
579             print $sms->{"status"}." ".$sms->{"from"}." ".$sms->{"date_time"}." ".$sms->{"smsc"}." ".$sms->{"text"}."\n";
580            
581             =back
582            
583             =cut
584            
585             sub read_sms {
586 0     0 1   my ($self, $index) = @_;
587 0 0         if($index > $self->read_sms_count())
588             {
589 0           carp("Index is out of bound");
590 0           return 0;
591             }
592            
593 0           my $command="AT+CMGR=$index".Device::Modem::CR;
594 0           $self->_at_send($command);
595 0           my ($result, @lines) = $self->parse_answer(qr/OK|ERROR/, 10000);;
596            
597             #my $result= $self->answer(qr/OK|ERROR/, 5000);
598            
599 0 0         if ($result ne "OK")
600             {
601 0           carp('Failed to read SMS');
602 0           return undef;
603             }
604            
605 0           my %sms= ();
606            
607             # expect smth like
608             # +CMGR: "REC READ","+79108922481",,"15/09/02,09:19:10+12",145,4,0,0,"+79101399997
609             # Testttt
610             # OK status from date smsc
611 0 0         if((scalar @lines) != 2)
612             {
613 0           carp("Unexpected CMGR output");
614 0           return undef;
615             }
616 0 0         if($lines[0] =~ /CMGR\:\s*\"?([A-Z ]+)\"?\,\s*\"(\+?\d+)\"\,.*?\,\s*\"(.+?)\"\,\s*\d+\,\s*\d+\,\s*\d+\,\s*\d+\,\s*\"?(\+?\d+)\"?/)
617             {
618 0           $sms{"status"}= $1;
619 0           $sms{"from"}= $2;
620 0           $sms{"date_time"}= $3;
621 0           $sms{"smsc"}= $4;
622 0           $sms{"text"}=$lines[1];
623            
624             }
625             else
626             {
627 0           carp("Unable to parse CPMS output");
628 0           return undef;
629             }
630            
631            
632 0           return \%sms;
633            
634            
635             }
636            
637             =head2 new_sms_count
638            
639             =over 4
640            
641             This method returns number of new SMS available in pre-defined storage.
642             In fact it returns number of SMS appeared since last query
643             Returns: number of SMS, -1 in case of errors
644            
645             Example:
646            
647             print $gsm->new_sms_count();
648            
649             =back
650            
651             =cut
652            
653             sub new_sms_count {
654 0     0 1   my ($self) = @_;
655 0           my $old= $self->{"sms_in_storage"};
656 0           my $total= $self->read_sms_count();
657 0 0 0       if(($total > 0) && ($total >$old ))
658             {
659 0           my $new_count= $total- $old;
660 0           return $new_count;
661             }
662 0           return 0;
663             }
664            
665             sub _at_send {
666 0     0     my ($self, $command) = @_;
667 0           $self->log->write('info', "Executing command: $command");
668 0           $self->atsend($command);
669            
670             }
671            
672             1;
673            
674             =head1 SUPPORT
675            
676             Feel free to contact me at dmitriii@gmail.com for questions or suggestions.
677             The code has been tested against Huawei E173
678             If you find that your modem is not compatible because of AT commands mismatch
679             (it may be different in different dongles) please provide modem name and attach AT command output.
680            
681             =head1 AUTHOR
682            
683             Dmitry Cheban, dmitriii@gmail.com
684            
685             =head1 COPYRIGHT
686            
687             (c) 2015, Dmitry Cheban, dmitriii@gmail.com
688            
689             This library is free software; you can only redistribute it and/or modify it under the same terms as Perl itself.
690            
691             =head1 SEE ALSO
692            
693             Device::Modem
694            
695             =cut