Last active
September 23, 2018 21:12
-
-
Save DDR0/c10682d0ff230ce462bf429ad90eeee0 to your computer and use it in GitHub Desktop.
Commodore 256 Serial Shell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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