#!/usr/bin/python

import sys, getopt, serial, binascii, struct, time


class Mode:
	NONE = 0
	READ = 1
	DUMP = 2
	BURN = 3
	CHECK = 4


class Command:
	PREFIX = 0x42
	NONE = 0
	PING = 1
	STARTRW = 2
	READBLOCK = 3
	READBLOCK_MASK = 4
	WRITEBLOCK = 5

PONG = 'PONG'
BLOCKSIZE = 512
CRCSIZE = 4

romfile = ''
serialport = ''


def main(argv):

	global romfile
	global serialport
	global mode

	mode = Mode.NONE
	
	if len(argv)==0:
		usage()

	try:
		opts, args = getopt.getopt(argv,'dRbcs:r:',['dump','burn','check','serial=','rom='])
	except getopt.GetoptError:
		usage()

	for opt, arg in opts:
		if opt == '-c':
			mode = Mode.CHECK
		elif opt == '-d':
			mode = Mode.DUMP
		elif opt == '-R':
			mode = Mode.READ
		elif opt == '-b':
			mode = Mode.BURN
		elif opt == '-s':
			serialport = arg
		elif opt == '-r':
			romfile = arg
		else:
			mode = Mode.NONE

	commands = {
		Mode.NONE:	usage,
		Mode.BURN:	burnROM,
		Mode.DUMP:	dumpROM,
		Mode.READ:  dumpROM,
		Mode.CHECK:	checkConnection
	}
	commands[mode]()


def checkConnection():
	if serialport=='':
		usage()

	print 'Checking connection to ' + serialport + '...'
	try:
		# Create the serial connection
		connection = serial.Serial(serialport,19200,timeout=1)
		print 'Found ' + serialport + '. Pinging EETool...'

	except (OSError,serial.SerialException):
		print 'Unable to find that serial port.'
		sys.exit(2)

	try:
		# Try sending a ping
		wrote = connection.write(bytearray([Command.PREFIX,Command.PING]))
		if wrote != 2:
			raise ValueError()
		print 'Sent', wrote, 'byte ping. Waiting for response...'

		# Did we get a pong back?
		while 1:
			numBytes = connection.inWaiting()
			if numBytes>0:
				break

		print 'Received', numBytes, 'byte pong'

		if numBytes != len(PONG):
			raise serial.SerialException
		response = connection.read(numBytes)
		if (response != PONG):
			raise serial.SerialException

	except (ValueError,OSError,serial.SerialException):
		print 'EETool is not responding. Check connections and try again.'
		connection.close()
		sys.exit(2)

	print 'EETool is online and ready for use.'
	connection.close()
	sys.exit(0)


def burnROM():
	if romfile=='' or serialport=='':
		usage()
	
	print 'Connecting to ' + serialport + '...'
	try:
		# Create the serial connection
		connection = serial.Serial(serialport,19200,timeout=1)

	except (OSError,serial.SerialException):
		print 'Unable to find that serial port.'
		sys.exit(2)

	try:
		destFile = open (romfile, "r")

	except OSError:
		print 'Unable to read from ROM file',romfile
		sys.exit(2)

	try:
		# Prepare to write
		wrote = connection.write(bytearray([Command.PREFIX,Command.STARTRW]))
		if wrote != 2:
			raise ValueError()

		print 'Sending data to EETool...'

		totalSize = 0
		
		# Transmit all the blocks
		for blockNum in range(0,64):
			wrote = connection.write(bytearray([Command.PREFIX,Command.WRITEBLOCK]))
			if wrote != 2:
				raise ValueError()

			# Read block from ROM file
			block = destFile.read(BLOCKSIZE)
			
			# Send block to EETool
			numBytes = connection.write(block)
			if numBytes != BLOCKSIZE:
				raise ValueError()
			
			# Wait for confirmation
			while 1:
				wrote = connection.inWaiting()
				if wrote>=1:
					break

			confirmation = connection.read(numBytes)
			chk = bytearray([confirmation]);
			
			if not ((wrote==1) and (chk[0]==Command.PREFIX)):
				print 'ERROR: Confirmation failed while uploading block',blockNum,'   Aborting.'
				sys.exit(2)

			sys.stdout.write('#')
			sys.stdout.flush()
			totalSize += numBytes
			time.sleep(0.1)

		destFile.close()
		print '\nDone! Wrote',totalSize,'bytes to EEPROM.'
		sys.exit(0)
			
	except (ValueError,OSError,serial.SerialException):
		print 'UNKNOWN ERROR'
		connection.close()
		sys.exit(0)



def dumpROM():

	if romfile=='' or serialport=='':
		usage()
	
	print 'Connecting to ' + serialport + '...'
	try:
		# Create the serial connection
		connection = serial.Serial(serialport,19200,timeout=1)

	except (OSError,serial.SerialException):
		print 'Unable to find that serial port.'
		sys.exit(2)

	try:
		destFile = open (romfile, "wb")

	except OSError:
		print 'Unable to write to ROM file',romfile
		sys.exit(2)

	try:
		# Prepare to read
		wrote = connection.write(bytearray([Command.PREFIX,Command.STARTRW]))
		if wrote != 2:
			raise ValueError()

		print 'Requesting data from EETool...'

		totalSize = 0
		cmd = Command.READBLOCK_MASK if mode==Mode.DUMP else Command.READBLOCK
		
		# Request all the blocks
		for blockNum in range(0,64):
			wrote = connection.write(bytearray([Command.PREFIX,cmd]))
			if wrote != 2:
				raise ValueError()

			while 1:
				numBytes = connection.inWaiting()
				if numBytes>=BLOCKSIZE + CRCSIZE:
					break

			if (numBytes!=BLOCKSIZE + CRCSIZE):
				print 'ERROR: Block',blockNum,'failed to download. Aborting.'
				sys.exit(2)

			response = connection.read(numBytes)
			crc = struct.unpack('<I',response[BLOCKSIZE:BLOCKSIZE+CRCSIZE])[0]
			block = response[0:BLOCKSIZE]
			calcCRC = binascii.crc32(block) & 0xFFFFFFFF
			if crc != calcCRC:
				print numBytes,'CRC failed on block',blockNum,'Read CRC =',hexU32(crc),'   Calcuated CRC =',hexU32(calcCRC)
				print 'Aborting.'
				sys.exit(2)

			# Write the block to our file
			destFile.write(bytes(block))

			sys.stdout.write('#')
			sys.stdout.flush()
			totalSize += numBytes - CRCSIZE
				
		destFile.close()
		print '\nDone! ROM is',totalSize,'bytes.'
		sys.exit(0)
			
	except (ValueError,OSError,serial.SerialException):
		print 'UNKNOWN ERROR'
		connection.close()
		sys.exit(0)


def hexU32(n):
	return "0x%x"%(n&0xffffffff)

def usage():
	print '''
Usage: EECommander -[d|b|c] -s <serial port> -r <rom file>
 -d: Dump the contents of the attached MASK ROM to a file
 -R: Read the contents of the attached EEPROM to a file
 -b: Burn the rom file to the attached EEPROM
 -c: Check your connection to EETool (no other action taken)

To find your serial port, connect EETool and try "ls /dev/tty.usbmodem*" on Mac or Linux.
On Windows, use a COM port name, such as "COM1". You can use the -c option to find out which one.
'''
	sys.exit(2)




if __name__ == "__main__":
	main(sys.argv[1:])