206 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python3
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#
 | 
						|
#  sendFile.py -- Send file to Gigatron through Arduino as interface
 | 
						|
#
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#
 | 
						|
# Examples:
 | 
						|
#
 | 
						|
#   Utils/sendFile.py Apps/Overworld/Overworld.gt1 # Reset and starts Loader
 | 
						|
#   Utils/sendFile.py BASIC/FastLines.gtb          # Also loads BASIC
 | 
						|
#   Utils/sendFile.py foo.bas                      # Only send text lines
 | 
						|
#   Utils/sendFile.py < foo.txt                    # Same...
 | 
						|
#
 | 
						|
# Notes:
 | 
						|
#
 | 
						|
#  - Needs Python 3.x: Python 2.7 isn't supported any longer
 | 
						|
#  - Systems: macOS, Linux and Windows
 | 
						|
#  - Devices: Arduino Uno/Micro/Nano, SparkFun Pro Micro
 | 
						|
#  - Arduino (-compatible) must run BabelFish.ino
 | 
						|
#
 | 
						|
# History:
 | 
						|
#
 | 
						|
# 2018-04-29 (marcelk) Initial version
 | 
						|
# 2018-05-13 (marcelk) Use application level flow control
 | 
						|
# 2018-05-29 (rockybulwinkle) Add option to override detected port,
 | 
						|
#               search on /dev/ttyACM* as well, swith to argparse.
 | 
						|
# 2018-05-30 (marcelk) Enable stdin again. A bit more consistency.
 | 
						|
# 2018-06-04 (marcelk) Allow timeout while waiting for first prompt
 | 
						|
# 2018-06-29 (marcelk) Send escaped control bytes to newer Babelfishes
 | 
						|
# 2018-07-23 (marcelk) Send GTB files into BASIC. Renamed to sendFile.py
 | 
						|
# 2018-07-28 (marcelk) Remove escaping of control bytes. Workaround FIFOCON bug.
 | 
						|
# 2019-12-19 (crouze) Initial port from senFile.py
 | 
						|
#                     BASIC truncation result added to stdout
 | 
						|
# 2019-12-22 (marcelk) Adopt crouze's sendFile3.py mods; Remove legacy check;
 | 
						|
#                      Simplifications; Check COM ports on Windows
 | 
						|
#
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
import argparse
 | 
						|
import serial # pip3 install pyserial
 | 
						|
from serial.tools.list_ports import comports
 | 
						|
import sys
 | 
						|
from time import sleep
 | 
						|
 | 
						|
# One-for-all error handler (don't throw scary stack traces at the user)
 | 
						|
sys.excepthook = lambda exType, exValue, exTrace: print('%s: %s' % (exType.__name__,  exValue))
 | 
						|
 | 
						|
if sys.version_info[0] < 3:     # Require Python 3
 | 
						|
  raise Exception('Need Python 3')
 | 
						|
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#       Command line arguments
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 | 
						|
parser = argparse.ArgumentParser(description='Send a file into the Gigatron')
 | 
						|
parser.add_argument('-v', '--verbose', dest='verbose', default=False,
 | 
						|
                    help='be verbose',
 | 
						|
                    action='store_true')
 | 
						|
parser.add_argument('-p', '--port', dest='port', default=None,
 | 
						|
                    help='USB port to Arduino (default is auto-detect)')
 | 
						|
parser.add_argument('-t', '--truncate', dest='truncate', default=250,
 | 
						|
                    help='Truncate text lines')
 | 
						|
parser.add_argument('filename', help='GT1 or GTB file', nargs='?')
 | 
						|
 | 
						|
args = parser.parse_args()
 | 
						|
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#       Functions
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 | 
						|
def log(prefix, line):
 | 
						|
  if args.verbose:
 | 
						|
    print(prefix, line)
 | 
						|
 | 
						|
def sendCommand(cmd):
 | 
						|
  """Send command to Gigatron Adapter Interface and wait for ready prompt"""
 | 
						|
  log('>', cmd)
 | 
						|
  ser.write(str.encode(cmd + '\n'))
 | 
						|
  return waitReply()
 | 
						|
 | 
						|
def waitReply(expectPrompt=True):
 | 
						|
  line = readLine()
 | 
						|
  while True:
 | 
						|
    if line is None:
 | 
						|
      if not expectPrompt:
 | 
						|
        break
 | 
						|
    else:
 | 
						|
      if line[-1] == '?':       # Got prompt
 | 
						|
        break
 | 
						|
      if line[0] == '!':        # Got error
 | 
						|
        raise Exception(line[1:])
 | 
						|
    line = readLine()
 | 
						|
  return line
 | 
						|
 | 
						|
def readLine():
 | 
						|
  """Read line from serial port with optional verbosity"""
 | 
						|
  line = ser.readline()
 | 
						|
  if line:
 | 
						|
    line = line.decode()        # Convert from bytes to Unicode
 | 
						|
    line = line.rstrip()        # Remove any trailing newline and spaces
 | 
						|
    log('<', line)
 | 
						|
  else:
 | 
						|
    line = None                 # Signal that there is a timeout
 | 
						|
    if args.verbose:
 | 
						|
      sys.stdout.write('.')
 | 
						|
      sys.stdout.flush()
 | 
						|
  return line
 | 
						|
 | 
						|
def sendGt1(fp):
 | 
						|
  """Send Gigatron object file"""
 | 
						|
  ask = sendCommand('U')
 | 
						|
 | 
						|
  while ask[0].isdigit():
 | 
						|
    # Arduino will ask for <n> bytes by sending '<n>?'
 | 
						|
    n = int(ask[:-1])
 | 
						|
    data = fp.read(n)
 | 
						|
    if len(data) < n:
 | 
						|
      raise Exception('File too short')
 | 
						|
    sys.stdout.write('.')
 | 
						|
    sys.stdout.flush()
 | 
						|
    ser.write(data)
 | 
						|
    ask = waitReply()           # Wait for next prompt
 | 
						|
  print()
 | 
						|
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#       Connect to Arduino
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 | 
						|
autoDetect = [                  # (VID, PID)
 | 
						|
        (0x2341, 0x0043),       # Arduino Uno
 | 
						|
        (0x2341, 0x8037),       # Arduino Micro
 | 
						|
        (0x0403, 0x6001),       # Arduino Nano
 | 
						|
        (0x2341, 0x0042),       # Arduino Mega
 | 
						|
        (0x1b4f, 0x9206) ]      # SparkFun Pro Micro (5V, 16 MHz)
 | 
						|
 | 
						|
port = None
 | 
						|
if not args.port:
 | 
						|
  serPorts = [p for p in comports() if (p.vid, p.pid) in autoDetect]
 | 
						|
  if len(serPorts) == 0:
 | 
						|
    raise Exception('No Arduino detected')
 | 
						|
  if len(serPorts) > 1:
 | 
						|
    raise Exception('More than one Arduino detected: %s' % ' '.join(serPorts))
 | 
						|
  port = serPorts[0].device
 | 
						|
  desc = ' (%s)' % serPorts[0].description
 | 
						|
else:
 | 
						|
  port, desc = args.port, ''
 | 
						|
 | 
						|
print('Connecting to %s%s' % (port, desc))
 | 
						|
ser = serial.Serial(port=port, baudrate=115200, timeout=2)
 | 
						|
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#       Send program
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 | 
						|
if args.filename:
 | 
						|
  isGt1 = args.filename.lower().endswith(('.gt1', '.gt1x'))
 | 
						|
  isGtb = args.filename.lower().endswith(('.gtb', '.gtbx'))
 | 
						|
  fp = open(args.filename, 'rb' if isGt1 else 'r')
 | 
						|
else:
 | 
						|
  isGt1, isGtb = False, False
 | 
						|
  fp = sys.stdin
 | 
						|
 | 
						|
waitReply(expectPrompt=False)   # Check for prompt, but don't wait forever
 | 
						|
 | 
						|
if isGt1 or isGtb:
 | 
						|
  print('Resetting Gigatron')
 | 
						|
  sendCommand('R')
 | 
						|
 | 
						|
  print('Starting Loader')
 | 
						|
  sendCommand('L')
 | 
						|
 | 
						|
if isGt1:
 | 
						|
  print('Sending program %s' % repr(args.filename))
 | 
						|
  sendGt1(fp)
 | 
						|
 | 
						|
if isGtb:
 | 
						|
  print('Loading Tiny BASIC')
 | 
						|
  sendCommand('P')              # Load BASIC from BabelFish PROGMEM
 | 
						|
  sleep(2)                      # Startup takes a while on 64K
 | 
						|
  args.truncate = 25            # Line limit
 | 
						|
 | 
						|
if not isGt1:
 | 
						|
  print('Sending text %s' % (repr(args.filename) if args.filename else 'from stdin'))
 | 
						|
  for line in fp:
 | 
						|
    line = line.rstrip('\r\n')
 | 
						|
    if len(line) > args.truncate:
 | 
						|
      print('Truncated %s' % repr(line))
 | 
						|
      line = line[0:args.truncate]
 | 
						|
      print('       to %s' % repr(line))
 | 
						|
    sendCommand('.' + line)
 | 
						|
 | 
						|
if isGtb:
 | 
						|
  sendCommand('.RUN')           # Start BASIC program
 | 
						|
 | 
						|
print('Finished')
 | 
						|
 | 
						|
if args.filename:
 | 
						|
  fp.close()
 | 
						|
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
#
 | 
						|
#-----------------------------------------------------------------------
 | 
						|
 |