<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl -w
#
# GSM phone toy
#
# actual functionality changes every time I edit it. handle with care.
#
use Device::SerialPort;
use strict;
use lib $ENV{HOME} . "/src/perl";
use GSM::SMS::PDU;
use POSIX;
use Data::Dumper;
use Mail::Folder;
use Mail::Folder::Mbox;
use Mail::Internet;
use Getopt::Long;
use Text::CSV_XS;
use Date::Parse;
use MyVodafone;
use Time::Local;

my %msgnum; # tie this, maybe
my ( $firstbill, $lastbill ) = ( "first", "" );

# hack
if ( open( LAST, "&lt;$ENV{HOME}/.vodafone/lastsms" )) {
    $msgnum{0} = &lt;LAST&gt;;
    chomp( $msgnum{0} );
    print STDERR "Loaded date " . scalar( localtime( $msgnum{0} )) . "\n"
      if $ENV{DEBUG};
}

# Load up login data
my %config;
if ( open( FILE, "&lt;$ENV{HOME}/.vodafone/myvoda" )) {
    while (my $line = &lt;FILE&gt;) {
        chomp( $line );
        next if $line =~ /^([;#].*|\s*)$/; # skip blanks + comments
        if ( $line =~ /^\[(.*)\]\s*$/ ) {
            next;
        }
        my ( $option, $value ) = split( /\s*=\s*/, $line, 2 );
        if ( defined( $option ) and defined( $value )) {
            $config{$option} = $value;
        }
    }
}

package PhoneBookEntry;
sub new {
    my ( $ref, $name, $num, $type ) = @_;

    my $entry = bless {
                       'name' =&gt; undef,
                       'number' =&gt; undef,
                       'type' =&gt; undef
                      }, $ref;

    $entry-&gt;name( $name );
    $entry-&gt;number( $num );
    $entry-&gt;type( $type );

    $entry;
}

sub name {
    my ( $ref, $name ) = @_;

    if ( defined( $name )) {
        # fixme Error check
    }

    $name ? $ref-&gt;{'name'} = $name : $ref-&gt;{'name'};
}

sub number {
    my ( $ref, $num ) = @_;

    if ( defined( $num )) {
        if ( $num !~ /^[0-9\+]+$/ ) {
            $^W and warn "$num is an invalid phone number.\n";
            return;
        }
    }

    $num ? $ref-&gt;{'number'} = $num : $ref-&gt;{'number'};
}

my %types = ( "129" =&gt; "unknown",
			  "161" =&gt; "national",
			  "145" =&gt; "international" );
sub type {
    my ( $ref, $type ) = @_;

    if ( defined( $type )) {    # 'set' operation
        if ( !grep /$type/, %types ) {
            $^W and warn "Unknown type $type\n";
            $type = "unknown";
        } else {
            $type = $type;
        }
    } else {
        $type = $ref-&gt;{ 'type' } || "unknown";
    }

    # convert from number to word if necessary
    if ( defined( $types{ $type })) {
        $type = $types{ $type };
    }

    # don't like unknowns
    if ( $type eq "unknown" and defined( $ref-&gt;{'number'} )) {
        $ref-&gt;{'number'} =~ /\+/ ?
          $type = "international" :
            $type = "national";
    }

    $ref-&gt;{'type'} = $type;     # every day is a "set" day :)

    $type;
}

package main;

my $debug = $ENV{DEBUG}||0;
my $vod = new MyVodafone( debug =&gt; $debug );

GetOptions( "debug!" =&gt; \$debug ) or die $!;

my $port;
my $LOCKFILE;
my @PhoneBook;
my %PhoneBook;
my %Lockables;
my %Lockable;
my @PhoneBookSources;
my ( $PowerSource, $BatteryLevel, $Status, $RSSI, $BER, $PhoneBookStorage );
my $reply;

sub openphone {
    my $device = shift;
    my $origdev = $device;      # for errors

    die "Please specify the port the phone is on!" unless $device;

    $device =~ s{^/dev/}{};
    $device =~ m{/} &amp;&amp; die "too many / in $origdev\n";

    $LOCKFILE = "/var/tmp/LCK..$device";

    # stat the lockfile, open it, check the process ID, check if
    # the process is still running, nuke the lockfile if it's not.
    if ( -f $LOCKFILE ) {
        if ( open( LOCKFILE, "&lt;$LOCKFILE" )) {
            my $pid = &lt;LOCKFILE&gt;;
            chomp $pid;
            if ( kill 0, $pid ) {
                # process still running
                die "$origdev is locked by process $pid";
            }
            close( LOCKFILE );
            unlink( $LOCKFILE );
        } else {
            die "Can't open lockfile for $origdev: $!";
        }
    }

    $port = new Device::SerialPort( "/dev/$device", 1, $LOCKFILE );

    if ( !$port ) {
        die "Failed to open port: $!\n";
    }

    # now set up the port
    $port-&gt;baudrate( 115200 );  # actually virtual speed
    $port-&gt;parity( "none" );
    $port-&gt;databits( 8 );
    $port-&gt;stopbits( 1 );
    $port-&gt;handshake( "rts" );

    $port-&gt;alias( "phone" );

    $port;
}

sub closephone {
    if ( ref( $port )) {
        $port-&gt;close;
    }
    unlink $LOCKFILE;
    undef $port;
}

# used for a number of things
my %storage = (
			   'DC' =&gt; 'ME dialed calls list',
			   'EN' =&gt; 'SIM (or ME) emergency number',
			   'FD' =&gt; 'SIM fixed dialing number phonebook',
			   'MC' =&gt; 'ME missed (unanswered received) calls list',
			   'ME' =&gt; 'ME phonebook',
			   'ON' =&gt; 'SIM (or ME) own numbers (MSISDNs) list',
			   'OW' =&gt; 'own telephone numbers',
			   'RC' =&gt; 'ME received calls list',
			   'SM' =&gt; 'SIM phonebook',
			   'TA' =&gt; 'TA (data card) phone book',
			   'MD' =&gt; 'last number redial memory',
			   'LD' =&gt; 'SIM last-dialing phonebook',
			   'MT' =&gt; 'combined ME and SIM phonebook',
			   'DC' =&gt; 'dialed calls list',
			   'RC' =&gt; 'received calls list',
              );

sub phonebooksource {
    my $source = shift;
    my $oldsource = askphone( "+CPBS?" );
    my $reply;

    if ( !defined( $oldsource )) {
        $oldsource = "flarp";   # magic!
        $reply = "ERROR";
    }

    if ( defined( $source )) {
        # upcase
        $source = uc( $source );

        # requote
        $source =~ s/"//g;
        $source = qq("$source");
        if ( $oldsource ne $source ) {
            $reply = askphone( "+CPBS=$source" );
            if ( !defined( $oldsource )) {
                $^W and warn "Unable to switch phonebook to $source\n";
            } else {
                $oldsource = $source;
            }
        }
    }

    defined( $oldsource ) and $oldsource =~ s/"//g;

    !defined( $storage{ $oldsource }) and $oldsource = undef;

    $oldsource;
}

sub getphonebook {
    my $verbose = shift;

    # How many entries?
    # AT+CPBR=?
    # +CPBR: (1-75),20,12
    writeport( $port, "AT+CPBR=?\r" );
    my ( $ok, $usage ) = checkreply( $port, "getting phonebook" );
    my $max;
    if ( $ok ) {
        ( $max ) = $usage =~ m/^\+CPBR: \(1\-(\d+)\).+/m;
        $max or $ok = 0;
    }

    if ( !$ok ) {
        $^W and warn "Failed to query phonebook size.\n";
        return 0;               # zero entries in THIS phonebook...
    } else {
        print "($max entries)";
    }

    # 1-n format only allows us to snag the first 20 entries, which is
    # kinda lame since the SH888 allows 75 entries...
    foreach my $i ( 1..$max ) {
        my $reply;
        writeport( $port, "AT+CPBR=$i\r" );
        ( $ok, $reply ) = checkreply( $port, "getting phone entries" );

        # DEBUG
        #$reply =~ s/\r/\\r/g;
        #$reply =~ s/\n/\\n/g;
        #print "$i: $reply\n";

        # $reply format:
        # 29: AT+CPBR=29\r\r\n+CPBR: 29,"1745",129,"!INFO"\r\n\r\nOK\r\n
        # or, for a blank,
        # 35: AT+CPBR=35\r\r\nOK\r\n
        # Let's parse that!
        my ( $pos, $num, $type, $tag ) =
          $reply =~ m/^\+CPBR: ($i),"(.+?)",(\d+),"(.+)?"\r?$/m;

        # If there's something there, load it into the phonebook.
        if ( defined( $pos )) {
            $PhoneBook[ $pos ] = new PhoneBookEntry( $tag, $num, $type );
        } else {
            $PhoneBook[ $i ] = undef; # make sure there's nothing there
        }
        print "." if $verbose;
    }

    return $max;
}

# putting a phonebook entry:
# +CPBW=index,"number",type,"text"
# +CPBW=,"number",type,"text" writes to first free location
# I guess +CPBW=index trashes whatever's at &lt;index&gt;

# The doco says your ranges are (1-100),20,(128-255),18; my SIM says
# (1-75),20,(128-255),12, which I'm guessing means you can have up to
# 20 digits in a phone number and 12 characters in a name. The phone
# itself says (1-99),20,(128-255),24. Might be worth investigating
# before dinking with the phonebook.

# Potentially useful stuff for autosensing the phone type:
sub getinfo {
    my $Manufacturer = askphone( "+CGMI" ) || "Unknown manufacturer";

    # Both my phones are ERICSSON&lt;SP&gt;&lt;SP&gt;

    my $Model = askphone( "+CGMM" ) || "Unknown model";

    # My phones:
    # GA628 = 1050702&lt;SP&gt;&lt;SP&gt;&lt;SP&gt;
    # SH888 = 1100801&lt;SP&gt;&lt;SP&gt;&lt;SP&gt;

    my $Revision = askphone( "+CGMR" ) || "Unknown revision";

    # My phones:
    # GA628: 9801141553,A,B,I,J,T2
    # SH888: 990212 1544 CXC125131
}

# AT+CFUN=0 will turn off the phone if the keylock is inactive...

my $STATUS_UNKNOWN = 2;
my @status = ( "Ready",
			   "Unavailable",
			   "Unknown",
               "Ringing",
               "Call in progress",
               "Asleep" );

my @rssi;

# RSSI map:
# 0 -&gt; -113dBm or less
# 1 - 30 -&gt; -111 dBm to -53 dBm in twos
# 31 -&gt; 51 dBm or greater
for my $i ( 0 .. 31 ) {
    $rssi[ $i ] = -113 + 2 * $i;
}

sub getstatus {
    # get various status stuff from the phone

    # battery level
    my $reply = askphone( "+CBC" );

    if ( defined( $reply )) {
        ( $PowerSource, $BatteryLevel ) = $reply =~ m/(\d+),(\d+)/m;
    } else {
        $PowerSource = -1;
        $BatteryLevel = -1;
    }

    # phone status
    $reply = askphone( "+CPAS" );
    $reply = $STATUS_UNKNOWN unless defined( $reply );
    ( $Status ) = $reply =~ m/(\d+)/m;
}

# signal quality
sub getsquelch {
    my $reply = askphone( "+CSQ" );
    if ( defined( $reply )) {
        ( $RSSI, $BER ) = $reply=~ m/(\d+),(\d+)/m;
    }
}

sub getphonebooks {
    # Let's find out what our options are.
    $reply = askphone( "+CPBS=?" );
    if ( defined( $reply )) {
        @PhoneBookSources = eval( $reply ); # evil!
    } else {
        @PhoneBookSources = (); # Wow. You're SO doomed.
    }

    # save this
    $PhoneBookStorage = phonebooksource;

    #  if ( defined( $PhoneBookStorage )) {
	# Ask each phonebook for its limits.
	for my $p ( @PhoneBookSources ) {
        phonebooksource( $p );
        $PhoneBook{ $p } = askphone( "+CPBW=?" );
	}
	# restore original setting
	phonebooksource( $PhoneBookStorage );
    #  }
}

# Select terminal character set: +CSCS
# When dialling, adding a ";" indicates that it's a voice call.
# ATDL should be redial last
# ATD=ME1 should dial entry 1 in the ME phonebook
# ATD=SIM1 should dial entry 1 in the SIM
# Can't say I've got ME or SIM to work.

# SMS stuff
# +CMGD=n delete message n
# +CMGF=n change format. seems to be nailed to "PDU mode", n = 0.
# +CMGL=n list messages
#       n = 0 received, unread
#       n = 1 received, read
#       n = 2 stored, unsent
#       n = 3 stored, sent
#       n = 4 all messages
# +CMGR=n read message n
# +CMGS=len&lt;CR&gt;msg&lt;EOT&gt;|&lt;ESC&gt; send message
#       len = "number of octest coded in the TP layer data unit". Eh.
#       msg = That pesky PDU format again
#       EOT (C-z) means send the message, ESC means cancel it.
# +CMGW=len[,stat]&lt;CR&gt;msg&lt;EOT&gt; store message [stat always = 2? it's optional]
# +CMSS=n send message n from storage
# +CMTI "unsolicited result" which I guess means it can happen at any time.
#       incoming mail, basically.
# +CNMI=mode,mt,bm,ds,bfr configures CMTI hocus.
#       mode = 0 buffer codes in modem
#              1 discard when modem is "reserved"
#              2 buffer while reserved, flush when reservation ends.
#       mt   = 0 No SMS-DELIVER
#            = 1 SMS-DELIVER
#       bm   = 0 No CBM ???
#       dt   = 0 No SMS-STATUS-REPORTS
#       bfr  = 0 mode 1, 2 flush to the computer
#            = 1 mode 1, 2 clear data
#          I'm confused now.
# +CPMS="m1","m2"
#       Memory 1 storage is used to list, read and delete messages (+CMGL,
#       +CMGR and +CMGD) whilst memory 2 is used to write and send messages
#       (+CMGW and +CMSS).
# +CSCA=num,type Service centre number &amp; optional type. DON'T TOUCH.
# +CSMS=mt,mo,bm SMS support. mt=incoming, mo=outgoing, bm=broadcast

# Lockable items
%Lockable =
  (
   "PS" =&gt; "lock phone to SIM",
   "SC" =&gt; "lock SIM card",
   "AO" =&gt; "bar all outgoing calls",
   "OI" =&gt; "bar all outgoing int'l calls",
   "OX" =&gt; "bar all outgoing int'l call except to home",
   "AI" =&gt; "bar all incoming calls",
   "NT" =&gt; "bar all incoming calls from numbers not stored in TA memory",
   "NM" =&gt; "bar all incoming calls from numbers not stored in ME memory",
   "NS" =&gt; "bar all incoming calls from numbers not stored in SIM memory",
   "NA" =&gt; "bar all incoming calls from numbers not stored in any memory",
   "IR" =&gt; "bar all incoming calls when roaming abroad",
   "AB" =&gt; "all barring services",
   "AG" =&gt; "all outgoing barring services",
   "AC" =&gt; "all incoming barring services",
   "FD" =&gt; "SIM fixed dialing memory",
   "WNL" =&gt; "network lock",
   "CS" =&gt; "lock control surface (e.g. keyboard)",
   "P2" =&gt; "SIM PIN2",
   "PN" =&gt; "network personalization",
   "PU" =&gt; "network subset personalization",
   "PP" =&gt; "service provider personalization",
   "PC" =&gt; "corporate personalization"
  );

sub getlocks {
    my ( $reply );
    $reply = askphone( "+CLCK=?" );
    $reply = "()" unless defined( $reply );

    # Stock up the hash
    for my $i ( eval( $reply )) {
        $Lockables{$i} = askphone( "+CLCK=\"$i\",2" );
    }
}

sub resetphone {
    my ( $reply, $ok );
    writeport( $port, "ATZ\r" );
    ( $ok, $reply ) = checkreply( $port, "resetting phone" );
    $reply ||= "unknown error";
    if ( !$ok ) {
        closephone;             # make sure to at least clean up.
        die "Failed to reset phone ($reply)\nStopped";
    }
}

# Convenience routines, which should be more robust.
sub askphone {
    my $string = shift;
    my $errorok = shift;

    $debug and print STDERR "Writing AT$string\n";
    writeport( $port, "AT" . $string . "\r" );
    my ( $ok, $reply ) = checkreply( $port, "doing $string", $errorok );
    if ( $ok ) {
        $string = quotemeta( $string );
        $debug and print STDERR "quotemeta: $string\n";
        #$string =~ s/(\\\=?)\\\?/($1\\?)?/; # a little wizardry ;)
        $debug and print STDERR "raw reply: $reply\n";
        # Unecho the thing
        $reply =~ s/^AT${string}\s*\n//s;
        $debug and print STDERR "unechoed: $reply\n";
        # some commands present the answer as "+FOO: answer", and there can be a
        # few of them over multiple lines.
        $string =~ s/(\\[=?])+.*$//;
        $reply =~ s/\s*${string}:\s*/ /sg; # leave a space, though
        $reply =~ s/\s*\+CCFC:\s*/ /sg; # special case, cos I'm lazy.
        $debug and print STDERR "removing matching $string: $reply\n";
        # nuke trailing OK
        $reply =~ s/(\r\n)*OK(\r\n)+//s;
        $reply =~ s/^\s+//s;

        $debug and print STDERR "Returning&gt;&gt;$reply\n";
    } else {
        $reply = undef;
    }

    $reply;
}

sub writeport {
    my $p = shift;
    my $str = shift;
    my $len = length( $str );   # not used

    $p-&gt;write( $str );
    while ( !($p-&gt;write_drain)[0] ) {
    }
    ;
    print STDERR "&gt;&gt;&gt; $str\n" if $debug;
}

sub checkreply {
    my $p = shift;
    my $context = shift;
    my $errorok = shift;
    my $reply = "";
    my $ok;
    my $now = time;

    while ( 1 ) {
        my $s = $p-&gt;input;
        print STDERR "&lt;&lt;&lt; $s\n" if $debug &amp;&amp; $s;
        $reply .= $s;
        last if ( $reply =~ m/^(OK|ERROR)\r?$/m ); # wait for answer
        if ( time &gt; ( $now + 60)) { # don't wait more than 1 minute
            $^W and warn "Timed out in checkreply ($context)\n";
            last
        }
    }

    $ok = ( $reply =~ m/^OK\r?$/m ) ? 1 : 0;
    if ( $errorok ) {
        $ok = 1;
    }

    if ( wantarray ) {
        return ( $ok, $reply );
    } else {
        return $ok;
    }
}

# START OF CODE: WHERE IT ALL HAPPENS
$| = 1;
print STDERR "Opening phone on $ARGV[0]..." if $debug;
openphone( $ARGV[0] );
#resetphone();
print STDERR "done\nDisabling quiet mode..." if $debug;
askphone( "Q0" );               # make sure we're getting answers
print STDERR "done\n" if $debug;
#resetphone #fails on the GA628

# identify phone
# print "Identifying phone...";
# getinfo;
# printf "Mf./Mod./Rev.: $Manufacturer / $Model / $Revision\n";

# # check status
# getstatus;
# printf "Phone status: %s.\n", $status[ $Status ];
# printf "Battery: %s, %d%% charged.\n",
#   $PowerSource == 0 ? "in use" : "not in use", $BatteryLevel;

# getsquelch;
# if ( defined( $RSSI )) {
#   printf "Signal Quality: %d dBm (%g%%)", $rssi[ $RSSI ], ($RSSI * 100 / 31);
# }
# if ( defined( $BER )) {
#   $RSSI &amp;&amp; print " with ";
#   printf( "BER %d", $BER );
# }
# ( $RSSI || $BER ) and print "\n";

# getlocks;
# for $item ( keys %Lockables ) {
#   printf( "%s: %s\n", $Lockable{$item}||$item,
#   		  $Lockables{ $item }||"[no reply]");
# }

goto sms_dump;
getphonebooks;
for my $pb ( @PhoneBookSources ) {
    phonebooksource( $pb );
    $pb =~ s/"//g;
    printf "Reading phonebook from %s ", $storage{ $pb } || $pb;
    my $max = getphonebook( 1 );
    print "done.\n";

    my ( $numw, $namew );

    # Get the formatting widths
    if ( defined( $PhoneBook{ $pb } )) {
        ( $numw, $namew ) =
          $PhoneBook{ $pb } =~ /\(\d+\-\d+\),(\d+),\(\d+\-\d+\),(\d+)/;
    }

    $numw ||= 0;
    $namew ||= 0;

    for ( my $i = 0; $i &lt; $max; $i++ ) {
        next unless defined( $PhoneBook[ $i ]);
        printf( "%-${namew}s|%${numw}s|(%s)\n",
                $PhoneBook[ $i ]-&gt;name||"Unknown",
                $PhoneBook[ $i ]-&gt;number||"",
                $PhoneBook[ $i ]-&gt;type||"" );
    }
}

print "===============\n";

sms_dump:

my $csv = new Text::CSV_XS;

# find out my number!
my $mynumber = askphone( "+CNUM" );
if ( $csv-&gt;parse( $mynumber )) {
    ( undef, $mynumber, undef ) = $csv-&gt;fields;
} else {
    die "can't parse $mynumber\n";
}

my $mbox = new Mail::Folder( 'mbox', $ENV{HOME} . '/Mail/phone',
                             Create =&gt; 1 );
my $count = $mbox-&gt;qty();
my %messageid;

for my $i ( 1..$count ) {
    my $msg = $mbox-&gt;get_message( $i ) or die $!;
    my $head = $msg-&gt;head();
    my $msgid = $head-&gt;get( "Message-ID" );
    chomp( $msgid );
    $messageid{$msgid} = 1;
}

print STDERR "SMS DUMP\n" if $debug;
# Get a list of all SMS messages
#$reply = askphone( "+CSCS=\"UCS2\"" );
$reply = askphone( "+CMGF=1" );

for my $source ( "ME", "SM" ) {
    $reply = askphone( "+CPMS=\"$source\"" );

    my @mboxes = ( "ALL" ); # ( "STO UNSENT", "STO SENT", "REC READ" );
    for my $mb ( @mboxes ) {
        $reply = askphone( "+MMGL=\"$mb\"", "ok" );

        my @msgs;
        while ( $reply ) {
            my ( $msg, undef, undef, $rest ) =
              $reply =~
                /(\d+,"(REC READ|STO (UN)?SENT)","[^"]+")(.*)$/s;
            last unless $msg;
            push @msgs, $msg;
            $reply = $rest;
        }

        my @count = reverse map { s/^(\d+),.*$/$1/ ? $_ : "" } @msgs;

        for my $msg ( sort @count ) {
            if ( $msg !~ /\d+/ ) {
                print STDERR "WTF $msg\n";
                next;
            }
            $reply = askphone( "+MMGR=$msg" );
            my $pdu = new GSM::SMS::PDU;
            my $c = 1;
            if ( defined( $reply )) {
                $reply =~ s/STO SENT/SENT/s;
                $reply =~ s/REC READ/READ/s;
                $reply =~ s/STO UNSENT/UNSENT/s;
                print STDERR "Raw reply for $msg: $reply\n" if $debug;
                my @replies = split( /[\r\n]+/, $reply, 2 );
                my ( $idx, $data ) = ( shift @replies, shift @replies );
                my ( $type, $number, $timestamp ) = split( /,/, $idx, 3 );

                if ( $csv-&gt;parse( $idx )) {
                    ( $type, $number, $timestamp ) = $csv-&gt;fields;
                }

                if (!defined( $data )) {
                    print STDERR "No data for message $msg\n" if $debug;
                    next;
                }

                # stray newlines... this might not actually be right
                $data =~ s/\r\n//gs;

                my %msg;

                $msg{raw} = $data;
                $msg{number} = $msg;

                $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)",
                                       localtime( 0 ));
                $msg{zone} = 0;
                $number =~ s/"//g if $number;

                if ( $number =~ / / ) {
                    print STDERR "Gaaah. We don't do multi-recipients\n";
                    $number =~ s/ .*$//;
                }

                # non-PDU version
                if ( $type =~ /READ|UNREAD/) {
                    die "No timestamp in $idx" unless $timestamp;
                    $msg{from} = $number;
                    $msg{to} = $mynumber;
                    $msg{body} = $data;
                    $msg{length} = length( $msg{body} );
                    $msg{delete} = 1;
                    my ( $y, $mt, $d, $h, $m, $s ) =
                      $timestamp =~ m|(\d+)/(\d+)/(\d+),(\d+):(\d+):(\d+)|;

                    $msg{unixtime} = timelocal( $s, $m, $h, $d, $mt - 1, $y );
                    $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)",
                                           localtime( $msg{unixtime} ));
                } elsif ( $type =~ /SENT/) {
                    $msg{from} = $mynumber;
                    $msg{to} = $number;
                    $msg{body} = $data;
                    $msg{length} = length( $msg{body} );
                    $msg{delete} = 0;
                    $msg{unixtime} = 0;
                } elsif ( $type =~ /^\d+$/ ) {
                    if ( $type &lt;= 1 ) {
                        my $decoded = $pdu-&gt;SMSDeliver( $data );
                        die "can't decode $data" unless $decoded;
                        print STDERR Dumper( $decoded ) if $debug;

                        $msg{from} = $decoded-&gt;{'TP-OA'};
                        $msg{to} = $mynumber;

                        my ( $y, $mt, $d, $h, $m, $s, $z ) =
                          $decoded-&gt;{'TP-SCTS'} =~ /^(..)(..)(..)(..)(..)(..)(..)$/;
                        $y += 100; # stuuuuuuupid
                        $mt--;
                        $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)",
                                               $s, $m, $h, $d, $mt, $y );
                        $msg{unixtime} = mktime( $s, $m, $h, $d, $mt, $y );
                        $msg{zone} = $z||0;
                        $msg{length} = $decoded-&gt;{'TP-UDL'};
                        $msg{body} = ( $decoded-&gt;{'TP-UD'}||"" );
                        $msg{delete} = 1;
                    } else {
                        my @decoded = $pdu-&gt;SMSSubmit_decode( $data );
                        # $VAR1 = [
                        #           '+353862105113',
                        #           '11', # pdu type (flags, not important)
                        #           '00', # protocol identifier (should be 00)
                        #           undef, # user data header
                        #           'When and where?^@'
                        #         ];
                        $msg{from} = $mynumber;
                        $msg{to} = $decoded[0];
                        $msg{body} = $decoded[4];
                        $msg{length} = length( $msg{body} );

                        # need to pull message status for this, which we can't
                        # do via the AT interface.
                        $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S GMT",
                                               gmtime( 0 ));
                        $msg{unixtime} = 0;
                        $msg{zone} = 0;
                        $msg{delete} = 0;
                    }
                }

                # do the msgnum dance to try and get this stuff in sequence
                if ( $msg{unixtime} == 0 ) {
                    # see if we have a date range to bracket our search
                    my ( $less, $greater ) = ( 0, 0 );
                    for my $check ( sort keys %msgnum ) {
                        if ( $check &lt; $msg{number} ) {
                            $less = $check;
                        } elsif ( $check &gt; $msg{number} ) {
                            $greater = $check unless $greater;
                        }
                    }

                    if ( $ENV{DEBUG}||0 ) {
                        print STDERR "Date range: $msgnum{$less} to " .
                          ( $msgnum{greater}||time) . "\n";
                    }

                    if ( $msgnum{$less} ) {
                        $vod-&gt;login( $config{number}, $config{password} )
                          unless $vod-&gt;{loggedin};
                        my $bits = get_sms_after( $msgnum{$less}, $msg{to} );
                        if ( defined( $bits-&gt;{date} )) {
                            $msg{unixtime} = $bits-&gt;{date};
                            $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)", localtime( $msg{unixtime} ));
                        } else {
                            print "No date found for $msg{to} after " . scalar( localtime( $msgnum{$less} )) . ", bailing\n";
                            last;
                        }
                    }
                }

                $msgnum{$msg{number}} = $msg{unixtime};

                $msg{body} =~ s/\x00$//;

                my $message = new Mail::Internet;
                $message-&gt;body( [ $msg{body} . "\n" ]);
                $message-&gt;head()-&gt;header_hashref(
                                                 {
                                                  "From" =&gt; $msg{from},
                                                  "To" =&gt; $msg{to},
                                                  "Date" =&gt; $msg{date},
                                                  "Content-Length" =&gt; $msg{length},
                                                  "X-Zone" =&gt; $msg{zone},
                                                  "X-Raw" =&gt; $msg{raw},
                                                  "Message-ID" =&gt;
                                                  sprintf( "&lt;%05d.SMS\@waider.ie&gt;", $msg{number} ),
                                                  "X-MsgNum" =&gt; $msg{number},
                                                 }
                                                );

                if ( !defined( $messageid{sprintf( "&lt;%05d.SMS\@waider.ie&gt;", $msg{number})})) {
                    $mbox-&gt;append_message( $message );
                    $messageid{sprintf( "&lt;%05d.SMS\@waider.ie&gt;", $msg{number})} = 1;
                }

                # decide to delete:
                # leading @ is mms indicator, maybe
                if ( $msg{length} == 0 or $msg{body} =~ /^@/ ) {
                    $msg{delete} = 0;
                }

                if ( $msg{delete} and !$ENV{DEBUG}) {
                    my $rep = askphone( "+CMGD=$msg", "ok" );
                }
            } else {
                print STDERR "no reply to msg $msg\n" if $debug;
            }
        }
    }
}
$mbox-&gt;sync();

if ( !$ENV{DEBUG} and open( LAST, "&gt;$ENV{HOME}/.vodafone/lastsms" )) {
    my @dates = sort { $a &lt;=&gt; $b } values( %msgnum );
    print LAST $dates[-1] . "\n";
    close( LAST );
}

# don't do this. it'll happen automatically when the variable is destroyed.
#$mbox-&gt;close();

END {
    closephone if $port;
}

sub get_sms_after {
    my $time = shift;
    my $number = shift;
    #my $intnumber = $number;

    die "no number???" unless $number;

    print STDERR "searching for $number after $time\n" if $ENV{DEBUG};

    my $get = $firstbill;
    my $last = "";

    while ( 1 ) {
        my ( $bill, $bdate ) = $vod-&gt;billing( $config{service}, $get );

        # "can't get bill for $get" unless $bill;
        if ( !defined( $bill )) {
            if ( $get eq $firstbill and $get ne "unbilled" ) {
                $get = "unbilled";
                next;
            }

            print STDERR "out of bills ($get)\n" if $ENV{DEBUG};
            last;
        }

        $bdate =~ s@/@@;
        $firstbill = $bdate if $firstbill eq "first";
        $last = $get;

        my $old = time;
        my $new = 0;

        for my $page ( @{$bill} ) {
            for my $item ( @{$page} ) {
                my $date = $item-&gt;{date};

                print STDERR $item-&gt;{date} . " " . $item-&gt;{to} . " " .
                  $item-&gt;{type} . "\n" if $ENV{DEBUG};

                if (( $number eq "any number" or $item-&gt;{to} eq $number )
                    and $item-&gt;{type} eq "SMS" ) {
                    if ( $date &gt; $time ) {
                        return $item;
                    }
                }

                if ( $number =~ /^\+/ ) {
                    my $intnum = $number;
                    $intnum =~ s/\+353/0/;
                    $intnum =~ s/\+1/001/;

                    if ( $item-&gt;{to} eq $intnum and $item-&gt;{type} eq "SMS" ) {
                        if ( $date &gt; $time ) {
                            return $item;
                        }
                    }
                }

                if ( $old &gt; $date ) {
                    $old = $date;
                }
                if ( $new &lt; $date ) {
                    $new = $date;
                }
            }
        }

        if ( $get eq $firstbill and $get ne "unbilled" ) {
            $get = "unbilled";
        } elsif ( $get eq "unbilled" ) {
            last;
        } else {
            my ( $m, $y ) = ( substr( $bdate, 0, 2 ), substr( $bdate, 2 ));

            $m++;
            if ( $m &gt; 12 ) {
                $m = 1;
                $y++;
            }

            $get = sprintf( "%02d/%02d", $m, $y );
        }
    }

    if ( !$lastbill ) {
        $lastbill = $last;
    }

    return {};
}
</pre></body></html>