Skip to content

Instantly share code, notes, and snippets.

@DDR0
Last active September 23, 2018 21:12
Show Gist options
  • Select an option

  • Save DDR0/c10682d0ff230ce462bf429ad90eeee0 to your computer and use it in GitHub Desktop.

Select an option

Save DDR0/c10682d0ff230ce462bf429ad90eeee0 to your computer and use it in GitHub Desktop.
Commodore 256 Serial Shell
#!/usr/bin/python3
import sys, cmd, ast, pathlib
try:
import serial
except ModuleNotFoundError:
print('Could not import pyserial library. Try running "pip3 install pyserial".')
sys.exit(1)
debug = False #Throw error on exceptions if True, otherwise, keep running and keep command history.
TXHDR = b'\xAA'
RXHDR = b'\x55'
class CMD:
READ = b'\x00'
WRITE = b'\x01'
ser = serial.Serial('/dev/ttyUSB0', baudrate=6000, timeout=1)
print('connecting via', ser)
class SerialShell(cmd.Cmd):
intro = "\nType help or ? to list commands."
prompt = 'c256: '
def do_read(self, arg):
"""read addy length: Read _length_ bytes from the c256, starting at address _addy_."""
try:
command = parseCommand(CMD.READ, *arg.split())
error = command.errorMessage()
except Exception as e:
error = e
if debug: raise e
if error:
print('error:', error)
else:
print('emitting', command.bytes())
ser.write(command.bytes())
data = ser.read(int.from_bytes(command.arg, byteorder='big') + 4)
print('received', data)
do_r = do_read
def do_write(self, arg):
"""write addy data: Write _data_ to the c256, starting at address _addy_."""
try:
command = parseCommand(CMD.WRITE, *arg.split())
error = command.errorMessage()
except Exception as e:
error = e
if debug: raise e
if error:
print('error:', error)
else:
print('emitting', command.bytes())
ser.write(command.bytes())
data = ser.read(4)
print('received', data)
do_w = do_write
def do_load(self, arg):
"""load addy filename: Write the data in _filename_ to the c256, starting at _addy_."""
args = arg.split()
try:
command = parseCommand(CMD.WRITE, args[0], pathlib.Path(args[1]).read_bytes())
error = command.errorMessage()
except Exception as e:
error = e
if debug: raise e
if error:
print('error:', error)
else:
print('emitting', command.bytes())
ser.write(command.bytes())
data = ser.read(4)
print('received', data)
do_l = do_load
def do_quit(self, arg):
"""Close this serial console."""
print("Bye-bye!")
return True
do_q = do_quit
class ParsedCommand:
def __init__(self, command:bytes):
"""Create a new (empty) command."""
self.command = command
if command == CMD.READ:
self.direction = TXHDR
elif command == CMD.WRITE:
self.direction = RXHDR
else:
raise ValueError(f'Command ({command}) neither read ({CMD.READ}) nor write ({CMD.WRITE}).')
self.addy = b''
self.arg = b''
def size(self) -> bytes:
"""Compute the size of data."""
return len(self.arg).to_bytes(2, 'big')
def lrc(self) -> bytes:
"""Compute logical redundancy check."""
check = 0x00
for byte in (self.command + self.direction + self.addy + self.arg):
check ^= byte
return bytes([check])
def errorMessage(self) -> str:
"""Return an error message str. Empty if no errors."""
if self.command == CMD.READ:
if self.direction != TXHDR:
return "Invalid READ."
if not self.addy:
return "Missing target memory address."
if not self.arg:
return "Missing how much memory to read."
elif self.command == CMD.WRITE:
if self.direction != RXHDR:
return "Invalid WRITE."
if not self.addy:
return "Missing target memory address."
if not self.arg:
return "Missing what to write."
else:
return f"Unknown command code {self.command}."
return ''
def setAddy(self, addy:int):
"""Set the target address of the operation, on the c256."""
self.addy = addy.to_bytes(3, 'big')
def setSize(self, size:int):
"""Set amount of data to read from c256."""
if self.command != CMD.READ:
raise Exception("Can only set size to read when reading.")
self.arg = size.to_bytes(2, 'big')
def setData(self, data:bytes):
"""Set data to write to c256."""
if self.command != CMD.WRITE:
raise Exception("Can only set data to write when writing.")
if type(data) is not bytes:
raise Exception(f"Data must be specified as bytes. (eg. b'\\x01\\xff') Data was type {type(data)}.")
if len(data) > 0xFFFF:
raise ValueError(f"Too much data to write. (got {len(data)} bytes, max is {0xFFFF} bytes)")
self.arg = data
def bytes(self) -> bytes:
"""Return the bytestream for this command."""
if self.command == CMD.READ:
return self.command + self.direction + self.addy + self.arg + self.lrc()
elif self.command == CMD.WRITE:
return self.command + self.direction + self.addy + self.size() + self.arg + self.lrc()
else:
raise Exception(f'Unknown command ({command}) to serialize.')
def parseCommand(command:bytes, *args:str) -> ParsedCommand:
cmd = ParsedCommand(command)
if len(args) != 2:
raise Exception(f'Command requires 2 space-separated arguments, got {len(args)}.')
cmd.setAddy(ast.literal_eval(args[0]))
if command == CMD.READ:
cmd.setSize(args[1] if type(args[1]) is bytes else ast.literal_eval(args[1]))
elif command == CMD.WRITE:
cmd.setData(args[1] if type(args[1]) is bytes else ast.literal_eval(args[1]))
else:
raise ValueError(f'Command ({command}) neither read ({CMD.READ}) nor write ({CMD.WRITE}).')
return cmd
if __name__ == '__main__':
SerialShell().cmdloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment