From 82d4e2586623f74d70bfdd84093cddff510f8536 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Wed, 7 Dec 2011 17:07:16 +0100
Subject: [PATCH] Implement config and wifi networks scanning, print them and
 ask for AP

---
 hp2101nw_setup.pl | 297 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 268 insertions(+), 29 deletions(-)

diff --git a/hp2101nw_setup.pl b/hp2101nw_setup.pl
index 4b964c1..16a3a8c 100755
--- a/hp2101nw_setup.pl
+++ b/hp2101nw_setup.pl
@@ -7,18 +7,17 @@ use strict;
 use warnings;
 
 my $debug=1;
+# my $debug=0;
+our $VERSION=0.01;
+print "hp2101nw_setup.pl - version $VERSION\n";
+print "Linux configuration utility for the HP 2101nw wireless G USB print server.\n\n";
+print "(C) 2011 Reinhold Kainhofer <reinhold\@kainhofer.com>\n";
+print "License: GPL v2 or later\n\n";
 
-=head1 NAME
-
-hp2101nw_setup.pl - Configure the HP 2101nw wireless G USB print server
-
-=head1 VERSION
-
-Version 0.01
 
-=cut
 
-our $VERSION=0.01;
+our $thisboxname = "XXXXXXXX";
+$| = 1;
 
 =head1 SYNOPSIS
 
@@ -34,39 +33,51 @@ http://wiki.kainhofer.com/hardware/hp2101nw_wlan_printserver
 
 =cut
 
-print "hp2101nw_setup.pl - version $VERSION\n";
-print "Linux configuration utility for the HP 2101nw wireless G USB print server.\n\n";
-print "(C) 2011 Reinhold Kainhofer <reinhold\@kainhofer.com>\n";
-print "License: GPL v2 or later\n\n";
-
 $Data::Dumper::Indent = 1; ## no critic(ProhibitPackageVars)
 
 # data structure: VendorID, ProductID, BULK_IN_EP, BULK_OUT_EP
 my @supported_devices=(
   [0x03f0, 0xcd02]
 );
+my $var_names = {};
 
+sub progress {
+#   my $str = shift;
+  printf (@_);
+}
+sub debug {
+  if ($debug) {
+    printf (@_);
+  }
+}
+sub trim($) {
+  my $string = shift;
+  $string =~ s/^\s+//;
+  $string =~ s/\s+$//;
+  return $string;
+}
 
 
 sub device_detect () {
   my $usb = Device::USB->new();
   my $dev;
-  print "Detected devices:\n";
+  progress "Detected devices:\n";
 
   foreach (@supported_devices) {
     $dev = $usb->find_device( @$_ );
     if ($dev) {
-      printf "\t%s / ID %04x:%04x (%s: %s)\n", $dev->filename(),
+      progress ("\t%s / ID %04x:%04x (%s: %s)\n", $dev->filename(),
              $dev->idVendor(), $dev->idProduct(),
-             $dev->manufacturer(), $dev->product();
+             $dev->manufacturer(), $dev->product());
     }
   }
   if ($dev) {
-    printf "\nUsing device: %04x:%04x (%s: %s)\n\n",
-           $dev->idVendor(), $dev->idProduct(),
-           $dev->manufacturer(), $dev->product();
+    progress "\n";
+    debug ("Using device: %04x:%04x (%s: %s)\n\n",
+             $dev->idVendor(), $dev->idProduct(),
+             $dev->manufacturer(), $dev->product() );
   } else {
-    print "\tNONE\n\n";
+    progress "\tNONE\n\n";
     print "ERROR: No supported device was found...\n\n";
     print "Please connect your HP wireless USB print server to \n";
     print "the computer with the black USB cable (micro USB plug) \n";
@@ -101,26 +112,213 @@ sub device_open ($) {
 }
 
 
-
-sub request_device_config ($) {
+sub write_bulk_data ($$$) {
   my $dev = shift;
-  my $request = "@\0\x06\0XXXXXXXX9100:\n";
+  my $data = shift;
+  my $len = shift;
+  # Add the header (12 bytes): @, \0, packet length, boxname (XXXXXXXX) 
+  my $request = sprintf ("@\0%s%s%s", pack ("v", $len), $thisboxname, $data);
   if ($debug) { print "Request: ", $request, "\n"; }
-  my $res = $dev->bulk_write (1, $request, 18, 1000);
+  my $res = $dev->bulk_write (1, $request, 18, 500);
   if ($res<0) {
     printf "ERROR write bulk data (%d): %s\n", $res, $!;
   }
-  $res = $dev->bulk_read (2, my $result="", 25600, 1000);
+  return $res;
+}
+
+# Read bulk data, maybe in loops until there is nothing left to read?
+sub read_bulk_data ($$$) {
+  my $dev = shift;
+  my $len = $_[1];
+  my $readdata = "";
+  my $res = $dev->bulk_read (2, $readdata, $len+13, 500);
   if ($res<0) {
     printf "ERROR reading bulk data (%d): %s\n", $res, $!;
   }
-  if ($debug) { printf "read %d bytes: \n%s\n\n", $res, $result; }
-  return $result;
+  debug ("read %d bytes: \n%s\n\n", $res, $readdata); 
+  # Check and cut off the header after some sanity checks:
+  if (substr ($readdata, 0, 3) ne "\@\0\0") {
+    printf "ERROR reading data: Wrong header %s\n", substr ($readdata, 0, 3);
+  }
+  my $datalen = unpack ("v", substr ($readdata, 3, 2));
+  $res = $res-13;
+  if ($datalen != $res) {
+    printf "ERROR reading data: Expected %d bytes of data, got %d\n", $datalen, $res;
+  }
+  my $boxname = substr ($readdata, 5, 8);
+  # FIXME: Check the name of the box...
+  
+  # cut off the header:
+  $_[0]=substr ($readdata, 13);
+  return $res;
+}
+
+sub security2string {
+  my $sec = shift;
+  if ($sec == 0) {
+    return "None";
+  } elsif ($sec == 1) {
+    return "WEP";
+  } elsif ($sec == 2) {
+    return "WPA-PSK";
+  } elsif ($sec == 4) {
+    return "WPA2-PSK"; # WPA2-PSK TKIP
+  } elsif ($sec == 6) {
+    return "WPA/WPA2-PSK"; # WPA/WPA2 and WPA2-PSK CCMP
+  } elsif ($sec == 7) {
+    return "WPA-EAP";
+  } elsif ($sec == 8) {
+    return "WPA2-EAP";
+  } else {
+    return sprintf ("Unknown (%d)", $sec);
+  }
+}
+
+sub val {
+  my $config = shift;
+  my $var = shift;
+  foreach my $e (@$config) {
+    if ($e->[0] eq $var) {
+      return $e->[2];
+    }
+  }
+  return undef;
+}
+
+sub print_current_configuration ($) {
+  my $config = shift;
+  print "Current configuration of the device:\n";
+  printf "\tDevice name: %s\n", val($config, '0001');
+  if (val($config,'0012') ne "Enable") {
+    printf "\tTCP/IP not yet configured and/or enabled.\n\n";
+    return;
+  }
+  printf "\tSSID:        %s\n", val($config,'7000');
+  printf "\tSecurity:    %s\n", security2string (val($config,'7003'));
+  printf "\tChannel:     %s\n", val($config,'7002');
+  
+  my $dhcp = (val($config,'4020') eq "Enable");
+  printf "\tIPv4 method: %s\n", $dhcp?"DHCP":"manual";
+  my $associated = val($config,'7014') =~ m/STATE:Associated/;
+  if ($associated || !$dhcp) {
+    printf "\tIP address:   %s\n", val($config,'4000');
+    printf "\tGateway:      %s\n", val($config,'4001');
+    printf "\tNetmask:      %s\n", val($config,'4002');
+  }
+  if ($associated) {
+    printf "\tLink state:   %s\n", val($config,'7014');
+  } else {
+    # Not connected
+    printf "\tWireless not connected\n";
+  }
+  # TODO: Print ad-hoc network setting if configured/enabled
+  print "\n\n";
+}
+
+
+sub request_device_config ($) {
+  my $dev = shift;
+  my $cfg = "";
+  progress "Reading current configuration...\n";
+  my $res = write_bulk_data ($dev, "9100:\n", 6);
+  $res = read_bulk_data ($dev, $cfg, 25600);
+  return $cfg;
 }
 
 sub parse_device_config ($) {
   my $cfg = shift;
+  my @entries = split ('\n', $cfg);
+  my @config = ();
+  foreach (@entries) {
+    my $key = substr ($_, 0, 4);
+    (my $name, my $value) = split (/:/, substr ($_, 5), 2);
+    push (@config, [$key, $name, $value]);
+#     print "key: ", $key, ", name: ", $name, ", value: ", $value, "\n";
+  }
+  return @config;
+}
 
+# Convert a list of the form [ ["OPTIONID", "OPTIONNAME", "VALUE], ...]
+# into a string to be sent to the device
+sub create_config ($) {
+  my $cfg = shift;
+  my $result = "";
+  foreach my $o (@$cfg) {
+    $result .= sprintf ("%s %s:%s\n", $o->[0], $o->[1], $o->[2]);
+  }
+  return $result;
+}
+
+sub parse_survey_data ($) {
+  my $survey = shift;
+  my $networks = {};
+  my @entries = split ("\x0b", $survey);
+  foreach (@entries) {
+    (my $ssid, my $settings) = split ("\x0c", $_);
+    my @settings = split (",", $settings);
+    $networks->{$ssid} = {
+      'mac' => $settings[0],
+      'channel' => $settings[1],
+      'wifi_ver' => $settings[2],
+      'secmode' => $settings[3],
+      'signal' => $settings[4],
+      'adhoc' => $settings[5]
+    };
+#     "SSID: ", $ssid, "settings: ", @settings, "\n";
+#     print "entry: |", $_, "|\n";
+  }
+  debug Dumper ($networks);
+  return $networks;
+}
+
+sub scan_for_aps ($) {
+  my $dev = shift;
+  progress "Scanning for access points: ";
+  my $loop = 0;
+  my $found;
+  my $survey;
+  while (!defined($found) && $loop <= 4) {
+    ++$loop;
+    debug ("loop=", $loop, "\n");
+    my $res = write_bulk_data ($dev, "9107:\n", 6);
+    $res = read_bulk_data ($dev, my $d="", 0);
+    foreach (1, 2, 3, 4, 5) {
+      sleep(1);
+      progress ".";
+    }
+    my $cfg = "";
+    $res = write_bulk_data ($dev, "9100:\n", 6);
+    $res = read_bulk_data ($dev, $cfg, 25600);
+    my @config = parse_device_config ($cfg);
+    $survey = val(\@config, '7021');
+    $found = (defined ($survey) && length ($survey) > 0);
+  }
+  progress "\n";
+  if ($found) {
+#     progress "Found wireless networks: \n", $survey;
+  } else {
+    print "ERROR: No wireless networks detected.\n";
+  }
+  return parse_survey_data ($survey);
+}
+
+sub print_wireless_networks {
+  my $aps = shift;
+  print "Detected wireless networks:\n";
+#   print Dumper ($aps);
+  my $format = "    %3s %-25s%-9s%-13s%-17s%-8s\n";
+  printf $format, "   ", "SSID", "Signal", "Security", "Type", "channel";
+  print "\t------------------------------------------------------------------------\n";
+  my $i = 0;
+  foreach my $ssid (sort {lc $a cmp lc $b} keys (%$aps)) {
+    ++$i;
+    my $network = $aps->{$ssid};
+    printf $format, $i.")", $ssid, $network->{'signal'}, 
+                    security2string ($network->{'secmode'}),
+                    $network->{'adhoc'}?"Ad-Hoc":"Infrastructure",
+                    $network->{'channel'};
+  }
+  print "\n";
 }
 
 
@@ -131,12 +329,53 @@ sub device_close ($) {
   $dev->reset;
 }
 
+sub ask_change {
+  my $exit = "";
+  until ($exit =~ /^[yn]/i ) {
+    print "Do you want to change these settings? (y,n) ";
+    chomp($exit = <STDIN>);
+    $exit = trim($exit);
+  }
+  return ($exit =~ /^y/i);
+}
 
+sub ask_ap {
+  my $aps = shift;
+  my @ssids = (sort {lc $a cmp lc $b} keys (%$aps));
+  my $newssid;
+  print "Please enter the number or the SSID of the desired wireless network.\n";
+  print "Enter '0', 'hidden' or 'h' to connect to a hidden network.\n";
+  do {
+    print "Desired wireless network: ";
+    chomp($newssid = <STDIN>);
+    $newssid = trim ($newssid);
+    return $newssid if (exists $aps->{$newssid});
+    if ($newssid =~ /^[1-9]\d*$/ && ($newssid <= scalar(@ssids))) {
+      return $ssids[$newssid-1];
+    }
+    if ($newssid eq "0" || $newssid eq "h" || $newssid eq "hidden") {
+      $newssid = "";
+      while (length($newssid) < 1) {
+        print "Please enter the SSID: ";
+        chomp ($newssid = <STDIN>);
+      }
+      return $newssid;
+    }
+  } while 1; # We'll jump out of the loop via return!
+}
 
 my $dev = device_detect ();
 if ($dev) {
   device_open ($dev);
   my $cfg = request_device_config ($dev);
-  my $cfg_array = parse_device_config ($cfg);
+  my @config = parse_device_config ($cfg);
+  print_current_configuration (\@config);
+  # TODO: Ask whether configuration shall be changed
+  exit unless (ask_change ());
+  my $aps = scan_for_aps ($dev);
+  print_wireless_networks ($aps);
+  my $newap = ask_ap ($aps);
+  debug ("\tNew wireless network: |%s|\n", $newap);
+  
   device_close ($dev);
 }
-- 
GitLab