#!/usr/bin/env python # -*- coding: utf-8 -*- # I, Danny Milosavljevic, hereby place this file into the public domain. """ this gets a stream of data from the bus. Each communication message is 1 byte, with the following contents: bit 3 = ATN bit 4 = CLOCK bit 5 = DATA d1541 will get each and every byte that wanders on the bus (and be able to write its own new values back, which will be ORed with the values of all the other devices). It can ignore things, however, as long as ATN is not set. ATN commands tell the wishes of the computer in that matter: 1) LISTEN means the device with the given ID and secondary address is supposed to listen to the computer. 2) UNLISTEN means the devices can stop listening now. 3) TALK means the device with the given ID and secondary address is supposed to talk to the computer. 4) UNTALK means the devices can stop talking now. In order for ATN commands not to need a complicated state machine, the control codes have marker bits (vgl. UTF-8): $20|device LISTEN $3F UNLISTEN $40|device TALK $5F UNTALK $60|channel OPEN CHANNEL/DATA $E0|channel CLOSE $F0|channel OPEN Example transmission is: /$28 /$F8 filename /$3F /$48 /$60 ... /$5F /$28 /$E0 /$3F Where / means "with ATN". Note that the drive can change its own device numbers by: print #15, "M-W" $77 $0 $2 $20|dev $40|dev """ import select import sys import StringIO import os #sys.path = [os.path.join(os.path.dirname(sys.argv[0]), "..", "c64")] + sys.path from loaders import d64 listen_device_ID = 8 # TODO pass that in from the caller? talk_device_ID = 8 image_name = sys.argv[-1] block_accessor = d64.BlockAccessor(open(image_name, "rb")) file_accessor = d64.FileAccessor(block_accessor) directory_stream = file_accessor.open(d64.block_from_TS(18, 1)) #for entry in d64.parse_directory(directory_stream): # print(entry) C_LISTEN = 0x20 C_OPEN = 0xF0 C_OPEN_DATA = 0x60 C_UNLISTEN = 0x3F C_TALK = 0x40 C_UNTALK = 0x5F C_CLOSE = 0xE0 B_ATN = False B_EOI = False errors = { 0: "OK", 1: "FILES SCRATCHED", 3: "UNIMPLEMENTED", 20: "READ ERROR", 21: "READ ERROR", 22: "READ ERROR", 23: "READ ERROR", 24: "READ ERROR", 25: "WRITE ERROR", 26: "WRITE PROTECT ON", 27: "READ ERROR", 28: "WRITE ERROR", 29: "DISK ID MISMATCH", 30: "SYNTAX ERROR", 31: "SYNTAX ERROR", 32: "SYNTAX ERROR", 33: "SYNTAX ERROR", 34: "SYNTAX ERROR", 60: "WRITE FILE OPEN", 61: "FILE NOT OPEN", 62: "FILE NOT FOUND", 63: "FILE EXISTS", 64: "FILE TYPE MISMATCH", 65: "NO BLOCK", 66: "ILLEGAL TRACK OR SECTOR", 70: "NO CHANNEL", 71: "DIR ERROR", 72: "DISK FULL", 73: "CBM DOS V2.6 1541", 74: "DRIVE NOT READY", } # ,%02d,%02d\x0D def parse_input(): while True: chunk = os.read(0, 1) if chunk == "": # EOF break lines = ord(chunk) yield lines def turnaround(): sys.stdout.write("\033[T") sys.stdout.flush() def stay(): sys.stdout.write(".") sys.stdout.flush() def transmit_EOF(): sys.stdout.write("\033[Q") def transmit(text): # use once per session sys.stdout.write(text[:-1]) transmit_EOF() sys.stdout.write(text[-1]) sys.stdout.flush() turnaround() class Channel(object): def __init__(self): self.buffer = StringIO.StringIO() self.data_offset = 0 def process(self, item): #print >>sys.stderr, "ITEM PROC", item self.buffer.write(item) def open_data(self): self.data_offset = self.buffer.tell() self.prepare_data() def prepare_data(self): name = self.buffer.getvalue() #self.buffer = StringIO.StringIO() #self.data_offset = 0 print >>sys.stderr, "dum dum dum DUM", name # TODO wait for turnaround """ pointer to next line (2 bytes) - 0 for none line number (2 bytes) $99 poke for the print keyword space and quote characters as literals petascii for the text quote character literal 0 at the end of the line """ # I'm not sure what the first byte is, the C64 doesn't get it. Bug somewhere. # The other file header is two bytes for the load address (should be $801). Ignored sometimes. transmit("\x00\x01\x08\x0D\x08\x0A\x00\x99\x20\x22\x20\x20\x20\x22\x00\x00\x00\x00\x00") # trailing junk (2) # 801 802 803 804 805 806 807 808 809 80A 80B 80C 80D def close(self): pass def process_command(self): pass def open_channel(secondary_number): channel = Channel() return channel #f = open("/tmp/LOG", "wb") B_ATN = False B_EOI = False NONE_MODE, LISTENING_MODE, TALKING_MODE = range(3) mode = NONE_MODE channel_index = None channels = {} channels[15] = open_channel(15) #channels[0] = open_channel(0) for item in parse_input(): #f.write(item) #f.flush() if len(item) > 1: # ATN or EOI change. if item == "\033[1A": B_ATN = True elif item == "\033[0A": B_ATN = False elif item == "\033[1E": B_EOI = True elif item == "\033[0E": B_EOI = False else: if B_ATN: if item == chr(C_LISTEN | listen_device_ID): mode = LISTENING_MODE elif (ord(item) & 0xF0) == C_OPEN: ix = ord(item) & 0xF channels[ix] = open_channel(ix) channel_index = ix elif (ord(item) & 0xF0) == C_CLOSE: ix = ord(item) & 0xF if ix in channels: channels[ix].close() del channels[ix] if channel_index == ix: channel_index = None elif (ord(item) & 0xF0) == C_OPEN_DATA: ix = ord(item) & 0xF channel_index = ix channels[channel_index].open_data() elif item == chr(C_UNLISTEN): if channel_index is not None: channels[channel_index].process_command() # TODO is it correct that this also does UNTALK implicitly? mode = NONE_MODE elif item == chr(C_TALK | talk_device_ID): mode = TALKING_MODE # TODO notify channel? elif item == chr(C_UNTALK): # TODO is it correct that this also does UNTALK implicitly? mode = NONE_MODE # TODO notify channel? else: if channel_index is not None: channels[channel_index].process(item) else: print >>sys.stderr, "D1541: warning: C64 is saying something to the wall" #print >>sys.stderr, "item %r" % (item, ) #stay() # FIXME remove #stay() # FIXME remove #print(hex(ord(item))), #if ord(item) == 0x60: # FIXME # turnaround() #else: stay()