#!/usr/bin/perl -w

use threads;
use Thread::Queue;

use strict;
use Time::HiRes qw(time);

my $version = "1.1.99";

BEGIN {

    if( index($^O, 'Win') >= 0 ) {   # MSWin32 (and not darwin, cygwin, ...)
        eval("require Win32::SerialPort") or die "Error: Failed dependencies!\nPlease install Win32::SerialPort via ppm or CPAN!\n";
        import  Win32::SerialPort;
    } else {
        eval("require Device::SerialPort") or die "Error: Failed dependencies!\nPlease install Device::SerialPort via CPAN!\n";
        import  Device::SerialPort;
    }
}

# Starting thread that asynchronously prints the progress lines
# dequeue() blocks until new data arrives in the queue, so we don't have to care about anything
my $printqueue = Thread::Queue->new();
my $printthread = threads->create( sub { while (my $line = $printqueue->dequeue()) { print $line } });
# instantly detach printing thread, as we never wanna get data back from it 
$printthread->detach();

sub formatstr($) {
	$_ = shift;
	s/([^\d\w :#\-\+])/'<#'.ord($1).'>'/ge;
	s/<#13>/<#13>\n/g;
	$_;
}

$|++; # disable buffering

print <<WARR_END;

$0 Ver $version

*** No Warranty
***
*** This program is distributed in the hope that it will be useful,
*** but is provided AS IS with ABSOLUTELY NO WARRANTY;
*** The entire risk as to the quality and performance
*** of the program is with you. Should the program prove defective,
*** you assume the cost of all necessary servicing, repair or correction.
*** In no event will any of the developers, or any other party,
*** be liable to anyone for damages arising out of the use or inability
*** to use the program.

WARR_END

my ($serial, $count, $filehnd, $cur, $test, $line);
my ($dev, $mode, $filename, $startaddr, $endaddr) = @ARGV;

die "Error: Not enough parameters! $0 <serial port> <-r|-w> <flash file> [<start adress> <end address>]\n" unless $dev and $mode and $filename;
die "Error: Unsupported mode parameter, please specify <r>ead or <w>rite!\n" unless $mode =~ /^[\-\/][rw]$/i;

my $ostype = $^O;
if (index($ostype, 'Win') >= 0) {
	$serial = Win32::SerialPort->new($dev);
} else {
	$serial = Device::SerialPort->new($dev);
}
die ("Error: can't open serial port. Typo? (e.g. COM1, /dev/ttyUSB0)\n") unless ref $serial;

$serial->baudrate(115200);
$serial->databits(8);
$serial->parity('none');
$serial->stopbits(1);
$serial->purge_all();
$serial->rts_active(0);
$serial->dtr_active(0);
$serial->read_const_time(1000);
if (index($ostype, 'Win') >= 0) {
	$serial->read_interval(100);
	$serial->read_char_time(5);
}
$serial->write_settings or die ("Error: can't init serial port.\n");

# -------------------------------------------------------------------------------------

my $starttime = time;

if ($mode =~ /r/i) {
	$startaddr = $startaddr ? hex($startaddr) : 0x40000;
	$endaddr = $endaddr ? hex($endaddr) : 0x7fffff;
	die "Error: Start address has to be smaller than end address!\n" if $startaddr >= $endaddr;
	my ($step, $steps) = (0, int(($endaddr - $startaddr) / 0x100) + 1);
	open($filehnd, '>', $filename) or die "Error: Couldn't create/rewrite file '$filename'!\n";
	print $filehnd "#############################################\n# GERMSloader.pl Ver $version\n";
	print $filehnd "# Download Flash from: " . localtime() . "\n";
	printf $filehnd "# Addr: 0x%08x to 0x%08x\n#############################################\nr0\n", $startaddr, $endaddr;
	my $estart = int($startaddr / 0x10000) * 0x10000;
	while ($estart < $endaddr) {
		printf $filehnd "e%06x\n", $estart;
		$estart += 0x10000;
	} 
	my ($firmware, $elapsedtime);
	while ($startaddr < $endaddr) {
		my $span = $startaddr + 0x100 < $endaddr ? 0x100 : $endaddr - $startaddr;
		printf "Reading %08x-%08x ", $startaddr, $startaddr+$span;
		$serial->write(sprintf "M%08x-%08x\r", $startaddr, $startaddr+$span); # append a CR 
		# waiting for response
		my ($readcount, $buffer) = (0, '');
#		my $estbufsize = 	19 +						# "Mstart-end\r"
#					5 * int(($span + 1) / 2) +			# 5 * count of 4-byte-values
#					12 * int(($span + 16) / 16) + #12 * ($span % 16) +	# 12 * count of lines
#					1;						# 1 for the plus sign
#		print " $span - $estbufsize\n";
# works not very well, so we fall back to 852, what's the size of a 256-byte-buffer
		my $estbufsize = 852;
#		my $estbufsize = 13332; # for $span = 0x1000
		while ($buffer !~ /\+/) {
			my (undef, $str) = $serial->read($estbufsize);
			$buffer = "$buffer$str";
			print '.';
			if ($readcount++ > 10) {
				$buffer = formatstr($buffer);
				die <<RTOEND;
\nError: Timeout while reading from DSO!
Response was: '$buffer'
Firmware readout was NOT successful!
Please fix the connection issue and retry!
RTOEND
			}
		}
		$startaddr += $span;
		$firmware = "$firmware$buffer";

		my $factor = ++$step / $steps;
		$elapsedtime = time - $starttime;
		my $esttime = ($elapsedtime / $factor) - $elapsedtime;
		my $speed = 0;
		$speed = (0.01 * length($firmware) / $elapsedtime) if ($elapsedtime > 0); # factor 0.01 because of kbps and startbit/stopbit => 10 Bit/Byte
                $printqueue->enqueue(" OK - " . sprintf("%.1f", $factor * 100) . "% - " . sprintf("%.1f",$elapsedtime) .
					" sec / " . sprintf("%.0f",$esttime) . " sec left / interface-speed: ".sprintf("%.2f",$speed) . " kbps        \r");
	}
	print "Successfully read out firmware in " . sprintf("%.1f",$elapsedtime) . " seconds! Writing firmware to $filename...  \n";
	for (split /\r/, $firmware) {					# line-wise
		next unless /^#/;
		next if /FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF/;
		chomp;
		my ($address, @data) = split;
		$address =~ s/[#:]//g;
		my ($dcount, $idx, $chksum) = qw(5 0 0);		# $dcount = 5 -> 4 addr bytes + 1 chksum byte
		for (@data) {						# endianess and strtoint...
			s/([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})/$2$1/;
			$_ = hex;
			$dcount += 2;
		}
		my $bytestr = sprintf "%02X%08X"."%04X"x@data, $dcount, hex($address), @data;
		while ($idx < length($bytestr)) {
			$chksum += hex(substr($bytestr, $idx, 2)) & 0xff;
			$idx += 2;
		}
		printf $filehnd "S3%s%02X\n", $bytestr, ~$chksum & 0xff;
	}
	print $filehnd "r0\n";
	close($filehnd);

} else {

	open($filehnd, $filename) or die "Error: File '$filename' not found.\n";
	my @temp = <$filehnd>;
	close($filehnd);

	$count = @temp;
	my ($elapsedtime);

	LINE: for $line (@temp) {

		$cur++;

		$line =~ s/[\r\n]//g; # get rid of any CR and LF
		printf "Writing line %06u of %06u: ", $cur, $count;
#		printf "Writing line %06u of %06u: '%.29s%s", $cur, $count, $line, length($line) > 30 ? "...' " : "' ";
#		print "$cur/$count: '", substr($line, 0, 49), length($line) > 50 ? '...' : '', "' ";
		if ($line =~ /^#.*/) {
			print "Comment not transferred.\r";
			next;
		}

		my $writecount = 0;
	SERWRT:	while (1) {
			$serial->write("$line\r"); # append a CR 
			
			if ($line =~ /^S([789])/) {
				print "S$1 detected, end of GERMS transmission.\n";
				last LINE;
			}

			# waiting for response
			my ($readcount, $buffer) = (0, '');
			while ($buffer !~ /[+!]/) {
				my (undef, $str) = $serial->read(length($line) + 2); # $line + "\r" + "+"
				$buffer = "$buffer$str";
				print '.';
				if ($readcount++ > 10) {
					$buffer = formatstr($buffer);
					die <<TOEND;
\nError: Timeout while reading response from DSO!
Response was: '$buffer'
Firmware update was NOT successful!
Please fix the connection issue and retry!
TOEND
				}
			}

			if ($buffer =~ /^$line\r\+$/) {
#				print " OK\n";
				last SERWRT;
			} else {
				print "X";
				if ($writecount++ > 10) {
					die <<WEEND;
\nError: Unknown transmission error.
Firmware update was NOT successful!
Please fix the issue and retry!
WEEND
				}
			}
		}
		my $factor = $cur / $count;
		$elapsedtime = time - $starttime;
		my $esttime = ($elapsedtime / $factor) - $elapsedtime;
                $printqueue->enqueue(" OK - " . sprintf("%.1f", $factor * 100) . "% - " . sprintf("%.1f",$elapsedtime) .
					" sec / " . sprintf("%.0f",$esttime) . " sec left  \r");
	}
	print "Successfully wrote firmware in " . sprintf("%.1f", $elapsedtime) . " seconds!                    \nPlease reboot DSO if not already done.\n";
}

print "READY!\nThanks for all the fish.\n";

