#!/usr/bin/perl -w

package Crosspad;
use strict;
use FileHandle;
use integer;

my $dev = '/dev/ttyS0';
my $baud = 115200;

sub new
{
	my $class = ref $_[0] ? ref shift : shift;

	my $dev = shift || $dev;
	my $baud = shift || $baud;

	my $fh = new FileHandle;
	sysopen $fh, $dev, 2
		or die "$class: Unable to open $dev: $!\n";

	$fh->autoflush(1);
	system( "stty $baud cs8 cread clocal < $dev" );

	bless {
		dev	=> $fh,
		name	=> "$class: $dev",
		verbose	=> 1,
	}, $class;
}

sub get
{
	my $dev = shift;
	my $len = shift;

	local $SIG{ALRM} = sub { die "$dev->{name}: Timed out\n" };

	my $buf;
	eval {
		alarm 5;
		while( $len > 0 ) {
			my $tmp;
			my $rc = sysread $dev->{dev}, $tmp, $len;

			die "$dev->{name}: sysread failed: $rc\n"
				unless $rc;

			$len -= $rc;
			$buf .= $tmp;
		}

		alarm 0;
	};

	die $@ if $@;
	return $buf;
}


sub put
{
	my $dev = shift;
	my $buf = shift;
	my $len = length $buf;

	warn "$dev->{name}: Write: ",
		(map { sprintf "%02x ", ord $_ } split //, $buf ),
		"\n"
			if $dev->{debug};

	if( $len != syswrite $dev->{dev}, $buf, length $buf ) {
		die "$dev->{name}: Short write: $!\n";
	}

	$len;
}

sub sendack
{
	my $dev = shift;
	$dev->put( "\x06" );
}


#
# Syncronizes communication with the tablet and returns the
# length of data to be downloaded from it.  The format looks like
# this:
#
# 0x02		Sync byte (wait until it arrives)
# a
# b
# c
# d
# a^b^c^d	Checksum
#
# The length of data is the binary value in msb first format: abcd.
#
# After reading this header, we send a 0x06 to the tablet to
# acknowledge receipt of the data.
#
sub data_len
{
	my $dev = shift;

	warn "$dev->{name}: Waiting for Crosspad\n"
		if $dev->{verbose};

	while(1) {
		my $byte = ord $dev->get(1);
		last if $byte eq 0x02;
		warn "$dev->{name}: Ignoring non-sync byte: ",
			sprintf "%02x\n", $byte
				if $dev->{verbose};
	}

	warn "$dev->{name}: Read sync byte.  Looking for packet\n"
		if $dev->{debug};

	my $sum = 0;
	my $len = 0;
	my $packet = $dev->get(5);
	for( split //, $packet ) {
		my $byte = ord $_;
		$sum ^= $byte;
		$len = $byte + ($len<<8);
	}

	# Remove checksum from length
	$len >>= 8;
	die "$dev->{name}: sync packet chesum error: $sum != 0\n"
		if $sum;

	warn "$dev->{name}: Expecting $len data bytes\n"
		if $dev->{verbose};

	$dev->sendack;
	$len;
}

#
# The data is transfered in several "sectors" that are
# each ack'd before the next is sent.  The sector header
# packet looks like this:
#
#	0x01		Sync byte
#	sector #	
#	complement	The sector # and its complement should xor to 0xFF
#	a
#	b
#
# The length of the sector is sent in MSB first form: ab
#
# The header is followed by ab bytes of data that contain
# that sector.  After receipt of this data the pad expects
# an ack packet (0x06) before it will send the next sector
#
# Allegedly if the sync packet is 0x07 it means that the tablet
# is done sending data.  I've never seen it
#
sub download
{
	my $dev = shift;
	my $data = '';
	my $sector = 0;

	my $len = $dev->data_len;

	while( $len > 0 ) {
		my @header = map { ord $_ } split //, $dev->get(5);
		my $header = join '', map { sprintf "%02x ", $_ } @header;

		warn "$dev->{name}: Sector $sector header: $header\n"
			if $dev->{debug};

		# Check for premature end of sending
		return $data
			if $header[0] == 0x07;	

		die "$dev->{name}: Bad sync byte: $header should begin 0x01\n"
			if $header[0] != 0x01;

		die "$dev->{name}: Bad sector header: $header\n"
			if ($header[1] ^ $header[2]) != 0xFF;

		my $slen = $header[3]<<8 | $header[4];
	
		warn "$dev->{name}: Sector $sector length $slen\n"
			if $dev->{verbose};
	
		$data .= $dev->get( $slen );
		$len -= $slen;
		$sector++;

		$dev->get(1);	# Unknown meaningo
		$dev->sendack;
	}


	warn "$dev->{name}: Read $sector sectors OK\n"
		if $dev->{verbose};

	$data;
}

1
