#!/usr/bin/perl #--------------- # # Sambru* - a free phonebook synch utility for Samsung # SCH-6100 and SCH-8500 cellular phones # # (*Samsung Backup and Restore Utility) # # Copyright (C) 2000 Eric Sandeen (eric_sandeen@bigfoot.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # #--------------- use FileHandle; use IPC::Open2; use Getopt::Long; #----------------------- # Constants - this is all you should need to edit $cu_path = "/usr/bin/cu"; $port = "/dev/ttyS0"; # Default $baud_rate = "19200"; # Can we run at 57600? #----------------------- # Other constants $version = 0.2; # Max values the phone can handle # Could get these from AT#PBOKR=? - maybe? $max_entries = 229; $max_ringer_number = 12; $max_name_length = 12; $max_number_length = 32; $max_label = 6; $max_fields = 15; # loc, name, ringer + (6 numbers * 2 fields/number) $max_phone_nums = 6; @num_to_label=("", "HOME", "WORK", "CELL", "PAGER", "FAX", ""); # Hash for entry types %label_to_num = ("HOME", 1, "WORK", 2, "CELL", 3, "PAGER", 4, "FAX", 5); #----------------------- # Begin code #----------------------- GetOptions ("format:s", # Records format (default RAW, or vcard) "get", # Get information from phone "put", # Put information into phone "file:s", # File to read or write from (else STDIN/STDOUT) "port:s", # Serial device (default /dev/ttyS0) "help" # Show help usage ) or Show_Usage(); if ((!$opt_put && !$opt_get) || ($opt_put && $opt_get)) { print "\nERROR - must specify either --get OR --put\n"; Show_Usage(); } if ($opt_help) { Show_Usage(); } # Open port and put the phone in data mode open_phone(); if ($opt_get) { if ($opt_file) { # Open file & Redirect STDOUT open(FILE, ">$opt_file"); *STDOUTSAVE = *STDOUT; *STDOUT = *FILE; } # Read each entry from phone, format, and print for ( $entry_num = 1; $entry_num <= $max_entries; $entry_num++) { print STDERR "Reading location $entry_num/$max_entries\r"; $entry = do_command("AT#PBOKR=$entry_num"); if ($entry) { if ($opt_format eq "vcard") { $entry = convert_to_vcard($entry); } print "$entry\n"; } } print STDERR "\nDone \n"; if ($opt_file) { # Close file & Restore STDOUT *FILE = *STDOUT; close(FILE); *STDOUT = *STDOUTSAVE; } } if ($opt_put) { if ($opt_file) { # Open file & Redirect STDOUT open(FILE, "<$opt_file"); *STDINSAVE = *STDIN; *STDIN = *FILE; } # Read each entry from file, and load into phone # WARNING - no checking for duplicate locations, so may overwrite # some previously loaded entries. while ( !eof(STDIN) ) { if ($opt_format eq "vcard") { $entry = convert_from_vcard(); } else { $entry = ; } chomp ($entry); @fields = split(/,/,$entry); # Split entry into array # Sanity check what we're writing to the phone if (!check_entry(@fields)) {; do_command("AT#PBOKW=$entry"); } } if ($opt_file) { # Close file & Restore STDOUT *FILE = *STDIN; close(FILE); *STDIN = *STDINSAVE; } } close_phone(); ################################################ # Subroutines ################################################ # Get phone's attention, set echo off, set data mode sub open_phone { # Start the 'cu' program and get handles to in, out # NEED TO DO SOME ERROR CHECKING HERE! open2(*FROM_PHONE, *TO_PHONE, "$cu_path -l$port -s$baud_rate 2>&1"); # Init phone, turn off command echo do_command("AT"); do_command("ATE0"); # Make sure this is a phone we can talk to... $model = do_command("AT+GMM"); if ($model =~ /6100|8500/) { print STDERR "Found phone model $model\n"; } else { die ("Whoa, buddy, can't talk to this $model phone!"); } $battery = do_command("AT+CBC?"); print STDERR "Battery level at $battery\n"; # Put it into data mode do_command("AT#PMODE=1"); } # end data mode, set echo on, kill cu sub close_phone { do_command("AT#PMODE=0"); # #PMODE=0 gives us 2 "OK" lines for some reason, flush it... read_line(); # Turn command echoes back on do_command("AT#E1"); # end cu with "~." print TO_PHONE "\r\n~.\r\n"; close FROM_PHONE; close TO_PHONE; die ("\n"); } # Sends a command to the phone, and returns result # (result is any information before "OK", with original # command stripped out of the line) sub do_command { my ($command) = @_; print TO_PHONE "$command\r\n"; $result = get_result(); die ("Got ERROR from phone on command $command") if ($result =~ /ERROR/); # Strip down to result # Remove original command, error, ok, etc. #$result =~ s/^.+:\s//; $result =~ s/.*:\s|OK|ERROR//g; return $result; } # Retrieves lines until it gets an "OK" or an "ERROR" # and then returns all the lines in one string... sub get_result { $lines = read_line(); while ( ( $lines !~ /OK/ ) && ( $lines !~ /ERROR/ ) ) { $lines .= read_line(); } return $lines; } # Gets a single line from the phone, dies if it's empty # Strips any newline monkey business as well sub read_line { $_ = ; $_ || die("got eof on serial"); s/[\r\n]+$//; return $_; } ############################## # Record conversion routines # ############################## sub convert_to_vcard { my ($entry) = @_; @fields = split(/,/,$entry); # Split entry into array $fields[1] =~ s/"//g; # Strip quote marks from name $record = "BEGIN:VCARD\n"; $record .= "TITLE:$fields[0]\n";# (speed dial) $record .= "FN:$fields[1]\n"; # (name) $record .= "ORG:$fields[2]\n"; # (ringer) for ($x=3; $x<=14; $x += 2) { # Process each phone number if ($fields[$x]) { # If we have an entry $type = $num_to_label[$fields[$x]]; $number = $fields[$x+1]; $number = beautify_number($number); if ($type) { # If there is a type $record = $record . "TEL;$type:$number\n"; } else { $record = $record . "TEL:$number\n"; } } } $record = $record . "END:VCARD\n\n"; return $record; } sub convert_from_vcard { # This is the cheesy bit: We use "Title" for the storage location, # and "ORG" for the ringer type (may be blank), for now. # Also, speed dial is set by first phone number encountered, no # way to control that for now... # # This assumes a decently formatted vcard entry... # Suck in the whole vcard record # NOTE: [\r\n] to take care of DOS format file (double check this...) $vcard = ""; while ( !eof(STDIN) && $vcard !~ /END:VCARD/i ) { $vcard .= ; } $entry = ""; # Get location $vcard =~ /TITLE:(.+)[\r\n]$/mi; $entry .= "$1"; # Get name $vcard =~ /FN:(.+)[\r\n]$/mi; $name = $1; $entry .= ",\"$1\""; # Get ringer # Save us from no ringer type... if ($vcard =~ /ORG:(.+)[\r\n]$/mi) { $entry .= ",$1"; } else { $entry .= ",0"; } # Now we need to get up to 6 phone numbers # In the vcard, these look like: TEL;WORK: for ($slot_num = 1; $slot_num <= $max_phone_nums; $slot_num++) { $vcard =~ /TEL\;*(.*):(.+)[\r\n]$/mgci; $label = $1; $phone_number = $2; # Strip "( ) -" $phone_number =~ s/[()-]//g; # If we didn't find another label & number, we're done. if ($label eq "" && $phone_number eq "") { last; } # If there's a label, convert label to upper case # and get the right number. if ($label eq "") { $label = 6; # No label (6 == none) } else { $label =~ s/[a-z]/[A-Z]/g; $label = $label_to_num{$label}; } $entry .= ",$label,$phone_number"; } # suck up trailing empty lines while ( eq "\n") {} return $entry; } # Turns XXXXXXXXXX into (XXX)XXX-XXXX # and XXXXXXX into XXX-XXXX sub beautify_number { my ($number) = @_; $length = length($number); if ($length > 4) { substr($number, $length-4, 0) = "-"; } if ($length >= 10) { substr($number, $length-7, 0) = ")"; substr($number, $length-10, 0) = "("; } return $number; } sub Show_Usage { print "\nSamBRU v.$version - Phone book utility for Samsung cell phones\n"; print " models SCH-6100 and SCH-8500\n\n"; print "Usage: sambru :\n"; print "\n"; print "options:\n"; print " --get get data from phone\n"; print " --put load data into phone\n"; print " --format format of input or output data (default raw)\n"; print " --file filename for data input/output (default STDIN/STDOUT)\n"; print " --port device to which phone is attached (default /dev/ttyS0)\n"; print " --help show usage\n"; die "\n"; } sub check_entry { my (@fields) = @_; # Ok, this is really messy. Should be cleaned up... # Basic rules: # first field must be between 1 and max_entries # Second field must be quoted chars, up to 12 chars # Third field must be between 0 and max_ringers # remaining fields are label/number pairs, up to 6 of them # labels must be between 1 and max_lable # numbers must be max 32 chars, of [0123456789*#pT] # Check entry number if ($fields[0] > $max_entries) { print "Error on entry $fields[0]:\tInvalid location ($fields[0])\n"; return 1; } # Check number of fields if (@fields > $max_fields) { print "Error on entry $fields[0]:\tToo many fields (@fields)\n"; return 1; } # Check name entry for quotes if ($fields[1] !~ /^\".*\"$/) { print "Error on entry $fields[0]:\tName must be quoted\n"; return 1; } # Check name entry for length - SHOULDN'T WE JUST SHORTEN IT? if (length($fields[1]) > $max_name_length+2) { print "Error on entry $fields[0]:\tName too long ($fields[1])\n"; return 1; } # Check ringer type if (($fields[2] > $max_ringer_number) || ($fields[2] !~ /^\d+$/ )) { print "Error on entry $fields[0]:\tInvalid ringer number ($fields[2])\n"; return 1; } # Check phone numbers & labels for ($slot_num = 1; $slot_num <= $max_phone_nums; $slot_num++) { # Location label (i.e. 0-6 = work, home, pager, etc) $label = $fields[2*$slot_num+1]; $phone_number = $fields[2*$slot_num+2]; # If no label and no phone number, check next slot if ( !$label && !$phone_number) { next; } # If label & no number, or number & no label, error if (!$label && $phone_number) { print "Error on entry $fields[0]:\tnumber $phone_number has no label\n"; return 1; } if ($label && !$phone_number) { print "Error on entry $fields[0]:\tlabel $label has no phone number\n"; return 1; } # Check that location label is valid, and a number if ( ($label > $max_label) || ($label !~ /^\d$/) ) { print "Error on entry $fields[0]:\tInvalid location label ($label)\n"; return 1; } # Check phone number is valid if ( (length($phone_number) > $max_number_length) || ($phone_number !~ /^[\d\*\#pT]+$/) ) { print "Error on entry $fields[0]:\tInvalid phone number ($phone_number)\n"; return 1; } } return 0; }