usb-uirt-config.pl

#!/usr/bin/perl
#
# Copyright (C) 2007 Kenneth L. Root. <http://the-b.org>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

use strict;
use warnings;

use Device::SerialPort;
use Getopt::Long;
use Pod::Usage;

use constant TIMEOUT_DEFAULT => 10;

use constant TRANSMITTING => 0x20;
use constant CMDOK => 0x21;
use constant CSERROR => 0x80;
use constant TOERROR => 0x81;
use constant CMDERROR => 0x82;

use constant ACTIONS => ['Pulse pin (positive)',
                        'Set pin',
                        'Clear pin',
                        'Pulse pin (negative)',
                        ];

my %config = (
        'port' => '/dev/ttyUSB0',
        'slot' => 0
);

my $port;

my $slots = -1;

sub openPort {
  my($portName) = @_;

  my $port = new Device::SerialPort($portName)
        || die "Can't open $portName: $!\n";

  $port->read_char_time(0);
  $port->read_const_time(1000);

  return $port;
}

sub checksum {
  my(@bytes) = @_;

  my $cksum = 0;

  map { $cksum += $_ } @bytes;

  return 0x100 - ($cksum & 0xFF);
}

sub printCommand {
  my ($prefix, @cmd) = @_;

  print "$prefix: ";

  map { printf "0x%02x ", $_ } @cmd;

  print "\n";
}

sub sendCommand {
  my @cmd = @_;

  push @cmd, checksum(@cmd);

  printCommand("send", @cmd) if $config{'debug'};

  $port->write(pack('C*', @cmd));
}

sub readCommand {
  my($numBytes) = @_;

  my $timeout = TIMEOUT_DEFAULT;
  my $buffer;
  my $chars = 0;

  while ($timeout > 0) {
    my ($count, $saw) = $port->read(255);
    if ($count > 0) {
      $chars += $count;
      $buffer .= $saw;
      if ($chars >= $numBytes) {
        my @cmd = map { ord } unpack("(Z)*", $buffer);

        printCommand("recv", @cmd) if $config{'debug'};

        return @cmd;
      }
    } else {
      $timeout--;
    }
  }

  if ($timeout == 0) {
    die "Couldn't communicate with USB-UIRT device after ". TIMEOUT_DEFAULT ." seconds.\n";
  }
}

sub getVersion {
  my $printing = shift;

  sendCommand(0x23);
  my @res = readCommand(7);

  return if not $printing;

  print "Firmware information:\n";
  printf "\tFirmware version = %d.%d\n", $res[1], $res[0];
  printf "\tProtocol compatability version = %d.%d\n", $res[3], $res[2];
  # Y3K bug
  printf "\tFirmware date (DMY) = %02d/%02d/%02d\n", $res[4], $res[5], $res[6] + 2000;
}

sub setUIRMode {
  sendCommand(0x20);
  my @res = readCommand(1);
  die "Cannot set UIR mode" if ($res[0] != CMDOK);
}

sub getGpioCaps {
  my $printing = shift;

  sendCommand(0x30, 0x01);

  my @res = readCommand(6);

  die "Failed to get GPIO caps" if (checksum(@res[0..$#res-1]) != $res[$#res]);

  $slots = $res[0];

  return if not $printing;

  print "GPIO capabilities:\n";
  printf "\tNumber of slots = %d\n", $slots;
  foreach my $slot (0..$slots-1) {
    printf "\tPort %c bitmask: %08b\n", (ord("A") + $slot), $res[1+$slot];;
  }
}

sub getGpioCfg {
  my ($slot) = @_;

  getGpioCaps if ($slots == -1);

  die "No such programmable slot: $slot" if ($slot > $slots);

  sendCommand(0x31, 0x02, $slot);
  my @res = readCommand(9);
  die "Failed to get GPIO config for slot $slot"
        if (checksum(@res[0..$#res-1]) != $res[$#res]);

  my $action = ($res[6] & 0xC0) >> 6;
  my $port = ($res[6] & 0x18) >> 3;
  my $pin = $res[6] & 0x03;
  my $duration = $res[7] * 5;

  print "GPIO configuration for slot $slot\n";
  printCommand("\tIR Code", @res[0..5]);
  print "\tPin action: " . ACTIONS->[$action] . "\n";
  print "\tPort: ". chr(ord("A") + $port) .".". $pin . "\n";
  print "\tDuration: ". $duration ." ms\n";
}

sub setWakeUpCode {
  my ($slot, @irCode) = @_;

  # bit   7,6: 11 (Pulse Pin (negative))
  # bit     5: reserved
  # bit   4,3: 00 (Port A)
  # bit 2,1,0: 011 (Pin 3)
  my $action = 0xC3;
  my $duration = 100 / 5; # Duration is in 5mS increments.

  sendCommand(0x32, 0x0A, $slot, @irCode, $action, $duration);
  my @res = readCommand(1);

  if ($res[0] == CMDOK) {
    print "Successfully set Wake-Up code in slot $slot.\n";
  } else {
    print "Error setting Wake-Up code.\n";
  }
}

sub getConfiguration {
  sendCommand(0x38, 0x01);

  my @res = readCommand(3);

  print "Configuration:\n";
  print "\tLED blink on IR TX: " . ((($res[0] & 0x01) == 0x01) ? "yes" : "no") ."\n";
  print "\tLED blink on IR RX: " . ((($res[0] & 0x02) == 0x02) ? "yes" : "no") ."\n";
}

sub compareCodes {
  my ($first, $second) = @_;

  no warnings;

  return 0 unless @$first == @$second;
  for (my $i = 0; $i < @$first; $i++) {
    return 0 if ($first->[$i] ne $second->[$i]);
  }
  return 1;
}

GetOptions(\%config,
        'help|?',
        'debug!',
        'port=s',
        'version!',
        'get-configuration!',
        'gpio-capabilities|gpiocaps|gpio-caps!',
        'slot-configuration!',
        'wakeup!',
        'slot=i',
        'get-code!',
        'code=s',
);

if (exists $config{'help'}) {
  pod2usage(1);
}

$port = openPort($config{'port'});

print "Opened port ". $config{'port'} ."\n" if ($config{'debug'});

getVersion($config{'version'});
exit(0) if $config{'version'};

if (exists $config{'gpio-capabilities'}) {
  getGpioCaps(1);
  exit(0);
}

if (exists $config{'get-configuration'}) {
  getConfiguration;
  exit(0);
}

if (exists $config{'slot-configuration'}) {
  getGpioCfg($config{'slot'});
  exit(0);
}

if (exists $config{'get-code'}) {
  setUIRMode;
  print "Press the button on the remote you want to see the UIR code for...\n";
  my @res = readCommand(6);
  printCommand("IR Code", @res);
  exit(0);
}

if ($config{'wakeup'}) {
  if (not exists $config{'code'}) {
    setUIRMode;
    print "Press key on remote that you want to be the Wake-Up key...\n";
    my @code1 = readCommand(6);

    my $tries = 6;
    my $identical = 0;
    while ($tries-- > 0 && !$identical) {
      print "Press key on remote that you want to be the Wake-Up key again...\n";
      my @code2 = readCommand(6);

      $identical = compareCodes(\@code1, \@code2);
      if (not $identical) {
        @code1 = @code2;
        print "Codes are not identical. Please press the button again...\n";
      }
    }
    if ($tries == 0) {
      print "Couldn't get two identical codes! Exiting...\n";
      exit(1);
    }

    $config{'code'} = \@code1;
  }

  setWakeUpCode($config{'slot'}, @{$config{'code'}});
  getGpioCfg($config{'slot'});
  exit(0);
}

$port->close
        || die "Failed to close ". $config{'port'} .": $!\n";

print "No actions specified!\n";
pod2usage(1);
exit(1);

__END__

=head1 NAME

usb-uirt-config - Configuration program for the USB-UIRT

=head1 SYNOPSIS

usb-uirt-config [options]

=head1 OPTIONS

=over 8

=item B<--port>

Serial port to which USB-UIRT is connected.

=item B<--debug>

Enable debugging.

=item B<--version>

Output USB-UIRT version.

=item B<--get-configuration>

Prints out the USB-UIRT's current configuration.

=item B<--gpio-capabilities>

Get GPIO capabilities of USB-UIRT.

=item B<--slot-configuration>

Get GPIO configuration for a single slot.

=item B<--get-code>

Reads and prints an incoming IR code.

=item B<--wakeup>

Configure wake-up on GPIO slot

=item B<--slot>

Specify GPIO slot to configure.

=item B<--code>

IR code to use (not yet implemented).

=back

=head1 DESCRIPTION

Configures the USB-UIRT device.
=cut

Copyright © Kenny Root. All rights reserved.