def open(self, rs=False, cs=1000, ds=1000, cd=0): """ Open the serial connection. """ self._serial.open() # handshake # by default, RTS is up, DTR down # RTS can be suppressed, DTR only accessible through machine ports # https://lbpe.wikispaces.com/AccessingSerialPort if not rs: self._serial.setRTS(True) now = datetime.datetime.now() timeout_cts = now + datetime.timedelta(microseconds=cs) timeout_dsr = now + datetime.timedelta(microseconds=ds) timeout_cd = now + datetime.timedelta(microseconds=cd) have_cts, have_dsr, have_cd = False, False, False while ((now < timeout_cts and not have_cts) and (now < timeout_dsr and not have_dsr) and (now < timeout_cd and not have_cd)): now = datetime.datetime.now() have_cts = have_cts and self._serial.getCTS() have_dsr = have_dsr and self._serial.getDSR() have_cts = have_cd and self._serial.getCD() # give CPU some time off backend.wait(suppress_events=True) # only check for status if timeouts are set > 0 # http://www.electro-tech-online.com/threads/qbasic-serial-port-control.19286/ # https://measurementsensors.honeywell.com/ProductDocuments/Instruments/008-0385-00.pdf if ((cs > 0 and not have_cts) or (ds > 0 and not have_dsr) or (cd > 0 and not have_cd)): raise error.RunError(error.DEVICE_TIMEOUT) self.is_open = True
def wait(addr, ander, xorer): """ Wait untial an emulated machine port has a specified value. """ store_suspend = state.basic_state.events.suspend_all state.basic_state.events.suspend_all = True while (inp(addr) ^ xorer) & ander == 0: backend.wait() state.basic_state.events.suspend_all = store_suspend
def inp(port): """ Get the value in an emulated machine port. """ # keyboard if port == 0x60: backend.wait() return state.console_state.keyb.last_scancode # game port (joystick) elif port == 0x201: value = ( (not backend.stick_is_firing[0][0]) * 0x40 + (not backend.stick_is_firing[0][1]) * 0x20 + (not backend.stick_is_firing[1][0]) * 0x10 + (not backend.stick_is_firing[1][1]) * 0x80) decay = (timedate.timer_milliseconds() - joystick_out_time) % 86400000 if decay < backend.stick_axis[0][0] * joystick_time_factor: value += 0x04 if decay < backend.stick_axis[0][1] * joystick_time_factor: value += 0x02 if decay < backend.stick_axis[1][0] * joystick_time_factor: value += 0x01 if decay < backend.stick_axis[1][1] * joystick_time_factor: value += 0x08 return value else: return 0
def wait_music(self, wait_length=0): """ Wait until a given number of notes are left on the queue. """ while ( self.queue_length(0) > wait_length or self.queue_length(1) > wait_length or self.queue_length(2) > wait_length ): backend.wait()
def read(self, num=1): """ Read num characters from the port as a string; blocking """ out = '' while len(out) < num: # non blocking read self.check_read() to_read = min(len(self._in_buffer), num - len(out)) out += str(self._in_buffer[:to_read]) del self._in_buffer[:to_read] # allow for break & screen updates backend.wait() self.last_read = out[-1:] return out
def read_raw(self, num=-1): """ Read num characters from the port as a string; blocking """ if num == -1: # read whole buffer, non-blocking self.check_read() out = self.in_buffer del self.in_buffer[:] else: out = '' while len(out) < num: # non blocking read self.check_read() to_read = min(len(self.in_buffer), num - len(out)) out += str(self.in_buffer[:to_read]) del self.in_buffer[:to_read] # allow for break & screen updates backend.wait() return out
def read_raw(self, num=-1): """ Read num characters from the port as a string; blocking """ if num == -1: # read whole buffer, non-blocking self.check_read() out = self.in_buffer del self.in_buffer[:] else: out = '' while len(out) < num: # non blocking read self.check_read() to_read = min(len(self.in_buffer), num - len(out)) out += str(self.in_buffer[:to_read]) del self.in_buffer[:to_read] # allow for break & screen updates # this also allows triggering BASIC events backend.wait() return out
def inp(port): """ Get the value in an emulated machine port. """ # keyboard if port == 0x60: backend.wait() return state.console_state.keyb.last_scancode # game port (joystick) elif port == 0x201: value = ((not backend.stick_is_firing[0][0]) * 0x40 + (not backend.stick_is_firing[0][1]) * 0x20 + (not backend.stick_is_firing[1][0]) * 0x10 + (not backend.stick_is_firing[1][1]) * 0x80) decay = (timedate.timer_milliseconds() - joystick_out_time) % 86400000 if decay < backend.stick_axis[0][0] * joystick_time_factor: value += 0x04 if decay < backend.stick_axis[0][1] * joystick_time_factor: value += 0x02 if decay < backend.stick_axis[1][0] * joystick_time_factor: value += 0x01 if decay < backend.stick_axis[1][1] * joystick_time_factor: value += 0x08 return value else: return 0
def inp(port): """ Get the value in an emulated machine port. """ # keyboard if port == 0x60: backend.wait() return state.console_state.keyb.last_scancode # game port (joystick) elif port == 0x201: value = ( (not inputs.stick_is_firing[0][0]) * 0x40 + (not inputs.stick_is_firing[0][1]) * 0x20 + (not inputs.stick_is_firing[1][0]) * 0x10 + (not inputs.stick_is_firing[1][1]) * 0x80) decay = (timedate.timer_milliseconds() - joystick_out_time) % 86400000 if decay < inputs.stick_axis[0][0] * joystick_time_factor: value += 0x04 if decay < inputs.stick_axis[0][1] * joystick_time_factor: value += 0x02 if decay < inputs.stick_axis[1][0] * joystick_time_factor: value += 0x01 if decay < inputs.stick_axis[1][1] * joystick_time_factor: value += 0x08 return value elif port in (0x379, 0x279): # parallel port input ports # http://www.aaroncake.net/electronics/qblpt.htm # http://retired.beyondlogic.org/spp/parallel.htm lpt_port_nr = 0 if port >= 0x378 else 1 base_addr = {0: 0x378, 1: 0x278} if lpt_device[lpt_port_nr].stream is None: return 0 # get status port busy, ack, paper, select, err = lpt_device[lpt_port_nr].stream.get_status() return busy * 0x80 | ack * 0x40 | paper * 0x20 | select * 0x10 | err * 0x8 else: # serial port machine ports # http://www.qb64.net/wiki/index.php/Port_Access_Libraries#Serial_Communication_Registers # http://control.com/thread/1026221083 for base_addr, com_port_nr in com_base.iteritems(): com_port = com_device[com_port_nr] if com_port.stream is None: continue # Line Control Register: base_address + 3 (r/w) if port == base_addr + 3: _, parity, bytesize, stopbits = com_port.stream.get_params() value = com_enable_baud_write[com_port_nr] * 0x80 value += com_break[com_port_nr] * 0x40 value += {'S': 0x38, 'M': 0x28, 'E': 0x18, 'O': 0x8, 'N': 0}[parity] if stopbits > 1: value += 0x4 value += bytesize - 5 return value # Line Status Register: base_address + 5 (read only) elif port == base_addr + 5: # not implemented return 0 # Modem Status Register: base_address + 6 (read only) elif port == base_addr + 6: cd, ri, dsr, cts = com_port.stream.get_pins() # delta bits not implemented return (cd*0x80 + ri*0x40 + dsr*0x20 + cts*0x10) # addr isn't one of the covered ports return 0
def get_low_memory(addr): """ Retrieve data from low memory. """ addr -= low_segment*0x10 # from MEMORY.ABC: PEEKs and POKEs (Don Watkins) # http://www.qbasicnews.com/abc/showsnippet.php?filename=MEMORY.ABC&snippet=6 # &h40:&h17 keyboard flag # &H80 - Insert state active # &H40 - CapsLock state has been toggled # &H20 - NumLock state has been toggled # &H10 - ScrollLock state has been toggled # &H08 - Alternate key depressed # &H04 - Control key depressed # &H02 - Left shift key depressed # &H01 - Right shift key depressed # &h40:&h18 keyboard flag # &H80 - Insert key is depressed # &H40 - CapsLock key is depressed # &H20 - NumLock key is depressed # &H10 - ScrollLock key is depressed # &H08 - Suspend key has been toggled backend.wait() # 108-115 control Ctrl-break capture; not implemented (see PC Mag POKEs) # 1040 monitor type if addr == 124: return ram_font_addr % 256 elif addr == 125: return ram_font_addr // 256 elif addr == 126: return memory.ram_font_segment % 256 elif addr == 127: return memory.ram_font_segment // 256 elif addr == 1040: if backend.mono_monitor: # mono return 48 + 6 else: # 80x25 graphics return 32 + 6 elif addr == 1041: return 18 + 64 * (1 + (backend.devices['LPT2:'] != None) + (backend.devices['LPT3:'] != None)) elif addr == 1047: return state.console_state.keyb.mod # not implemented: peek(1048)==4 if sysrq pressed, 0 otherwise elif addr == 1048: return 0 elif addr == 1049: return int(state.console_state.keyb.keypad_ascii or 0)%256 elif addr == 1050: # keyboard ring buffer starts at n+1024; lowest 1054 return (state.console_state.keyb.buf.start*2 + key_buffer_offset) % 256 elif addr == 1051: return (state.console_state.keyb.buf.start*2 + key_buffer_offset) // 256 elif addr == 1052: # ring buffer ends at n + 1023 return (state.console_state.keyb.buf.stop()*2 + key_buffer_offset) % 256 elif addr == 1053: return (state.console_state.keyb.buf.stop()*2 + key_buffer_offset) // 256 elif addr in range(1024+key_buffer_offset, 1024+key_buffer_offset+32): index = (addr-1024-key_buffer_offset)//2 odd = (addr-1024-key_buffer_offset)%2 c = state.console_state.keyb.buf.ring_read(index) if c[0] == '\0': return ord(c[-1]) if odd else 0xe0 else: # should return scancode here, not implemented return 0 if odd else ord(c[0]) # 1097 screen mode number elif addr == 1097: # these are the low-level mode numbers used by mode switching interrupt cval = state.console_state.screen.colorswitch % 2 if state.console_state.screen.mode.is_text_mode: if (backend.video_capabilities in ('mda', 'ega_mono') and state.console_state.screen.mode.width == 80): return 7 return (state.console_state.screen.mode.width == 40)*2 + cval elif state.console_state.screen.mode.name == '320x200x4': return 4 + cval else: mode_num = {'640x200x2': 6, '160x200x16': 8, '320x200x16pcjr': 9, '640x200x4': 10, '320x200x16': 13, '640x200x16': 14, '640x350x4': 15, '640x350x16': 16, '640x400x2': 0x40, '320x200x4pcjr': 4 } # '720x348x2': ? # hercules - unknown try: return mode_num[state.console_state.screen.mode.name] except KeyError: return 0xff # 1098, 1099 screen width elif addr == 1098: return state.console_state.screen.mode.width % 256 elif addr == 1099: return state.console_state.screen.mode.width // 256 # 1100, 1101 graphics page buffer size (32k for screen 9, 4k for screen 0) # 1102, 1103 zero (PCmag says graphics page buffer offset) elif addr == 1100: return state.console_state.screen.mode.page_size % 256 elif addr == 1101: return state.console_state.screen.mode.page_size // 256 # 1104 + 2*n (cursor column of page n) - 1 # 1105 + 2*n (cursor row of page n) - 1 # we only keep track of one row,col position elif addr in range(1104, 1120, 2): return state.console_state.col - 1 elif addr in range(1105, 1120, 2): return state.console_state.row - 1 # 1120, 1121 cursor shape elif addr == 1120: return state.console_state.screen.cursor.to_line elif addr == 1121: return state.console_state.screen.cursor.from_line # 1122 visual page number elif addr == 1122: return state.console_state.screen.vpagenum # 1125 screen mode info elif addr == 1125: # bit 0: only in text mode? # bit 2: should this be colorswitch or colorburst_is_enabled? return ((state.console_state.screen.mode.width == 80) * 1 + (not state.console_state.screen.mode.is_text_mode) * 2 + state.console_state.screen.colorswitch * 4 + 8 + (state.console_state.screen.mode.name == '640x200x2') * 16 + blink_enabled * 32) # 1126 color elif addr == 1126: if state.console_state.screen.mode.name == '320x200x4': return (state.console_state.screen.palette.get_entry(0) + 32 * state.console_state.screen.cga4_palette_num) elif state.console_state.screen.mode.is_text_mode: return state.console_state.screen.border_attr % 16 # not implemented: + 16 "if current color specified through # COLOR f,b with f in [0,15] and b > 7 # 1296, 1297: zero (PCmag says data segment address) return -1
def wait_char(self): """ Wait for character, then return it but don't drop from queue. """ while self.buf.is_empty() and not redirect.input_closed: backend.wait() return self.buf.peek()
def get_char(self): """ Read any keystroke, nonblocking. """ backend.wait() return self.buf.getc()
def get_low_memory(addr): """ Retrieve data from low memory. """ addr -= low_segment * 0x10 # from MEMORY.ABC: PEEKs and POKEs (Don Watkins) # http://www.qbasicnews.com/abc/showsnippet.php?filename=MEMORY.ABC&snippet=6 # &h40:&h17 keyboard flag # &H80 - Insert state active # &H40 - CapsLock state has been toggled # &H20 - NumLock state has been toggled # &H10 - ScrollLock state has been toggled # &H08 - Alternate key depressed # &H04 - Control key depressed # &H02 - Left shift key depressed # &H01 - Right shift key depressed # &h40:&h18 keyboard flag # &H80 - Insert key is depressed # &H40 - CapsLock key is depressed # &H20 - NumLock key is depressed # &H10 - ScrollLock key is depressed # &H08 - Suspend key has been toggled backend.wait() # 108-115 control Ctrl-break capture; not implemented (see PC Mag POKEs) # 1040 monitor type if addr == 124: return ram_font_addr % 256 elif addr == 125: return ram_font_addr // 256 elif addr == 126: return memory.ram_font_segment % 256 elif addr == 127: return memory.ram_font_segment // 256 elif addr == 1040: if display.mono_monitor: # mono return 48 + 6 else: # 80x25 graphics return 32 + 6 # http://textfiles.com/programming/peekpoke.txt # "(PEEK (1041) AND 14)/2" WILL PROVIDE NUMBER OF RS232 PORTS INSTALLED. # "(PEEK (1041) AND 16)/16" WILL PROVIDE NUMBER OF GAME PORTS INSTALLED. # "(PEEK (1041) AND 192)/64" WILL PROVIDE NUMBER OF PRINTERS INSTALLED. elif addr == 1041: return (2 * ((state.io_state.devices['COM1:'].stream is not None) + (state.io_state.devices['COM2:'].stream is not None)) + 16 + 64 * ((state.io_state.devices['LPT1:'].stream is not None) + (state.io_state.devices['LPT2:'].stream is not None) + (state.io_state.devices['LPT3:'].stream is not None))) elif addr == 1047: return state.console_state.keyb.mod # not implemented: peek(1048)==4 if sysrq pressed, 0 otherwise elif addr == 1048: return 0 elif addr == 1049: return int(state.console_state.keyb.keypad_ascii or 0) % 256 elif addr == 1050: # keyboard ring buffer starts at n+1024; lowest 1054 return (state.console_state.keyb.buf.start * 2 + key_buffer_offset) % 256 elif addr == 1051: return (state.console_state.keyb.buf.start * 2 + key_buffer_offset) // 256 elif addr == 1052: # ring buffer ends at n + 1023 return (state.console_state.keyb.buf.stop() * 2 + key_buffer_offset) % 256 elif addr == 1053: return (state.console_state.keyb.buf.stop() * 2 + key_buffer_offset) // 256 elif addr in range(1024 + key_buffer_offset, 1024 + key_buffer_offset + 32): index = (addr - 1024 - key_buffer_offset) // 2 odd = (addr - 1024 - key_buffer_offset) % 2 c, scan = state.console_state.keyb.buf.ring_read(index) if odd: return scan elif c == '': return 0 else: # however, arrow keys (all extended scancodes?) give 0xe0 instead of 0 return ord(c[0]) # 1097 screen mode number elif addr == 1097: # these are the low-level mode numbers used by mode switching interrupt cval = state.console_state.screen.colorswitch % 2 if state.console_state.screen.mode.is_text_mode: if (display.video_capabilities in ('mda', 'ega_mono') and state.console_state.screen.mode.width == 80): return 7 return (state.console_state.screen.mode.width == 40) * 2 + cval elif state.console_state.screen.mode.name == '320x200x4': return 4 + cval else: mode_num = { '640x200x2': 6, '160x200x16': 8, '320x200x16pcjr': 9, '640x200x4': 10, '320x200x16': 13, '640x200x16': 14, '640x350x4': 15, '640x350x16': 16, '640x400x2': 0x40, '320x200x4pcjr': 4 } # '720x348x2': ? # hercules - unknown try: return mode_num[state.console_state.screen.mode.name] except KeyError: return 0xff # 1098, 1099 screen width elif addr == 1098: return state.console_state.screen.mode.width % 256 elif addr == 1099: return state.console_state.screen.mode.width // 256 # 1100, 1101 graphics page buffer size (32k for screen 9, 4k for screen 0) # 1102, 1103 zero (PCmag says graphics page buffer offset) elif addr == 1100: return state.console_state.screen.mode.page_size % 256 elif addr == 1101: return state.console_state.screen.mode.page_size // 256 # 1104 + 2*n (cursor column of page n) - 1 # 1105 + 2*n (cursor row of page n) - 1 # we only keep track of one row,col position elif addr in range(1104, 1120, 2): return state.console_state.col - 1 elif addr in range(1105, 1120, 2): return state.console_state.row - 1 # 1120, 1121 cursor shape elif addr == 1120: return state.console_state.screen.cursor.to_line elif addr == 1121: return state.console_state.screen.cursor.from_line # 1122 visual page number elif addr == 1122: return state.console_state.screen.vpagenum # 1125 screen mode info elif addr == 1125: # bit 0: only in text mode? # bit 2: should this be colorswitch or colorburst_is_enabled? return ((state.console_state.screen.mode.width == 80) * 1 + (not state.console_state.screen.mode.is_text_mode) * 2 + state.console_state.screen.colorswitch * 4 + 8 + (state.console_state.screen.mode.name == '640x200x2') * 16 + blink_enabled * 32) # 1126 color elif addr == 1126: if state.console_state.screen.mode.name == '320x200x4': return (state.console_state.screen.palette.get_entry(0) + 32 * state.console_state.screen.cga4_palette_num) elif state.console_state.screen.mode.is_text_mode: return state.console_state.screen.border_attr % 16 # not implemented: + 16 "if current color specified through # COLOR f,b with f in [0,15] and b > 7 # 1296, 1297: zero (PCmag says data segment address) return -1
def wait_all_music(self): """ Wait until all music (not noise) has finished playing. """ while self.is_playing(0) or self.is_playing(1) or self.is_playing(2): backend.wait()
def inp(port): """ Get the value in an emulated machine port. """ # keyboard if port == 0x60: backend.wait() return state.console_state.keyb.last_scancode # game port (joystick) elif port == 0x201: value = ((not inputs.stick_is_firing[0][0]) * 0x40 + (not inputs.stick_is_firing[0][1]) * 0x20 + (not inputs.stick_is_firing[1][0]) * 0x10 + (not inputs.stick_is_firing[1][1]) * 0x80) decay = (timedate.timer_milliseconds() - joystick_out_time) % 86400000 if decay < inputs.stick_axis[0][0] * joystick_time_factor: value += 0x04 if decay < inputs.stick_axis[0][1] * joystick_time_factor: value += 0x02 if decay < inputs.stick_axis[1][0] * joystick_time_factor: value += 0x01 if decay < inputs.stick_axis[1][1] * joystick_time_factor: value += 0x08 return value elif port in (0x379, 0x279): # parallel port input ports # http://www.aaroncake.net/electronics/qblpt.htm # http://retired.beyondlogic.org/spp/parallel.htm lpt_port_nr = 0 if port >= 0x378 else 1 base_addr = {0: 0x378, 1: 0x278} if lpt_device[lpt_port_nr].stream is None: return 0 # get status port busy, ack, paper, select, err = lpt_device[ lpt_port_nr].stream.get_status() return busy * 0x80 | ack * 0x40 | paper * 0x20 | select * 0x10 | err * 0x8 else: # serial port machine ports # http://www.qb64.net/wiki/index.php/Port_Access_Libraries#Serial_Communication_Registers # http://control.com/thread/1026221083 for base_addr, com_port_nr in com_base.iteritems(): com_port = com_device[com_port_nr] if com_port.stream is None: continue # Line Control Register: base_address + 3 (r/w) if port == base_addr + 3: _, parity, bytesize, stopbits = com_port.stream.get_params() value = com_enable_baud_write[com_port_nr] * 0x80 value += com_break[com_port_nr] * 0x40 value += { 'S': 0x38, 'M': 0x28, 'E': 0x18, 'O': 0x8, 'N': 0 }[parity] if stopbits > 1: value += 0x4 value += bytesize - 5 return value # Line Status Register: base_address + 5 (read only) elif port == base_addr + 5: # not implemented return 0 # Modem Status Register: base_address + 6 (read only) elif port == base_addr + 6: cd, ri, dsr, cts = com_port.stream.get_pins() # delta bits not implemented return (cd * 0x80 + ri * 0x40 + dsr * 0x20 + cts * 0x10) # addr isn't one of the covered ports return 0
def wait_music(self, wait_length=0): """ Wait until a given number of notes are left on the queue. """ while (self.queue_length(0) > wait_length or self.queue_length(1) > wait_length or self.queue_length(2) > wait_length): backend.wait()
def wait_all_music(self): """ Wait until all music (not noise) has finished playing. """ while (self.is_playing(0) or self.is_playing(1) or self.is_playing(2)): backend.wait()