File Coverage

blib/lib/Device/Gsm/Sms.pm
Criterion Covered Total %
statement 75 175 42.8
branch 17 74 22.9
condition 3 21 14.2
subroutine 13 33 39.3
pod 12 22 54.5
total 120 325 36.9


line stmt bran cond sub pod time code
1             # Device::Gsm::Sms - SMS message simple class that represents a text SMS message
2             # Copyright (C) 2002-2009 Cosimo Streppone, cosimo@cpan.org
3             # Copyright (C) 2006-2011 Grzegorz Wozniak, wozniakg@gmail.com
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it only under the terms of Perl itself.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # Perl licensing terms for details.
12             #
13             # Commercial support is available. Write me if you are
14             # interested in new features or software support.
15             #
16             # $Id$
17              
18             package Device::Gsm::Sms;
19              
20 6     6   44 use strict;
  6         14  
  6         248  
21 6     6   6937 use integer;
  6         62  
  6         29  
22              
23 6     6   184 use constant SMS_DELIVER => 0x00;
  6         14  
  6         383  
24 6     6   36 use constant SMS_SUBMIT => 0x01;
  6         9  
  6         245  
25 6     6   29 use constant SMS_STATUS => 0x02;
  6         9  
  6         217  
26              
27 6     6   3853 use Device::Gsm::Pdu;
  6         28  
  6         151  
28 6     6   4026 use Device::Gsm::Sms::Structure;
  6         26  
  6         308  
29 6     6   73 use Device::Gsm::Sms::Token;
  6         12  
  6         16225  
30              
31 0     0   0 sub _log { print @_, "\n"; }
32 0     0   0 sub _parent { $_[0]->{_parent} }
33              
34             #
35             # new(
36             # header => '+CMGL: .....',
37             # pdu => '[encoded pdu string]',
38             # )
39             #
40             # creates message object
41             #
42             sub new {
43 6     6 1 6169 my ($proto, %opt) = @_;
44 6   33     43 my $class = ref $proto || $proto;
45              
46             # Create new message object
47 6         10 my $self = {};
48              
49             # Store gsm parent object reference
50 6 50       24 if (exists $opt{'parent'}) {
51 0         0 $self->{'_parent'} = $opt{'parent'};
52              
53             # Assume default storage for sms message
54 0   0     0 $opt{'storage'} ||= $self->{'_parent'}->storage();
55             }
56              
57             # Store options into main object
58 6         57 $self->{'options'} = \%opt;
59              
60             # Hash to contain token objects after decoding (must be accessible by name)
61 6         14 $self->{'tokens'} = {};
62              
63 6 50 33     42 return undef unless (exists $opt{'header'} && exists $opt{'pdu'});
64              
65             #_log("NEW SMS OBJECT");
66             #_log("Header [$opt{header}]");
67             #_log("PDU [$opt{pdu}]");
68              
69             # Check for valid msg header (thanks to Pierre Hilson for his patch
70             # to make this regex work also for Alcatel gsm software)
71 6 50       62 if ($opt{'header'} =~ /\+CMGL:\s*(\d+),\s*(\d+),\s*(\w*),\s*(\d+)/o) {
72              
73 6         28 $self->{'index'} = $1; # Position of message in SIM card
74 6         19 $self->{'status'}
75             = $2; # Status of message (REC READ/UNREAD, STO, ...);
76 6         17 $self->{'alpha'} = $3; # Alphanumeric representation of sender
77 6         22 $self->{'length'} = $4; # Final length of message
78 6         14 $self->{'pdu'} = $opt{'pdu'}; # PDU content
79 6         23 $self->{'storage'} = $opt{'storage'}; # Storage (SM or ME)
80              
81 6         21 bless $self, $class;
82              
83 6 50       17 if ($self->decode(Device::Gsm::Sms::SMS_DELIVER)) {
    0          
84              
85             # _log('OK, message decoded correctly!');
86             }
87             elsif ($self->decode(Device::Gsm::Sms::SMS_STATUS)) {
88              
89             }
90             else {
91              
92             # _log('CASINO!');
93 0         0 undef $self;
94             }
95              
96             }
97             else {
98              
99             # Warning: could not parse message header
100 0         0 undef $self;
101              
102             }
103              
104 6         30 return $self;
105             }
106              
107             #
108             # time(): returns message time in ascii format
109             #
110             sub time {
111 0     0 0 0 my $self = shift;
112 0 0       0 if (my $t = $self->token('SCTS')) {
113 0         0 return $t->toString();
114             }
115 0         0 return '';
116             }
117              
118             #
119             # time_dt (): returns status message discharge time in ascii format
120             #
121             sub time_dt {
122 0     0 0 0 my $self = shift;
123 0 0       0 if (my $t = $self->token('DT')) {
124 0         0 return $t->toString();
125             }
126 0         0 return '';
127             }
128              
129             #
130             # message_ref(): returns message reference of status message
131             #
132             sub message_ref {
133 0     0 0 0 my $self = shift;
134 0 0       0 if (my $t = $self->token('MR')) {
135 0         0 return $t->toString();
136             }
137 0         0 return '';
138             }
139              
140             #
141             # type(): returns message type in ascii readable format
142             #
143             {
144              
145             # List of allowed status strings
146             my @status
147             = ('UNKNOWN', 'REC UNREAD', 'REC READ', 'SENT UNREAD', 'SENT READ');
148              
149             sub status () {
150 0     0 1 0 my $self = shift;
151 0 0       0 return $status[ defined $self->{'status'} ? $self->{'status'} : 0 ];
152             }
153              
154             }
155              
156             #
157             # decode( CMGL_header, pdu_string )
158             #
159             # creates a new Device::Gsm::Sms object from
160             # PDU encoded message string returned by +CMGL commands
161             #
162             # If some error occurs, returns undef.
163             #
164             #
165             sub _old_decode {
166 0     0   0 my ($header, $pdu) = @_;
167 0         0 my %msg = ();
168 0         0 my $errors = 0;
169              
170             # Copy original header/pdu strings
171 0         0 $msg{'_HEADER'} = $header;
172 0         0 $msg{'_PDU'} = $pdu;
173              
174             #
175             # Decode header string
176             #
177 0 0       0 if ($header =~ /\+CMGL:\s*(\d+),(\d+),(\d*),(\d+)/) {
178 0         0 $msg{'index'} = $1;
179 0         0 $msg{'type'} = $2;
180 0         0 $msg{'xxx'} = $3; # XXX
181 0         0 $msg{'length'} = $4;
182             }
183              
184             #
185             # Decode all parts of PDU message
186             #
187              
188             # ----------------------------------- SCA (service center address)
189 0         0 my $sca_length = hex(substr $pdu, 0, 2);
190 0 0       0 if ($sca_length == 0) {
191              
192             # No SCA provided, take default
193 0         0 $msg{'SCA'} = undef;
194             }
195             else {
196              
197             # Parse SCA address
198             #print STDERR "SCA length = ", $sca_length, "; ";
199             #print STDERR "Parsing address ", substr( $pdu, 0, ($sca_length+1) << 1 );
200 0         0 $msg{'SCA'} = Device::Gsm::Pdu::decode_address(
201             substr($pdu, 0, ($sca_length + 1) << 1));
202              
203             #print STDERR ' = `', $msg{'SCA'}, "'\n";
204             }
205              
206             # ----------------------------------- PDU type
207 0         0 $pdu = substr $pdu => (($sca_length + 1) << 1);
208 0         0 $msg{'PDU_TYPE'} = substr $pdu, 0, 2;
209 0         0 undef $sca_length;
210              
211             # ----------------------------------- OA (originating address)
212 0         0 $pdu = substr $pdu => 2;
213 0         0 my $oa_length = hex(substr $pdu, 0, 2);
214              
215 0         0 $msg{'OA'} = Device::Gsm::Pdu::decode_address(
216             substr($pdu, 0, ($oa_length + 1) << 1));
217 0         0 undef $oa_length;
218              
219             # PID (protocol identifier)
220             # DCS (data coding scheme)
221             # SCTS (service center time stamp)
222             # UDL + UD (user data)
223 0         0 @msg{qw/PID DCS SCTS UDL UD/} = unpack 'A2 A2 A14 A2 A*', $pdu;
224              
225             #map { $msg{$_} = hex $msg{$_} } qw/PID DCS UDL/;
226             #
227             # Decode USER DATA in 7/8 bit encoding
228             #
229 0 0       0 if ($msg{'DCS'} eq '00') { # DCS_7BIT
    0          
230 0         0 Device::Gsm::Pdu::decode_text7($msg{'UD'});
231             }
232             elsif ($msg{'DCS'} eq 'F6') { # DCS_8BIT
233 0         0 Device::Gsm::Pdu::decode_text8($msg{'UD'});
234             }
235              
236             # XXX DEBUG
237             #foreach( sort keys %msg ) {
238             # print STDERR 'MSG[', $_, '] = `'.$msg{$_}.'\'', "\n";
239             #}
240              
241 0         0 bless \%msg, 'Device::Gsm::Sms';
242             }
243              
244             sub decode {
245 6     6 1 10 my ($self, $type) = @_;
246 6         17 $self->{'type'} = $type;
247              
248             # Get list of tokens for this message (from ::Sms::Structure)
249 6         12 my $cPdu = $self->{'pdu'};
250              
251             # Check that PDU is not empty
252 6 50       16 return 0 unless $cPdu;
253              
254             # Backup copy for "backtracking"
255 6         9 my $cPduCopy = $cPdu;
256              
257 6         32 my @token_names = $self->structure();
258 6         12 my $decoded = 1;
259              
260             #is udh in pdu?
261 6         8 my $udh_parsed = 0;
262 6         17 while (@token_names) {
263              
264             # Create new token object
265 48         238 my $token = new Sms::Token(shift @token_names,
266             { messageTokens => $self->{'tokens'} });
267 48 50       165 if (!defined $token) {
268 0         0 $decoded = 0;
269 0         0 last;
270             }
271              
272             # If decoding is completed successfully, add token object to message
273             #_log('PDU BEFORE ['.$cPdu.']', length($cPdu) );
274              
275 48 50       209 if ($token->decode(\$cPdu)) {
276              
277             # Store token object into SMS message
278 48         183 $self->{'tokens'}->{ $token->name() } = $token;
279              
280             # Catch message type indicator (MTI) and re-load structure
281             # We must also skip message types 0x02 and 0x03 because we don't handle them currently
282 48 100       158 if ($token->name() eq 'PDUTYPE') {
283              
284 8         22 my $mti = $token->MTI();
285 8         24 my $udhi = $token->UDHI();
286              
287             # # If MTI has bit 1 on, this could be a SMS-STATUS message (0x02), or (0x03???)
288             # if( $mti >= SMS_STATUS ) {
289             # _log('skipping unhandled message type ['.$mti.']');
290             # return undef;
291             # }
292              
293 8 100       28 if ($mti != $type) {
294              
295             #_log('token PDUTYPE, data='.$token->data().' MTI='.$token->get('MTI').' ->MTI()='.$token->MTI());
296             #
297             # This is a SMS-SUBMIT message, so:
298             #
299             # 1) change type
300             # 2) restore original PDU message
301             # 3) reload token structure
302             # 4) restart decoding
303             #
304 2         8 $self->type($type = $mti);
305              
306 2         2 $cPdu = $cPduCopy;
307 2         9 @token_names = $self->structure();
308              
309             #_log('RESTARTING DECODING AFTER MTI DETECTION'); #;
310 2         7 redo;
311             }
312              
313 6 50 33     51 if ($udh_parsed == 0 and $udhi == 1) {
314 0         0 $cPdu = $cPduCopy;
315 0         0 @token_names = $self->structure();
316 0         0 $udh_parsed = 1;
317 0         0 redo;
318             }
319              
320             #_log(' ', $token->name(), ' DATA = ', $token->toString() );
321              
322             }
323              
324             }
325              
326             #_log('PDU AFTER ['.$cPdu.']', length($cPdu) );
327              
328             }
329              
330             #_log("\n", 'PRESS ENTER TO CONTINUE'); ;
331              
332 6         28 return $decoded;
333              
334             }
335              
336             #
337             # Delete an sms message
338             #
339             sub delete {
340 0     0 1 0 my $self = $_[0];
341 0         0 my $gsm = $self->_parent();
342 0         0 my $ok;
343              
344             # Try to delete message
345 0         0 my $msg_index = $self->index();
346 0         0 my $storage = $self->storage();
347              
348             # Issue delete command
349 0 0 0     0 if (ref $gsm && $storage && $msg_index >= 0) {
      0        
350 0         0 $ok = $gsm->delete_sms($msg_index, $storage);
351 0 0       0 $gsm->log->write('info',
352             'Delete sms n.'
353             . $msg_index
354             . ' in storage '
355             . $storage . ' => '
356             . ($ok ? 'OK' : '*ERROR'));
357             }
358             else {
359 0         0 $gsm->log->write('warn',
360             'Could not delete sms n.'
361             . $msg_index
362             . ' in storage '
363             . $storage
364             . '. Internal error.');
365 0         0 $ok = undef;
366             }
367              
368 0         0 return $ok;
369             }
370              
371             #
372             # Returns message own index number (position)
373             #
374             sub index {
375 0     0 1 0 my $self = $_[0];
376 0         0 return $self->{'index'};
377             }
378              
379             #
380             # Returns message storage (SM - SIM card or ME - phone memory)
381             #
382             sub storage {
383 0     0 1 0 my $self = $_[0];
384 0         0 return $self->{'storage'};
385             }
386              
387             #
388             # Only valid for SMS_SUBMIT and SMS_STATUS messages
389             #
390             sub recipient {
391 0     0 1 0 my $self = shift;
392 0 0 0     0 if ($self->type() == SMS_SUBMIT or $self->type() == SMS_STATUS) {
393 0         0 my $t = $self->token('DA');
394 0 0       0 return $t->toString() if $t;
395             }
396             }
397              
398             #
399             #Only valid for SMS_STATUS messages returns status code(in hex) extracted from status message
400             #Codes are explained in ST.pm
401             #
402             sub delivery_status {
403 0     0 0 0 my $self = shift;
404 0 0       0 if ($self->type() == SMS_STATUS) {
405 0         0 my $t = $self->token('ST');
406 0 0       0 return $t->toString() if $t;
407             }
408             }
409              
410             #
411             # Only valid for SMS_DELIVER messages (?)
412             #
413             sub sender {
414 0     0 1 0 my $self = shift;
415 0 0       0 if ($self->type() == SMS_DELIVER) {
416 0         0 my $t = $self->token('OA');
417 0 0       0 return $t->toString() if $t;
418             }
419             }
420              
421             # Alias for text()
422             sub content {
423 0     0 1 0 return $_[0]->text();
424             }
425              
426             sub text {
427 4     4 1 31 my $self = shift;
428 4         17 my $t = $self->token('UD');
429 4 50       25 return $t->toString() if $t;
430             }
431              
432             #
433             #only valid for SMS_DELIVER messages, retuns presence of UDH
434             #
435             sub is_udh {
436 0     0 0 0 my $self = shift;
437 0 0       0 if ($self->type() == SMS_DELIVER) {
438 0         0 return $self->{'tokens'}->{'PDUTYPE'}->{'_UDHI'};
439             }
440             }
441              
442             #
443             #only valid for SMS_DELIVER messages with UDH, returns if sms is csms
444             #
445             sub is_csms {
446 0     0 0 0 my $self = shift;
447 0 0       0 if ($self->is_udh()) {
448 0         0 return $self->{'tokens'}->{'UDH'}->{'_IS_CSMS'};
449             }
450             }
451              
452             #
453             #only valid for SMS_DELIVER messages with UDH, retuns CSM reference number
454             #
455             sub csms_ref_num {
456 0     0 0 0 my $self = shift;
457 0 0       0 if ($self->is_csms()) {
458 0         0 return $self->{'tokens'}->{'UDH'}->{'_REF_NUM'};
459             }
460             }
461              
462             #
463             #only valid for SMS_DELIVER messages with UDH, retuns CSM reference number
464             #
465             sub csms_ref_hex {
466 0     0 0 0 my $self = shift;
467 0 0       0 if ($self->is_csms()) {
468 0         0 return $self->{'tokens'}->{'UDH'}->{'_REF_HEX'};
469             }
470             }
471              
472             #
473             #only valid for SMS_DELIVER messages with UDH, retuns CSM parts count
474             #
475             sub csms_parts {
476 0     0 0 0 my $self = shift;
477 0 0       0 if ($self->is_csms()) {
478 0         0 return $self->{'tokens'}->{'UDH'}->{'_PARTS'};
479             }
480             }
481              
482             #
483             #only valid for SMS_DELIVER messages with UDH, retuns CSM current part number
484             #
485             sub csms_part_num {
486 0     0 0 0 my $self = shift;
487 0 0       0 if ($self->is_csms()) {
488 0         0 return $self->{'tokens'}->{'UDH'}->{'_PART_NUM'};
489             }
490             }
491              
492             sub token ($) {
493 4     4 1 9 my ($self, $token_name) = @_;
494 4 50       12 return undef unless $token_name;
495              
496 4 50       15 if (exists $self->{'tokens'}->{$token_name}) {
497 4         16 return $self->{'tokens'}->{$token_name};
498             }
499             else {
500 0         0 warn('undefined token ' . $token_name . ' for this sms');
501 0         0 return undef;
502             }
503             }
504              
505             #
506             # Returns type of sms (SMS_DELIVER || SMS_SUBMIT)
507             #
508             sub type {
509 12     12 1 17 my $self = shift;
510 12 100       29 if (@_) {
511 2         4 $self->{'type'} = shift;
512             }
513 12         47 $self->{'type'};
514             }
515              
516             =pod
517              
518             =head1 NAME
519              
520             Device::Gsm::Sms - SMS message internal class that represents a single text SMS message
521              
522             =head1 SYNOPSIS
523              
524             # A list of Device::Gsm::Sms messages is returned by
525             # Device::Gsm messages() method.
526              
527             use Device::Gsm;
528             ...
529             @sms = $gsm->messages();
530              
531             if( @sms ) {
532             foreach( @sms ) {
533             print $msg->storage() , "\n";
534             print $msg->recipient() , "\n";
535             print $msg->sender() , "\n";
536             print $msg->content() , "\n";
537             print $msg->time() , "\n";
538             print $msg->type() , "\n";
539             }
540             }
541              
542             # Or you can instance a sms message from raw PDU data
543             my $msg = new Device::Gsm::Sms(
544             header => '+CMGL: ...',
545             pdu => `[encoded pdu data]',
546             storage=> 'ME', # or 'SC'
547             );
548              
549             if( defined $msg ) {
550             print $msg->recipient() , "\n";
551             print $msg->sender() , "\n";
552             print $msg->content() , "\n"; # or $msg->text()
553             print $msg->time() , "\n";
554             print $msg->type() , "\n";
555             }
556              
557             $msg->delete();
558              
559             =head1 DESCRIPTION
560              
561             C class implements very basic SMS message object,
562             that can be used to decode C<+CMGL> GSM command response to build a more
563             friendly high-level object.
564              
565             =head1 METHODS
566              
567             The following is a list of methods applicable to C objects.
568              
569             =head2 content()
570              
571             See text() method.
572              
573             =head2 decode()
574              
575             Starts the decoding process of pdu binary data. If decoding process
576             ends in success, return value is true and sms object is filled with
577             all proper values.
578              
579             If decoding process has errors or pdu data is not provided, return
580             value is 0 (zero).
581              
582              
583             =head2 delete()
584              
585             Delete the current SMS message from sim card.
586             Example:
587              
588             $gsm = Device::Gsm->new();
589             ...
590             my @msg = $gsm->messages();
591             $msg[0] && $msg[0]->delete();
592              
593             =head2 new()
594              
595             Basic constructor. You can build a new C object from the
596             raw B<+CMGL> header and B data. Those data is then decoded and a new
597             sms object is instanced and all information filled, to be available
598             for subsequent method calls.
599              
600             The allowed parameters to new() method are:
601              
602             =over 4
603              
604             =item header
605              
606             This is the raw B<+CMGL> header string as modem outputs when you
607             issue a B<+CMGL> command
608              
609             =item pdu
610              
611             Binary encoded sms data
612              
613             =item storage
614              
615             Tells which storage to delete the message from. Check the documentation of your
616             phone to know valid storage values. Default values are:
617              
618             =over 4
619              
620             =item C
621              
622             Deletes messages from gsm phone memory.
623              
624             =item C
625              
626             Deletes messages from sim card.
627              
628             =back
629              
630             =back
631              
632             =head2 index()
633              
634             Returns the sms message index number, that is the position of message in the
635             internal device memory or sim card.
636             This number is used for example to delete the message.
637              
638             my $gsm = Device::Gsm->new(port=>'/dev/ttyS0');
639             ...
640             my @messages = $gsm->messages();
641             ...
642             # Delete the first returned message
643             my $msg = shift @messages;
644             $gsm->delete_sms( $msg->index() );
645              
646             =head2 recipient()
647              
648             Returns the sms recipient number (destination address = DA)
649             as string (ex.: C<+39012345678>).
650              
651             =head2 sender()
652              
653             Returns the sms sender number (originating address = OA) as string.
654              
655             =head2 status()
656              
657             Status of the message can be one value from the following list:
658              
659             =for html
660            
661              
662             =for pod
663             'UNKNOWN', 'REC UNREAD', 'REC READ', 'SENT UNREAD', 'SENT READ'
664              
665             =head2 storage()
666              
667             Returns the storage where SMS has been read from.
668              
669             =head2 text()
670              
671             Returns the textual content of sms message.
672              
673             =head2 token()
674              
675             Returns the given PDU token of the decoded message (internal usage).
676              
677             =head2 type()
678              
679             SMS messages can be of two types: SMS_SUBMIT and SMS_DELIVER, that are defined by
680             two constants with those names. type() method returns one of these two values.
681              
682             Example:
683              
684             if( $sms->type() == Device::Gsm::Sms::SMS_DELIVER ) {
685             # ...
686             }
687             elsif( $sms->type() == Device::Gsm::Sms::SMS_SUBMIT ) {
688             # ...
689             }
690              
691             =head1 REQUIRES
692              
693             =over 4
694              
695             =item *
696              
697             Device::Gsm
698              
699             =back
700              
701             =head1 EXPORTS
702              
703             None
704              
705             =head1 TODO
706              
707             =over 4
708              
709             =item *
710              
711             Complete and proof-read documentation and examples
712              
713             =back
714              
715             =head1 COPYRIGHT
716              
717             Device::Gsm::Sms - SMS message simple class that represents a text SMS message
718              
719             Copyright (C) 2002-2009 Cosimo Streppone, cosimo@cpan.org
720              
721             This program is free software; you can redistribute it and/or modify
722             it only under the terms of Perl itself.
723              
724             This program is distributed in the hope that it will be useful,
725             but WITHOUT ANY WARRANTY; without even the implied warranty of
726             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
727             Perl licensing terms for details.
728              
729             =head1 AUTHOR
730              
731             Cosimo Streppone, cosimo@cpan.org
732              
733             =head1 SEE ALSO
734              
735             L, perl(1)
736              
737             =cut