Preliminary version of EMS and Alcatel proprietary format implemented
authorshort <>
Fri, 21 Dec 2001 07:04:44 +0000 (07:04 +0000)
committershort <>
Fri, 21 Dec 2001 07:04:44 +0000 (07:04 +0000)
gsmcmd/library supports:
  --ring-send
  --logo-group-send
  --ems-picture-send
  --ems-animation-send
  --alcatel-picture-send
  --alcatel-animation-send
Transport::Print implemented

16 files changed:
GSM/.cvsignore [new file with mode: 0644]
GSM/MANIFEST
GSM/README
GSM/SMS/Bitmap.pm [new file with mode: 0644]
GSM/SMS/NBS.pm
GSM/SMS/NBS/Alcatel.pm [new file with mode: 0644]
GSM/SMS/NBS/Frame.pm [deleted file]
GSM/SMS/NBS/Lib.pm [new file with mode: 0644]
GSM/SMS/NBS/Message.pm
GSM/SMS/PDU.pm
GSM/SMS/Transport/Print.pm [new file with mode: 0644]
GSM/SMS/Transport/XmlRpc.pm
GSM/examples/commandline/README [new file with mode: 0644]
GSM/examples/commandline/gsmcmd [new file with mode: 0755]
GSM/examples/commandline/transport.conf [new file with mode: 0644]
GSM/t/01_constructor.t

diff --git a/GSM/.cvsignore b/GSM/.cvsignore
new file mode 100644 (file)
index 0000000..625327a
--- /dev/null
@@ -0,0 +1,4 @@
+Makefile
+Makefile.old
+blib
+pm_to_blib
index 068f331..49bb8a5 100644 (file)
@@ -7,16 +7,19 @@ novelsoft_example.conf
 SMS/Config.pm
 SMS/Log.pm
 SMS/NBS.pm
-SMS/NBS/Frame.pm
 SMS/NBS/Message.pm
 SMS/NBS/Stack.pm
+SMS/NBS/Lib.pm
+SMS/NBS/Alcatel.pm
 SMS/PDU.pm
+SMS/Bitmap.pm
 SMS/Spool.pm
 SMS/Transport.pm
 SMS/Transport/Transport.pm
 SMS/Transport/NovelSoft.pm
 SMS/Transport/Serial.pm
 SMS/Transport/XmlRpc.pm
+SMS/Transport/Print.pm
 SMS/OTA/OTA.pm
 SMS/OTA/Bitmap.pm
 SMS/OTA/CLIicon.pm
@@ -40,5 +43,8 @@ examples/smartmessagingserver/media/rtttl/0003.rtttl
 examples/slashdot/README
 examples/slashdot/slashdot
 examples/slashdot/transport.conf
+examples/commandline/README
+examples/commandline/gsmcmd
+examples/commandline/transport.conf
 codes.txt
 rtttlsyntax.txt
index f4c0257..8193997 100644 (file)
@@ -97,7 +97,7 @@ EXAMPLES
 
 Look in the examples directory, this should get you started.
 The most complex one is the smartmessagingserver, allowing you to request a
-rttl or groupgraphic by sending an sms message. The slashdot example allows
+rtttl or groupgraphic by sending an sms message. The slashdot example allows
 you to receive the latest slashdot headlines in a SMS message. The ideas are
 ofcourse endless. I've build an application on top of these modules that
 expose the send functions in an XMLRPC way, thus creating a SMS webservice.
diff --git a/GSM/SMS/Bitmap.pm b/GSM/SMS/Bitmap.pm
new file mode 100644 (file)
index 0000000..a6e5a66
--- /dev/null
@@ -0,0 +1,98 @@
+package GSM::SMS::Bitmap;
+# Generic package for monochrome bitmaps
+
+my $VERSION='0.1';
+
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA=qw(Exporter);
+our @EXPORT=qw();
+
+use Image::Magick;
+use Carp;
+
+
+sub new {
+       my ($class, $arg) = @_;
+       my $self={};
+
+       bless($self,$class);
+
+       my $img;
+       if ("Image::Magick" ne ref $arg) {
+               my $err;
+               $img=Image::Magick->new() or carp "Image::Magick->new(): $!" and return undef;
+               $err=$img->Read($arg) and carp "Image::Magick->Read(\"$arg\"): $err" and return undef;
+               }
+       else {
+               $img=$arg;
+               }
+
+       $img->Set("magick"=>"mono");
+       $self->{"blob"  }=$img->ImageToBlob();
+       $self->{"width" }=$img->Get("columns");
+       $self->{"height"}=$img->Get("height" );
+       return $self;
+}
+
+sub crop {
+       my ($self, $width, $height) = @_;
+
+       if ($self->{"width"} > $width) {
+               carp "Cutting image width to $width pixels (from ".$self->{"width"}.")";
+               $self->{"width"}=$width;
+       }
+       if ($self->{"height"} > $height) {
+               carp "Cutting image height to $height pixels (from ".$self->{"height"}.")";
+               $self->{"height"}=$height;
+       }
+}
+
+sub pixref {
+       my ($self, $x, $y) = @_;
+       my $zero = 0;
+
+       return \$zero if 0  # out of bouds
+                       || $x<0 || $x>=$self->{"width" }
+                       || $y<0 || $y>=$self->{"height"}
+                       ;
+       return \vec($self->{"blob"},int(($self->{"width"}+7)/8)*$y + $x,1);
+}
+
+sub pixget {
+       my ($self, @rest) = @_;
+
+       return(${$self->pixref(@rest)});
+}
+
+# Convert the bit stream to the list of byte values
+# Hmm, it is a back ineffective to unpack() it back but who cares
+# Two versions exist - FIXME: how to merge them?
+
+sub pixlist_horiz {
+       my ($self) = @_;
+
+       my $bits="";
+       for my $y (0..$self->{"height"}) {
+               for my $x (0..int(($self->{"width"}+7)&~0x7)-1) {
+                       $bits.=$self->pixget($x,$y);
+               }
+       }
+       return unpack("C*",pack("B*",$bits));
+}
+
+sub pixlist_vert {
+       my ($self) = @_;
+
+       my $bits="";
+       for my $x (0..$self->{"width"}-1) {
+               for my $y (0..int(($self->{"height"}+7)&~0x7)-1) {
+                       $bits.=$self->pixget($x,$y);
+               }
+       }
+       return unpack("C*",pack("B*",$bits));
+}
+
+1;
index 18ec756..29e6c34 100644 (file)
@@ -13,6 +13,7 @@ use GSM::SMS::OTA::VCard;
 use GSM::SMS::OTA::Config;
 use GSM::SMS::Transport;
 use MIME::Base64;
+use Carp;
 
 
 #
@@ -31,6 +32,25 @@ sub new {
 }
 
 #
+# Send  message (new style)
+#
+sub sendsms {
+       my ($self, $msisdn, $message, %args ) = @_;
+       my $ret = 0;
+
+       my $transport = $self->{'__TRANSPORT__'};
+
+       my $nbs_message = GSM::SMS::NBS::Message->new();
+       my $err = $nbs_message->store($msisdn, $message, %args);
+       carp "Error during SMS conversion: $err" if $err;
+       foreach my $frame ( @{$nbs_message->get_frames()} ) {
+               # transport->send returns -1 on failure.
+               $ret = -1 if $transport->send($msisdn, $frame);
+       }
+       return $ret;    
+}
+
+#
 # Send  message
 #
 sub sendto {
diff --git a/GSM/SMS/NBS/Alcatel.pm b/GSM/SMS/NBS/Alcatel.pm
new file mode 100644 (file)
index 0000000..a7c90bf
--- /dev/null
@@ -0,0 +1,78 @@
+package GSM::SMS::NBS::Alcatel;
+
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA=qw(Exporter);
+our @EXPORT=qw();
+
+use GSM::SMS::NBS::Lib;
+use Carp;
+
+use vars qw($VERSION);
+$VERSION = '0.1';
+
+use constant IEI_ALCATEL_PICTURE   => 0b100;
+use constant IEI_ALCATEL_ANIMATION => 0b101;
+
+
+# returns ($msg, $udh=[...])
+sub alcatel_picture {
+       my ($class, $bitmap) = @_;
+
+       $bitmap->crop(0xFF,0xFF);
+       my @msglist=($bitmap->{"width"}, $bitmap->{"height"}, $bitmap->pixlist_vert());
+       return (pack("C*", @msglist), [
+                       {
+                               "type"=>"alcatel",
+                               "ems_compat"=>1
+                                               && !($bitmap->{"width"}&0x7)                            # width%8 == 0
+                                               && ($bitmap->{"width"}/8 * $bitmap->{"height"} <= 128), # width/8*height <= 128
+                               "alcatel_type"=>IEI_ALCATEL_PICTURE,
+                               "length"=>scalar(@msglist),
+                       }]);
+}
+
+# expects list of GSM::SMS::Bitmap instances mixed with numbers (display times)
+# returns ($msg, $udh=[...])
+sub alcatel_animation {
+       my ($class, @list) = @_;
+
+       my $sized;
+       my @words=();
+       my %imgs=();
+       my $imgmsg="";  # concatenated image bodies (produced by alcatel_picture())
+
+       my $header_len = 2*(1+@list);  # "2*"=words, "1+"=sequence_size, "@list"=items
+       for my $item (@list) {
+               if ("GSM::SMS::Bitmap" eq ref $item) {
+                       if (!exists $imgs{$item}) {
+                               $imgs{$item}=$header_len+length($imgmsg);
+                               my ($msg)=$class->alcatel_picture($item);
+                               $imgmsg.=$msg;
+                               $sized = ((0  # condition tests whether we have _violated_ EMS compatibility
+                                               || $item->{"width"} != $item->{"height"}
+                                               || ($sized && $sized != $item->{"width"})
+                                               || ($item->{"width"} != 8 && $item->{"width"} != 16)
+                                               ) ? -1 : $item->{"width"});
+                       }
+                       push(@words,$imgs{$item});
+               } else {  # "display time" here:
+                       push(@words,min($item & 0x0FFF));
+               }
+       }
+       unshift(@words,scalar(@words));
+       my $r=pack("v*",@words).$imgmsg;
+       return ($r, [
+                       {
+                               "type"=>"alcatel",
+                               "ems_compat"=>1
+                                               && 4 == keys %imgs  # exactly 4 frames
+                                               && -1 != $sized,    # frame sizes valid
+                               "alcatel_type"=>IEI_ALCATEL_ANIMATION,
+                               "length"=>length($r),
+                       }]);
+}
+
+1;
diff --git a/GSM/SMS/NBS/Frame.pm b/GSM/SMS/NBS/Frame.pm
deleted file mode 100644 (file)
index c67e690..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-package GSM::SMS::NBS::Frame;
-
-$VERSION = '0.1';
-
-sub new {
-    my $proto = shift;
-    my $class = ref($proto) || $proto;
-
-       my $self = {};
-       bless($self, $class);
-       return $self;
-}
-
-
-sub destination_port {
-       my ($self, $dest)=@_;
-
-       $self->{destination} = $dest;
-}
-
-
-sub source_port {
-       my ($self, $source)=@_;
-
-       $self->{source} = $source;
-}
-
-sub datagram_reference_number {
-       my ($self, $drn)=@_;
-
-       $self->{drn} = $drn;
-}
-
-
-sub fragment_maximum {
-       my ($self, $fmax)=@_;
-
-       $self->{fmax} = $fmax;
-}
-
-sub fragment_sequence_number {
-       my ($self, $fsn) = @_;
-
-       $self->{fsn} = $fsn;
-}
-
-
-sub asString {
-       my ($self) = @_;
-
-       my $len;
-       my $out='';
-       my @NBS_HEADER;
-
-       $NBS_HEADER[0]  = 11;           # header length, without this byte
-       $NBS_HEADER[1]  = 5;            # Port address information element, 16bit
-       $NBS_HEADER[2]  = 4;            #       Length of the info element
-       $NBS_HEADER[3]  = ($self->{destination} & 0xff00) >> 8;         # high byte destination
-       $NBS_HEADER[4]  = $self->{destination} & 0xff;                          # low byte destination
-       $NBS_HEADER[5]  = ($self->{source} & 0xff00) >> 8;                      # high byte source
-       $NBS_HEADER[6]  = $self->{source} & 0xff;                                       # low byte source
-       $NBS_HEADER[7]  = 0;            # Fragmentation information element
-       $NBS_HEADER[8]  = 3;            # Length of Info el
-       $NBS_HEADER[9]  = $self->{drn};         # fragment id
-       $NBS_HEADER[10] = $self->{fmax};        # max amount of frags
-       $NBS_HEADER[11] = $self->{fsn};         # sequence number fragment
-
-       $len=12;
-       if ($self->{fmax} == 1) {
-               $len=7;
-               $NBS_HEADER[0] = 6;
-       }
-
-       for (my $j=0; $j<$len; $j++) {
-               $i=$NBS_HEADER[$j];
-               $out.=sprintf("%02x", $i);      
-       }
-
-       return $out;
-}
-
-1;
-
-=head1 NAME
-
-GSM::SMS::NBS::Frame - Encapsulates frames for NBS messages.
-
-=head1 DESCRIPTION
-
-Create a frame for a NBS message.
-
-=head1 AUTHOR
-
-Johan Van den Brande <johan@vandenbrande.com>
diff --git a/GSM/SMS/NBS/Lib.pm b/GSM/SMS/NBS/Lib.pm
new file mode 100644 (file)
index 0000000..4fdc87a
--- /dev/null
@@ -0,0 +1,64 @@
+package GSM::SMS::NBS::Lib;
+
+use strict;
+use warnings;
+
+require Exporter;
+our @ISA=qw(Exporter);
+our @EXPORT=qw(&dcs_to_bits &empty_subload &encode_payload &nail_payload_len &min);
+
+use Carp;
+
+use vars qw($VERSION);
+$VERSION = '0.1';
+
+
+sub dcs_to_bits {
+       my ($dcs) = @_;
+
+       return ($dcs & 0xF4)==0xF4 ? 8 : 7;
+}
+
+sub empty_subload {
+       my ($dcs, $freebytes) = @_;
+
+       return $freebytes if 8==dcs_to_bits($dcs);
+       # assumed 7==dcs_to_bits($dcs)
+       return int($freebytes*8/7);  # floor-rounding is the proper one
+}
+
+sub encode_payload {
+       my ($dcs, $payload) = @_;
+
+       return $payload if 8==dcs_to_bits($dcs);
+       # assumed 7==dcs_to_bits($dcs)
+
+       my $bits="";
+       $bits.=unpack("b7",$&) while ($payload=~s/^.//);
+       $bits.="0"x7; # trailing padding
+       my $r="";
+       $r.=pack("b8",$&) while ($bits=~s/^.{8}//);
+
+       return $r;
+}
+
+# returns length(encode_payload($dcs, "T"x$payload)) but it is more effective
+sub nail_payload_len {
+       my ($dcs, $payload_len) = @_;
+
+       return $payload_len if 8==dcs_to_bits($dcs);
+       # assumed 7==dcs_to_bits($dcs)
+       return int(($payload_len*7+7)/8);
+}
+
+# Very common function but where is standardized?
+sub min {
+       my $r = shift;
+
+       for (@_) {
+               $r = $_ if $_ < $r;
+       }
+       return $r;
+}
+
+1;
index 88f51dd..4aa132b 100644 (file)
@@ -1,10 +1,30 @@
 package GSM::SMS::NBS::Message;
+
+use strict;
+use warnings;
+
+use    GSM::SMS::NBS::Lib;
 use    GSM::SMS::PDU;
-use GSM::SMS::NBS::Frame;
+use Carp;
 
+use vars qw($VERSION);
 $VERSION = '0.1';
 
-use constant DATAGRAM_LENGTH => 128;
+use constant DEFAULT_DCS_UDH    => 0xF5; # 8bit, ME specific
+use constant DEFAULT_DCS_NOUDH  => 0x00; # default DCS (typically 7bit, SIM specific)
+use constant DEFAULT_VALIDITY   => "1d"; # FIXME: currently fixed to 1 day
+use constant DEFAULT_SMS_CENTER => "";   # FIXME: currently fixed to empty SMS center address
+
+use constant USERDATA_LENGTH    => 140;
+use constant DECONCAT_TOTAL     => "_deconcat_total";  # internal: undef=>try single message, *=>try & messages
+use constant TEXT_POSITION      => "text_position";    # numeric position in $payload, UDH IE will be present only once
+
+use constant IEI_ANIMATION_LARGE  => 0x0E;  #IEI: EMS large animation (16x16 x4 = 128 bytes)
+use constant IEI_ANIMATION_SMALL  => 0x0F;  #IEI: EMS small animation ( 8x 8 x4 =  32 bytes)
+use constant IEI_PICTURE_LARGE    => 0x10;  #IEI: EMS large    picture (32x32 = 128 bytes)
+use constant IEI_PICTURE_SMALL    => 0x11;  #IEI: EMS small    picture (16x16 =  32 bytes)
+use constant IEI_PICTURE_VARIABLE => 0x12;  #IEI: EMS variable picture (other)
+use constant IEI_ALCATEL          => 0x80;  #IEI: Alcatel proprietary "data download" type
 
 # SAR for NBS messages
 # --------------------
@@ -21,71 +41,234 @@ sub new {
        return $self;
 }
 
-# Create a message from a payload
+# Create a message from a payload -- compatibility cludge!
 sub create {
        my ($self, $number, $payload, $destination_port, $source_port, $datacodingscheme) = @_;
+       my %dcs_compat = (
+                       "7bit" =>0x00,  # default DCS (typically 7bit, SIM specific)
+                       "7biti"=>0xF0,  # 7bit, immediate display
+                       "8bit" =>0xF6,  # 8bit, SIM specific
+                       "8biti"=>0xF4,  # 8bit, immediate display
+                       "8bitm"=>0xF5,  # 8bit, ME specific
+       );
 
-       # Reset the FRAME array
-       $#{$self->{'__FRAMES__'}} = -1;
+       $datacodingscheme=$dcs_compat{$datacodingscheme} if $datacodingscheme && exists $dcs_compat{$datacodingscheme};
+
+       return $self->store($number, pack("H*", $payload),
+                       "dcs"=>$datacodingscheme,
+                       "udh"=>[
+                               {
+                                       "type"            =>"port16",  #IEI: application port addressing scheme, 16 bit address
+                                       "destination_port"=>$destination_port,
+                                       "source_port"     =>$source_port,
+                               },
+                       ],
+                       );
+}
+
+sub _iebuilder {
+       my ($self, $iei, @iedata) = @_;
 
+       return ($iei,scalar(@iedata),@iedata);
+}
 
-       my $nbs = GSM::SMS::NBS::Frame->new();
-       my $pdu = GSM::SMS::PDU->new();
+sub _ie_port16 {
+       my ($self, %args) = @_;
 
-       my $datagram_length = DATAGRAM_LENGTH;
+       return () if !defined $args{"destination_port"};
+       my $source_port=$args{"source_port"} || $args{"destination_port"};
 
-       $source_port = $destination_port if (!defined($source_port));
-       my $dcs = '7bit';
-       $dcs = '8bitm' if (defined($destination_port));
+       return $self->_iebuilder(0x05,  #IEI: application port addressing scheme, 16 bit address
+                       ($args{"destination_port"} >> 8), ($args{"destination_port"} & 0xFF),
+                       ($source_port >> 8), ($source_port & 0xFF),
+       );
+}
+
+sub _ie_concat8 {
+       my ($self, %args) = @_;
+
+       return $self->_iebuilder(0x00,  #IEI: concatenated short message, 8 bit reference
+                       $args{"drn"},  # reference number
+                       $args{"fmax"}, # maximum number of short messages
+                       $args{"fsn"},  # sequence number of the current short message
+       );
+}
+
+sub _ie_ems_picture {
+       my ($self, %args) = @_;
+       my $bitmap = $args{"bitmap"};  # GSM::SMS::Bitmap instance
        
-       # print "DATACODINGSCHEME: $datacodingscheme\n";
-               
-       $dcs = $datacodingscheme if (defined($datacodingscheme));
+       $bitmap->crop(0xFF*8,0xFF);
+       my ($width, $height) = ($bitmap->{"width"}, $bitmap->{"height"});
 
-       my $udhi = -1;  
-       $udhi = 0 if (!defined($destination_port));
+       my $iei;
+          if ($width == 16 && $height == 16) { $iei = IEI_PICTURE_SMALL   ; }
+       elsif ($width == 32 && $height == 32) { $iei = IEI_PICTURE_LARGE   ; }
+       else                                  { $iei = IEI_PICTURE_VARIABLE; }
 
+       return $self->_iebuilder($iei,
+                       $args{TEXT_POSITION}, # position in the SMS
+                       ($iei != IEI_PICTURE_VARIABLE ? () : (int(($width+7)/8), $height)),  # image size (optional)
+                       $bitmap->pixlist_horiz()
+       );
+}
 
-    my $payload_len = length($payload)/2;
-       my $frags;
+sub _ie_ems_animation {
+       my ($self, %args) = @_;
+       my $bitmaps = $args{"bitmaps"};  # list of GSM::SMS::Bitmap instances
 
-       # test if the payload can fit in one message (6= length of minimal header)
-       if (($payload_len + 6) <= 140) {
-               # Ok we can have 1 message
-               $frags=1;
-               $datagram_length = $payload_len;
-       } else {
-       $frags = int($payload_len/$datagram_length) + 1;
+       if (4 != @$bitmaps) {
+               carp "Invalid length of EMS animation (4 required)"; return ();
+       }
+       my $bitmap = $$bitmaps[0];
+       my ($width, $height) = ($bitmap->{"width"}, $bitmap->{"height"});
+       for my $testbitmap (@$bitmaps) {
+               if ($width != $testbitmap->{"width"} || $height != $testbitmap->{"height"}) {
+                       carp "Non-matching sizes in EMS animation";
+               }
        }
 
-    $nbs->destination_port($destination_port);
-    $nbs->source_port($source_port);
-       
-       # If no destination port defined, then also no source port can be defined
-       # There can be a problem ! what if 2 nbs's send a nbs to the same phone with 2 the same datagrams?
-       # We have to solve this!
-       $nbs->datagram_reference_number(int(rand(255)));
-    $nbs->fragment_maximum($frags);
-
-       my $ok = 0; 
-    for (my $i=1; $i<=$frags; $i++) {
-        my $subload = substr($payload, ($i-1)*$datagram_length*2, $datagram_length*2);
-        $nbs->fragment_sequence_number($i);
-               my $msg;
-               if ($destination_port) {        
-                       $msg = uc $nbs->asString().$subload;
+       my $iei;
+          if ($width ==  8 && $height ==  8) { $iei = IEI_ANIMATION_SMALL   ; }
+       elsif ($width == 16 && $height == 16) { $iei = IEI_ANIMATION_LARGE   ; }
+       else {
+               $iei = IEI_ANIMATION_LARGE;
+               carp "Invalid frame size of EMS animation (8x8 or 16x16 required)";
+       }
+
+       return $self->_iebuilder($iei,
+                       $args{TEXT_POSITION}, # position in the SMS
+                       &{sub {
+                               my @r=();
+                               for $bitmap (@$bitmaps) {
+                                       push @r,$bitmap->pixlist_horiz();
+                               }
+                               return @r;
+                       }}
+       );
+}
+
+sub _ie_alcatel {
+       my ($self, %args) = @_;
+
+       return $self->_iebuilder(IEI_ALCATEL,
+                       (!defined($args{"name"}) ? (0x00) :  # bit 7=0 (use GSM charset)
+                                       (length($args{"name"}), unpack("C*",
+                                                       encode_payload(0xF5, GSM::SMS::PDU->inversetranslate($args{"name"}))))  # 0xF5 DCS=any 8bit
+                                       ),
+                       (!!$args{"ems_compat"} << 7)  # bit 7=EMS compatibility
+                                       |($args{"alcatel_type"} & 0x07),  # bits 0..2=Alcatel message type
+                       ($args{"length"} >> 8), ($args{"length"} & 0xFF),
+       );
+}
+
+sub _udh_build {
+       my ($self, @srcs) = @_;
+
+       my @r=();
+       no strict 'refs';
+       my @list=map { &{"_ie_".$$_{"type"}}($self,%$_); } @srcs;
+       use strict 'refs';
+       return "" if !@list;
+       return pack "C*",scalar(@list),@list;
+}
+
+# Create a message from a payload
+# returns either error message or list (DECONCAT_TOTAL,total messages)
+sub _store_try {
+       my ($self, $number, $payload, %args) = @_;
+
+       # Reset the FRAME array
+       $self->{'__FRAMES__'} = [];
+
+
+       my $PDU = GSM::SMS::PDU->new();
+
+       my @udhs=($args{"udh"} ? @{$args{"udh"}} : ());
+
+       # We put ME-specific default if ANY element of UDH was specified
+       # Autodetect $dcs BEFORE possible concatenation UDH IEs get inserted!
+       my $dcs=$args{"dcs"} || (@udhs ? DEFAULT_DCS_UDH : DEFAULT_DCS_NOUDH);
+       # print "DCS: $dcs\n";
+
+       my $deconcat_ieref; # defined only if deconcatenating
+       if (defined $args{DECONCAT_TOTAL}) {
+               my %deconcat_ie=(
+                               "type"=>"concat8",
+                               "drn"=>int(rand(0xFF)),        # reference number
+                               "fmax"=>$args{DECONCAT_TOTAL}, # maximum number of short messages
+                               "fsn"=>0x00,                   # sequence number of the current short message
+               );
+               $deconcat_ieref=\%deconcat_ie;
+               push @udhs,$deconcat_ieref;
+               return "Too long message (".$deconcat_ie{"fmax"}." elements)" if (0xFF < $deconcat_ie{"fmax"});
+       }
+
+       my @udhstatic=();
+       my @udhpositioned=();
+       for my $udh (@udhs) {
+               if (exists $udh->{TEXT_POSITION}) {
+                       push(@udhpositioned, $udh);
                } else {
-                       $msg = $subload;
+                       push(@udhstatic, $udh);
                }
-               # print "DCS-> $dcs\n";
-        my $p =  $pdu->SMSSubmit('', $number, $msg, $dcs, '1d', $udhi);
-       # print "--> $p\n"; 
+       }
+       @udhpositioned = sort { $a->{TEXT_POSITION} <=> $b->{TEXT_POSITION}; } @udhpositioned;
+
+       my $position=0;           # absolute current position in the whole long SMS
+       while (length $payload || @udhpositioned) {
+               my @udhpositioned_now=();    # items from @udhpositioned placed in the current SMS part
+               my $payload_now="";          # text  from $payload       placed in the current SMS part
+               while (1) {
+                       if (@udhpositioned && $udhpositioned[0]{TEXT_POSITION} <= $position) { # "<=" should be "=="
+                               last if USERDATA_LENGTH < length($self->_udh_build(@udhstatic,@udhpositioned_now,$udhpositioned[0]))
+                                               +nail_payload_len($dcs,length($payload_now));
+
+                               my %udhei=%{shift @udhpositioned};  # copy it to local state - we will be modifying it
+                               # we subtract the absolute position base of current SMS part:
+                               $udhei{TEXT_POSITION} -= $position-length($payload_now);
+
+                               push @udhpositioned_now,\%udhei;
+                               next;
+                       }
+                       my $minlen=length $payload;
+                       $minlen=min($minlen,$udhpositioned[0]{TEXT_POSITION}-$position) if @udhpositioned;
+                       $minlen=min($minlen,empty_subload($dcs,
+                                       USERDATA_LENGTH-length($self->_udh_build(@udhstatic,@udhpositioned_now))-length($payload_now)))
+                                       if defined $args{DECONCAT_TOTAL};
+                       last if $minlen<=0; # "<=" should be "=="
+                       $payload_now.=substr($payload,0,$minlen);
+                       $payload=substr($payload,$minlen);
+                       $position+=$minlen;
+               }
+
+               # We will ship out the current SMS part here
+               ++$deconcat_ieref->{"fsn"} if $deconcat_ieref;
+               # We exceeded userdata size, this is probably the first undeconcatenaed try from store()!
+               my $userdata=$self->_udh_build(@udhstatic,@udhpositioned_now).encode_payload($dcs,$payload_now);
+
+               return (DECONCAT_TOTAL, undef) if USERDATA_LENGTH < length($userdata);
+
+               my $pdu = $PDU->SMSSubmit(DEFAULT_SMS_CENTER, $number, $userdata,
+                               $dcs, DEFAULT_VALIDITY, !!@udhstatic || !!@udhpositioned_now
+                               );
                # Push on to frame array
-               push(@{$self->{'__FRAMES__'}}, $p );
-   
-       }       
-       return 0 if ($ok == $frags);
-       return -1;
+               # print "--> $pdu\n"; 
+               push(@{$self->{"__FRAMES__"}}, $pdu);
+       }
+
+       carp "Last fsn==".$deconcat_ieref->{"fsn"}." but fmax==".$deconcat_ieref->{"fmax"}
+                       if $args{DECONCAT_TOTAL} && $deconcat_ieref->{"fsn"}!=$deconcat_ieref->{"fmax"};
+       return (undef,(!$deconcat_ieref ? 1 : $deconcat_ieref->{"fsn"})); # success, return number of _PRODUCED_ MSes
+}
+
+sub store {
+       my ($self, $number, $payload, %args) = @_;
+
+       my ($err, $total);
+       ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>undef ); return $err if !$err || $err ne DECONCAT_TOTAL;
+       ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>0     ); return $err if $err;
+       ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>$total); return $err;
 }
 
 # Return the frames
index e851c9a..49df79f 100644 (file)
@@ -338,16 +338,9 @@ sub SMSSubmit {
        ###########################################################################     
        
        ###########################################################################     
-       # Data coding scheme (probably need to experiment withthis one!)
-       # -------------------------------------------------------------------------
-       # See SMSDeliver for a description
-       #       We use  '00' for 7bit, SIM specific                     '7bit'  (default)
-       #                       'F0' for 7bit, immediate display        '7biti'
-       #                       'F6' for 8bit, SIM specific                     '8bit'
-       #                       'F4' for 8bit, immediate display        '8biti'
-       #                       'F5' for 8bit, ME specific                      '8bitm' 
+       # Data coding scheme
        # -------------------------------------------------------------------------     
-       $pdu.=$self->encodeDataCodingScheme($dcs);
+       $pdu.=sprintf "%.2X",$dcs;
        ###########################################################################     
                
        ###########################################################################     
@@ -365,22 +358,14 @@ sub SMSSubmit {
        ###########################################################################
        # Length of message (Length of user data)
        # -------------------------------------------------------------------------     
-       $pdu.=sprintf("%.2X", length($data));
+       $pdu.=sprintf("%.2X", length($data));
        ###########################################################################
        
 
        ###########################################################################
        # Message of user data. 
        # -------------------------------------------------------------------------
-       if (($dcs eq '8bit') || ($dcs eq '8biti' || ($dcs eq '8bitm'))) {
-               $pdu.=sprintf("%.2X", length($data)/2);
-               $pdu.=$self->encode_8bit(substr($data,0,160*2));
-       } else {
-       # First to the alphabet translation on the data...
-               $pdu.=sprintf("%.2X", length($data));
-               $data = $self->inversetranslate($data);
-               $pdu.=$self->encode_7bit(substr($data,0,160));
-       }       
+       $pdu.=uc unpack "H*",$data;
        ###########################################################################
                
        return $pdu;
@@ -523,19 +508,6 @@ sub encodeValidityPeriod {
        return sprintf("%.2X", $vp);
 }
 
-sub encodeDataCodingScheme {
-       my ($self, $dcs) = @_;
-       my $c = '00';                   # default '7bit'
-       DCS: {
-               $dcs eq '7bit'  && do { $c = '00';      last; };        
-               $dcs eq '7biti' && do { $c = 'F0';      last; };
-               $dcs eq '8bit'  && do { $c = 'F6';      last; };
-               $dcs eq '8biti' && do { $c = 'F4';      last; };
-               $dcs eq '8bitm' && do { $c = 'F5';  last; };
-       };
-       return $c;      
-}
-
 sub encodeDestinationAddress {
        my ($self, $number) = @_;
        my $pdu;
@@ -668,8 +640,8 @@ sub translate {
 
 sub inversetranslate {
        my ($self, $msg) = @_;
-       $msg=~ tr (\@\$) (\x00\x02);
-       # $msg=~ tr (iaaeeEOoUu) (\x07\x0f\x7f\x04\x05\x1f\x5c\x7c\x5e\x7e);    
+       $msg=~ tr (\@\$) (\x00\x02);
+       $msg=~ tr (iaaeeEOoUu) (\x07\x0f\x7f\x04\x05\x1f\x5c\x7c\x5e\x7e);      
        return $msg;
 }
 1;
diff --git a/GSM/SMS/Transport/Print.pm b/GSM/SMS/Transport/Print.pm
new file mode 100644 (file)
index 0000000..9ac6b8c
--- /dev/null
@@ -0,0 +1,97 @@
+package GSM::SMS::Transport::Print;
+# Printing for debugging and contect conversions
+
+$VERSION = '0.1';
+
+use strict;
+use warnings;
+
+use GSM::SMS::Transport::Transport;
+use vars qw(@ISA);
+@ISA = qw(GSM::SMS::Transport::Transport);
+
+
+# All the parameters I need to run
+my @config_vars = qw( 
+       name
+       userdata
+       nomsisdn
+       binary
+);
+
+# Send a (PDU encoded) message  
+sub send       {
+       my ($self, $msisdn, $pdu) = @_;
+
+       print "$msisdn:"        if !$self->{"cfg"}{"nomsisdn"};
+       print "$pdu\n",return 0 if !$self->{"cfg"}{"userdata"};
+
+       $pdu=pack "H*",$pdu;  #unhex
+       $pdu=substr($pdu,0
+                       +1+ord(substr($pdu,0,1))  # skip SMS center address octets (incl. type) length + SMS center data
+                       );
+       my $validity_type=(ord(substr($pdu,0,1)) >> 3) & 0x03;  # extract validity period from PDU type
+       $pdu=substr($pdu,0+1+1);  # skip PDU type + message reference
+       $pdu=substr($pdu,0
+                       +1+1+int(1+ord(substr($pdu,0,1))/2)  # skip msisdn nibbles (w/o type) length + msisdn type + msisdn data
+                       +1+1  # skip PID + DCS
+                       );
+
+          if ($validity_type == 2) { $pdu=substr($pdu,1); }  # validity == relative (1 octet)
+       elsif ($validity_type == 3) { $pdu=substr($pdu,7); }  # validity == absolute (7 octets)
+       # otherwise validity either "none" (0 octets) or invalid
+       $pdu=substr($pdu,0+1);  # skip body length
+
+       if ($self->{"cfg"}{"binary"}) {
+               print $pdu;
+       } else {
+               print uc(unpack("H*",$pdu))."\n";
+       }
+       return 0;
+};
+
+# Receive a PDU encoded message
+#      $ is a ref to a PDU string
+#      return
+#      0 if PDU received
+#      -1 if no message pending  
+sub receive    {
+       my ($self, $pduref) = @_;
+
+       return -1;
+};     
+
+# Close
+sub close       {
+       my ($self) = @_;
+}
+
+# A ping command .. just return an informative string on success
+sub ping {
+       my ($self) = @_;
+
+       return "Pong.. Print  transport ok";
+}
+
+
+# give out the needed config paramters
+sub get_config_parameters {
+       my ($self) = @_;
+
+       return @config_vars;
+}
+
+# Do we have a valid route for this msisdn
+sub has_valid_route {
+       my ($self, $msisdn) = @_;
+       
+       # print "route";
+       return -1;
+}
+
+#####################################################################
+# transport specific
+#####################################################################
+
+1;
index 37292d9..793ddc2 100644 (file)
@@ -1,7 +1,7 @@
 package GSM::SMS::Transport::XmlRpc;
 
 #
-# HTTP for Remote Serial modem 
+# XmlRpc Transport for remote node functionality
 #
 
 use GSM::SMS::Transport::Transport;
diff --git a/GSM/examples/commandline/README b/GSM/examples/commandline/README
new file mode 100644 (file)
index 0000000..5557c6d
--- /dev/null
@@ -0,0 +1,7 @@
+                         SMS content convertor
+                         ~~~~~~~~~~~~~~~~~~~~~
+WHAT
+
+You have some images (gif/png) and/or ringing tones (rtttl) and you need to
+send them via some already existing SMS sending software/gateway. So you
+just need to convert them to the raw PDU (or just its UserData) format.
diff --git a/GSM/examples/commandline/gsmcmd b/GSM/examples/commandline/gsmcmd
new file mode 100755 (executable)
index 0000000..79d71d0
--- /dev/null
@@ -0,0 +1,118 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+#
+# Load some modules
+use GSM::SMS::Config;
+use GSM::SMS::NBS;
+use GSM::SMS::Bitmap;
+use GSM::SMS::NBS::Message;
+use Getopt::Long;
+require GSM::SMS::NBS::Alcatel;
+
+$|=1;     # No output buffering
+
+
+#
+# Arguments
+my $opt_verbose;
+my $opt_transport;
+my $opt_ring_send;
+my $opt_logo_group_send;
+my $opt_ems_picture_send;
+my $opt_ems_animation_send;
+my $opt_alcatel_name;
+my $opt_alcatel_picture_send;
+my $opt_alcatel_animation_send;
+GetOptions( 
+       "v|verbose"                => \$opt_verbose,
+       "transport:s"              => \$opt_transport,
+       "ring-send:s"              => \$opt_ring_send,
+       "logo-group-send:s"        => \$opt_logo_group_send,
+       "ems-picture-send:s"       => \$opt_ems_picture_send,
+       "ems-animation-send:s"     => \$opt_ems_animation_send,
+       "alcatel-name:s"           => \$opt_alcatel_name,
+       "alcatel-picture-send:s"   => \$opt_alcatel_picture_send,
+       "alcatel-animation-send:s" => \$opt_alcatel_animation_send,
+);
+
+unless ( @ARGV ) {
+       print <<EOT;
+Usage: $0 [--verbose] [--transport=<transport.conf>]
+       [--ring-send=<rtttl file>]
+       [--logo-group-send=<image file>]
+       [--ems-picture-send=<image file>]
+       [--ems-animation-send=<image file>]
+       [--alcatel-name=<resource name>]
+       [--alcatel-picture-send=<image file>]
+       [--alcatel-animation-send=<image file>]
+       <phone numbers>...
+EOT
+       exit(1);
+}
+
+#
+# Initialize
+my $nbs = GSM::SMS::NBS->new( $opt_transport ? $opt_transport : "/dev/null" );
+
+my $msisdn;
+my $message;
+my $timestamp;
+my $transportname;
+my $port;
+
+undef $/; # Read files at once, NEVER set it before as at least NBS->new() needs line-reading!
+
+for $msisdn (@ARGV) {
+
+       if ($opt_ring_send) {
+               local (*F);
+               open F, $opt_ring_send;
+               my $rtttl_string = <F>;
+               close F;
+               $nbs->sendRTTTL($msisdn, $rtttl_string);
+       }
+
+       if ($opt_logo_group_send) {
+               $nbs->sendGroupGraphic_file($msisdn, $opt_logo_group_send);
+       }
+
+       if ($opt_ems_picture_send) {
+               my $bitmap = GSM::SMS::Bitmap->new($opt_ems_picture_send);
+               $nbs->sendsms($msisdn, "", udh=>[ { "type"=>"ems_picture", TEXT_POSITION=>0, "bitmap"=>$bitmap } ]);
+       }
+
+       if ($opt_ems_animation_send) {
+               my @names=split /:/,$opt_ems_animation_send;
+               die "Required exactly 4 colon (':') delimited filenames for --ems-animation-send" if 4 != @names;
+               my @bitmaps=map { GSM::SMS::Bitmap->new($_); } @names;
+               $nbs->sendsms($msisdn, "", udh=>[ { "type"=>"ems_animation", TEXT_POSITION=>0, "bitmaps"=>\@bitmaps } ]);
+       }
+
+       if ($opt_alcatel_picture_send) {
+               my $bitmap = GSM::SMS::Bitmap->new($opt_alcatel_picture_send);
+               my ($msg, $udh) = GSM::SMS::NBS::Alcatel->alcatel_picture($bitmap);
+               $udh->[0]{"name"}=$opt_alcatel_name if defined $opt_alcatel_name;
+               $nbs->sendsms($msisdn, $msg, udh=>$udh);
+       }
+
+       if ($opt_alcatel_animation_send) {
+               my @names=split /:/,$opt_alcatel_animation_send;
+               my %imgmap=();
+               my @list=map {
+                               if (/^\d+$/) {
+                                       $_;
+                               } else {
+                                       $imgmap{$_}=GSM::SMS::Bitmap->new($_) if (!exists $imgmap{$_});
+                                       $imgmap{$_};
+                               }
+               } @names;
+               my ($msg, $udh) = GSM::SMS::NBS::Alcatel->alcatel_animation(@list);
+               $udh->[0]{"name"}=$opt_alcatel_name if defined $opt_alcatel_name;
+               $nbs->sendsms($msisdn, $msg, udh=>$udh);
+       }
+}
+
+exit(0);
diff --git a/GSM/examples/commandline/transport.conf b/GSM/examples/commandline/transport.conf
new file mode 100644 (file)
index 0000000..585dbe2
--- /dev/null
@@ -0,0 +1,6 @@
+[Print]
+       type = Print
+       name = Print
+       nomsisdn = 1  # Prefixed by "tel.no:"
+       userdata = 1  # Strip PDU header, print only UserDataHeader (+UserData), UserData length NOT present
+#      binary = 1    # Dump everything without hex encapsulation (multiple SMSes glued!)
index ccee475..dc4fc2e 100644 (file)
@@ -1,5 +1,5 @@
 use strict;
-use Test::More tests => 13;
+use Test::More tests => 16;
 
 BEGIN {
        use_ok( 'GSM::SMS::NBS');
@@ -8,11 +8,14 @@ BEGIN {
        use_ok( 'GSM::SMS::Log');
        use_ok( 'GSM::SMS::Spool');
        use_ok( 'GSM::SMS::Config');
-       use_ok( 'GSM::SMS::NBS::Frame');
+       use_ok( 'GSM::SMS::Bitmap');
        use_ok( 'GSM::SMS::NBS::Message');
        use_ok( 'GSM::SMS::NBS::Stack');
+       use_ok( 'GSM::SMS::NBS::Alcatel');
+       use_ok( 'GSM::SMS::NBS::Lib');
        use_ok( 'GSM::SMS::Transport::Transport');
        use_ok( 'GSM::SMS::Transport::Serial');
        use_ok( 'GSM::SMS::Transport::NovelSoft');
        use_ok( 'GSM::SMS::Transport::MCube' );
+       use_ok( 'GSM::SMS::Transport::Print' );
 }