def setUpClass(cls): FtdiTestCase.setUpClass() if VirtLoader: cls.loader = VirtLoader() with open('pyftdi/tests/resources/ft232r.yaml', 'rb') as yfp: cls.loader.load(yfp) vftdi = cls.loader.get_virtual_ftdi(1, 1) vport = vftdi.get_port(1) # create virtual connections as real HW in_pins = [vport[pos] for pos in range(4)] out_pins = [vport[pos] for pos in range(4, 8)] for in_pin, out_pin in zip(in_pins, out_pins): out_pin.connect_to(in_pin) if cls.url == 'ftdi:///1': # assumes that if not specific device is used, and a multiport # device is connected, there is no loopback wires between pins of # the same port. This hack allows to run the same test with a # FT232H, then a FT2232H for ex, to that with two test sessions # the whole test set is run. If a specific device is selected # assume the HW always match the expected configuration. ftdi = Ftdi() ftdi.open_from_url(cls.url) count = ftdi.device_port_count ftdi.close() cls.skip_loopback = count > 1 else: cls.skip_loopback = False
def setUpClass(cls): FtdiTestCase.setUpClass() if VirtLoader: cls.loader = VirtLoader() with open('pyftdi/tests/resources/ft2232h.yaml', 'rb') as yfp: cls.loader.load(yfp) vftdi = cls.loader.get_virtual_ftdi(1, 1) vport1 = vftdi.get_port(1) vport2 = vftdi.get_port(2) # create virtual connections as real HW in_pins = [vport1[pos] for pos in range(8)] out_pins = [vport2[pos] for pos in range(8)] for in_pin, out_pin in zip(in_pins, out_pins): out_pin.connect_to(in_pin) ftdi = Ftdi() ftdi.open_from_url(cls.url) count = ftdi.device_port_count pos = ftdi.port_index ftdi.close() if pos != 1: raise ValueError("FTDI interface should be the device's first") if count < 2: raise SkipTest('FTDI device is not a multi-port device') url = cls.url[:-1] cls.urls = [f'{url}1', f'{url}2']
def setUpClass(cls): FtdiTestCase.setUpClass() if VirtLoader: cls.loader = VirtLoader() with open('pyftdi/tests/resources/ft2232h.yaml', 'rb') as yfp: cls.loader.load(yfp) vftdi = cls.loader.get_virtual_ftdi(1, 1) vport1 = vftdi.get_port(1) vport2 = vftdi.get_port(2) # create virtual connections as real HW in_pins = [vport1[pos] for pos in range(16)] out_pins = [vport2[pos] for pos in range(16)] for in_pin, out_pin in zip(in_pins, out_pins): out_pin.connect_to(in_pin) # prevent from using the tracer twice (Ftdi & VirtualFtdi) cls.debug_mpsse = False else: cls.debug_mpsse = cls.debug url = environ.get('FTDI_DEVICE', 'ftdi:///1') ftdi = Ftdi() ftdi.open_from_url(url) count = ftdi.device_port_count width = ftdi.port_width ftdi.close() if count < 2: raise SkipTest('FTDI device is not a multi-port device') if width < 2: raise SkipTest('FTDI device does not support wide ports') url = url[:-1] cls.urls = [f'{url}1', f'{url}2']
def test_output_gpio(self): """Simple test to demonstrate ouput bit-banging on CBUS. You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose EEPROM has been configured to support GPIOs on CBUS0 and CBUS3. Hard-wiring is required to run this test: * CBUS0 (output) should be connected to CTS (input) * CBUS3 (output) should be connected to DSR (input) """ ftdi = Ftdi() ftdi.open_from_url(self.url) # sanity check: device should support CBUS feature self.assertEqual(ftdi.has_cbus, True) eeprom = FtdiEeprom() eeprom.connect(ftdi) # sanity check: device should have been configured for CBUS GPIOs self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001) # configure CBUS0 and CBUS3 as output ftdi.set_cbus_direction(0b1001, 0b1001) # no input pin available self.assertRaises(FtdiError, ftdi.get_cbus_gpio) for cycle in range(40): value = cycle & 0x3 # CBUS0 and CBUS3 cbus = ((value & 0x2) << 2) | value & 0x1 # for now, need a digital/logic analyzer to validate output ftdi.set_cbus_gpio(cbus) # CBUS0 is connected to CTS, CBUS3 to DSR # need to inverse logical level as RS232 uses negative logic sig = int(not ftdi.get_cts()) | (int(not ftdi.get_dsr()) << 1) self.assertEqual(value, sig)
def test_input_gpio(self): """Simple test to demonstrate input bit-banging on CBUS. You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose EEPROM has been configured to support GPIOs on CBUS0 and CBUS3. Hard-wiring is required to run this test: * CBUS0 (input) should be connected to RTS (output) * CBUS3 (input) should be connected to DTR (output) """ ftdi = Ftdi() ftdi.open_from_url(self.url) # sanity check: device should support CBUS feature self.assertEqual(ftdi.has_cbus, True) eeprom = FtdiEeprom() eeprom.connect(ftdi) # sanity check: device should have been configured for CBUS GPIOs self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001) # configure CBUS0 and CBUS3 as input ftdi.set_cbus_direction(0b1001, 0b0000) # no output pin available self.assertRaises(FtdiError, ftdi.set_cbus_gpio, 0) for cycle in range(40): rts = bool(cycle & 0x1) dtr = bool(cycle & 0x2) ftdi.set_rts(rts) ftdi.set_dtr(dtr) # need to inverse logical level as RS232 uses negative logic cbus = ~ftdi.get_cbus_gpio() sig = (cbus & 0x1) | ((cbus & 0x8) >> 2) value = cycle & 0x3 self.assertEqual(value, sig)
def test_close_on_disconnect(self): """Validate close after disconnect.""" log = logging.getLogger('pyftdi.tests.ftdi') url = environ.get('FTDI_DEVICE', 'ftdi:///1') ftdi = Ftdi() ftdi.open_from_url(url) self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI') print('Please disconnect FTDI device') while ftdi.is_connected: try: ftdi.poll_modem_status() except FtdiError: break sleep(0.1) ftdi.close() print('Please reconnect FTDI device') while True: UsbTools.flush_cache() try: ftdi.open_from_url(url) except (FtdiError, UsbToolsError): log.debug('FTDI device not detected') sleep(0.1) except ValueError: log.warning('FTDI device not initialized') ftdi.close() sleep(0.1) else: log.info('FTDI device detected') break ftdi.poll_modem_status() ftdi.close()
def test(self): ftdi = Ftdi() ftdi.open_from_url(URL) for baudrate in self.BAUDRATES: actual, _, _ = ftdi._convert_baudrate(baudrate) ratio = baudrate / actual self.assertTrue(0.97 <= ratio <= 1.03, "Invalid baudrate")
def test_eeprom_read(self): """Check full read sequence.""" ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') data = ftdi.read_eeprom() self.assertEqual(len(data), 0x400) ftdi.close()
def test_dump(self): """Check EEPROM full content.""" ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') self._restore_eeprom(ftdi) ref_data = bytes(list(range(256))) size = len(ref_data) data = ftdi.read_eeprom() self.assertEqual(len(data), size) self.assertEqual(ref_data, data) ftdi.close()
def setUpClass(cls): FtdiTestCase.setUpClass() if VirtLoader: cls.loader = VirtLoader() with open(cls.TEST_CONFIG_FILENAME, 'rb') as yfp: cls.loader.load(yfp) if cls.url == 'ftdi:///1': ftdi = Ftdi() ftdi.open_from_url(cls.url) count = ftdi.device_port_count ftdi.close()
def test_simple_reset(self): """Demonstrate how to connect to an hotplugged FTDI device, i.e. an FTDI device that is connected after the initial attempt to enumerate it on the USB bus.""" url = environ.get('FTDI_DEVICE', 'ftdi:///1') ftdi = Ftdi() ftdi.open_from_url(url) self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI') ftdi.close() self.assertFalse(ftdi.is_connected, 'Unable to close connection') ftdi.open_from_url(url) self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI') ftdi.reset(False)
def test_hotplug_discovery(self): """Demonstrate how to connect to an hotplugged FTDI device, i.e. an FTDI device that is connected after the initial attempt to enumerate it on the USB bus.""" url = environ.get('FTDI_DEVICE', 'ftdi:///1') ftdi = Ftdi() timeout = now() + 5.0 # sanity check: bail out after 10 seconds while now() < timeout: try: ftdi.open_from_url(url) break except UsbToolsError: UsbTools.flush_cache() sleep(0.05) continue self.assertTrue(ftdi.is_connected, 'Unable to connect to FTDI') print('Connected to FTDI', url)
def test_230x(self): """Check simple GPIO write and read sequence.""" # load custom CBUS config, with: # CBUS0: GPIO (gpio) # CBUS1: GPIO (gpio) # CBUS0: DRIVE1 (forced to high level) # CBUS0: TXLED (eq. to highz for tests) with open('pyftdi/tests/resources/ft230x_io.yaml', 'rb') as yfp: self.loader.load(yfp) ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') self.assertEqual(ftdi.has_cbus, True) vftdi = self.loader.get_virtual_ftdi(1, 1) vport = vftdi.get_port(1) # CBUS0: in, CBUS1: out, CBUS2: in, CBUS3: out # however, only CBUS0 and CBUS1 are mapped as GPIO, # CBUS2 forced to 1 and CBUS3 not usable as IO # even if use mask is 1111 eeprom_mask = 0b0011 eeprom_force = 0b0100 cbus_mask = 0b1111 cbus_dir = 0b1010 ftdi.set_cbus_direction(cbus_mask, cbus_dir) cbus_out = 0b0011 # CBUS0: 1, CBUS1: 1 # however, only CBUS1 is out, so CBUS0 output value should be ignored ftdi.set_cbus_gpio(cbus_out) exp_out = cbus_dir & cbus_out exp_out &= eeprom_mask exp_out |= eeprom_force vcbus, vactive = vport.cbus self.assertEqual(vcbus, exp_out) self.assertEqual(vactive, eeprom_mask | eeprom_force) cbus_out = 0b0000 ftdi.set_cbus_gpio(cbus_out) exp_out = cbus_dir & cbus_out exp_out &= eeprom_mask exp_out |= eeprom_force vcbus, vactive = vport.cbus self.assertEqual(vcbus, exp_out) cbus_in = 0b0101 vport.cbus = cbus_in cbus = ftdi.get_cbus_gpio() exp_in = cbus_in & eeprom_mask self.assertEqual(cbus, exp_in) ftdi.close()
def test_lc231x(self): """Check simple GPIO write and read sequence.""" # load custom CBUS config, with: # CBUS0: GPIO (gpio) # CBUS1: TXLED # CBUS2: DRIVE0 (to light up RX green led) # CBUS3: GPIO (gpio) # only CBUS0 and CBUS3 are available on LC231X # CBUS1 is connected to TX led, CBUS2 to RX led with open('pyftdi/tests/resources/ft231x_cbus.yaml', 'rb') as yfp: self.loader.load(yfp) ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') self.assertEqual(ftdi.has_cbus, True) vftdi = self.loader.get_virtual_ftdi(1, 1) vport = vftdi.get_port(1) # CBUS0: in, CBUS1: out, CBUS2: in, CBUS3: out # however, only CBUS0 and CBUS3 are mapped as GPIO, # CBUS1 not usable as IO, CBUS2 is fixed to low # even if use mask is 1111 eeprom_mask = 0b1001 eeprom_force_low = 0b0100 cbus_mask = 0b1111 cbus_dir = 0b1010 ftdi.set_cbus_direction(cbus_mask, cbus_dir) cbus_out = 0b1111 # however, only CBUS0 & 3 are out, so CBUS1/CBUS2 should be ignored ftdi.set_cbus_gpio(cbus_out) exp_out = cbus_dir & cbus_out exp_out &= eeprom_mask vcbus, vactive = vport.cbus self.assertEqual(vcbus, exp_out) self.assertEqual(vactive, eeprom_mask | eeprom_force_low) cbus_out = 0b0000 ftdi.set_cbus_gpio(cbus_out) exp_out = cbus_dir & cbus_out exp_out &= eeprom_mask vcbus, vactive = vport.cbus self.assertEqual(vcbus, exp_out) cbus_in = 0b0101 vport.cbus = cbus_in cbus = ftdi.get_cbus_gpio() exp_in = cbus_in & eeprom_mask self.assertEqual(cbus, exp_in) ftdi.close()
def test_randow_access_write(self): """Check EEPROM random write access.""" ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') bus, address, _ = ftdi.usb_path vftdi = self.loader.get_virtual_ftdi(bus, address) self._restore_eeprom(ftdi) checksum1 = vftdi.eeprom[-2:] orig_data = vftdi.eeprom[:8] ref_data = b'ABCD' ftdi.write_eeprom(0, ref_data, dry_run=False) checksum2 = vftdi.eeprom[-2:] # verify the data have been written self.assertEqual(vftdi.eeprom[:4], ref_data) # verify the data have not been overwritten self.assertEqual(vftdi.eeprom[4:8], orig_data[4:]) # verify the checksum has been updated # TODO compute the expected checksum self.assertNotEqual(checksum1, checksum2) checksum1 = vftdi.eeprom[-2:] orig_data = vftdi.eeprom[:24] ftdi.write_eeprom(9, ref_data, dry_run=False) checksum2 = vftdi.eeprom[-2:] # verify the unaligned data have been written self.assertEqual(vftdi.eeprom[9:13], ref_data) # verify the data have not been overwritten self.assertEqual(vftdi.eeprom[:9], orig_data[:9]) self.assertEqual(vftdi.eeprom[13:24], orig_data[13:]) # verify the checksum has been updated self.assertNotEqual(checksum1, checksum2) checksum1 = vftdi.eeprom[-2:] orig_data = vftdi.eeprom[:48] ftdi.write_eeprom(33, ref_data[:3], dry_run=False) checksum2 = vftdi.eeprom[-2:] # verify the unaligned data have been written self.assertEqual(vftdi.eeprom[33:36], ref_data[:3]) # verify the data have not been overwritten self.assertEqual(vftdi.eeprom[:33], orig_data[:33]) self.assertEqual(vftdi.eeprom[36:48], orig_data[36:]) # verify the checksum has been updated self.assertNotEqual(checksum1, checksum2)
def test_dual_if_reset(self): """Demonstrate how to connect to an hotplugged FTDI device, i.e. an FTDI device that is connected after the initial attempt to enumerate it on the USB bus.""" url1 = environ.get('FTDI_DEVICE', 'ftdi:///1') ftdi1 = Ftdi() ftdi1.open_from_url(url1) count = ftdi1.device_port_count if count < 2: ftdi1.close() raise SkipTest('FTDI device is not a multi-port device') next_port = (int(url1[-1]) % count) + 1 url2 = 'ftdi:///%d' % next_port ftdi2 = Ftdi() self.assertTrue(ftdi1.is_connected, 'Unable to connect to FTDI') ftdi2.open_from_url(url2) # use latenty setting to set/test configuration is preserved ftdi2.set_latency_timer(128) # should be the same value self.assertEqual(ftdi2.get_latency_timer(), 128) self.assertTrue(ftdi2.is_connected, 'Unable to connect to FTDI') ftdi1.close() self.assertFalse(ftdi1.is_connected, 'Unable to close connection') # closing first connection should not alter second interface self.assertEqual(ftdi2.get_latency_timer(), 128) ftdi1.open_from_url(url1) self.assertTrue(ftdi1.is_connected, 'Unable to connect to FTDI') # a FTDI reset should not alter settings... ftdi1.reset(False) self.assertEqual(ftdi2.get_latency_timer(), 128) # ... however performing a USB reset through any interface should alter # any previous settings made to all interfaces ftdi1.reset(True) self.assertNotEqual(ftdi2.get_latency_timer(), 128)
def test_random_access_read(self): """Check EEPROM random read access.""" ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') self._restore_eeprom(ftdi) ref_data = bytes(list(range(256))) size = len(ref_data) # out of bound self.assertRaises(ValueError, ftdi.read_eeprom, size, 2) # last bytes buf = ftdi.read_eeprom(size - 2, 2) self.assertEqual(buf[0:2], ref_data[-2:]) self.assertEqual(buf[0:2], b'\xfe\xff') # out of bound self.assertRaises(ValueError, ftdi.read_eeprom, size - 2, 4) # unaligned access buf = ftdi.read_eeprom(1, 2) self.assertEqual(buf[0:2], ref_data[1:3]) self.assertEqual(buf[0:2], b'\x01\x02') # long read, unaligned access, unaligned size buf = ftdi.read_eeprom(43, 43) self.assertEqual(len(buf), 43) self.assertEqual(buf, ref_data[43:86]) ftdi.close()
def test_open_close(self): """Check simple open/close sequence.""" ftdi = Ftdi() ftdi.open_from_url('ftdi:///1') self.assertEqual(ftdi.usb_path, (4, 5, 0)) ftdi.close()
class ReaderBoard: _unlockTimeouts: Dict[int, List[LockTimeout]] def __init__(self, deviceid): """ :rtype: object """ # self.input = queue.Queue(20) self.packetCallback = None self.errorCallback = None self._commandLock = Lock() self._packetReadEvent = Event() self._startedEvent = Event() self._stoppedEvent = Event() self._run = True self.device_id = deviceid self._ftdi = Ftdi() self.numScanners = 0 self.numRelays = 0 self.relaystatus = {} self.version = "0.00" self.model = "unknown" self._unlockTimeouts = {} self._parserState = ParserState.BEGIN self._firstChar = ' ' self._body = bytearray() # self.ftdi.open(vendor=0x0403,product=0x6001, serial=self.device) self._ftdi.open_from_url(self.device_id) self._ftdi.set_baudrate(57600) self._backgroundThread = Thread(target=self.background) self._backgroundThread.start() self._otherThread = Thread(target=self.lock_watch_loop) self._otherThread.start() if not self._startedEvent.wait(3.0): raise RuntimeError("Unable to startup board!") else: logger.log(logging.DEBUG, "Started up, disabling echo") self.send_command('e', '0') def get_device_info(fc, b, me): if fc == 'I': info = {i.split(':')[0]: i.split(':')[1] for i in b.split(',')} self.model = info['m'] self.version = info['v'] self.numRelays = int(info['r']) self.numScanners = int(info['s']) for a in range(self.numRelays): self._unlockTimeouts[a] = [] self.relaystatus[a] = False self.packetCallback = get_device_info logger.info("calling getting device info") self.send_command('i', '') logger.info("done interrogating") self.packetCallback = None def shutdown(self): self._run = False self._commandLock.acquire() if not self._stoppedEvent.wait(3.0): raise TimeoutError("Failed to shut down board in a timely manner") self._backgroundThread.join() def background(self): self._ftdi.set_dtr(False) sleep(0.1) self._ftdi.set_dtr(True) # Reset the device totaltime = 0 ready_bytes = bytearray() started_up = False while totaltime < 5: sleep(0.1) totaltime += 0.1 in_bytes = self._ftdi.read_data_bytes(50) if len(in_bytes) > 0: ready_bytes.extend(in_bytes) s = ready_bytes.decode(encoding="utf-8") if '? for help' in s: totaltime = 1000 started_up = True self._startedEvent.set() try: self.parse_loop() except pyftdi.ftdi.FtdiError as error: logger.fatal(f"USB ERROR! {error}") if self.errorCallback is not None: self.errorCallback(error) if not started_up: print("Failed to startup device!") logger.log(logging.DEBUG, "Shutting down FTDI device...") self._ftdi.close() self._commandLock.release() logger.log(logging.DEBUG, "Done shutting down hardware interface") def lock_watch_loop(self): if not self._startedEvent.wait(5000): logger.fatal("Didn't start up!") else: while self._run: now = time() tos: List[LockTimeout] for (r, tos) in self._unlockTimeouts.items(): if any(tos): for t in tos: if now >= t.timeout: tos.remove(t) if len(tos) == 0: # close the door self.send_command('o', str(r)) self.relaystatus[r] = False elif self.relaystatus[r]: self.send_command('o', str(r)) self.relaystatus[r] = False sleep(0.25) def parse_loop(self): totaltime = 0 while self._run: sleep(0.1) totaltime += 0.1 in_bytes = self._ftdi.read_data_bytes(50) self.parse(in_bytes) # Check out open/close queue self._stoppedEvent.set() def parse(self, in_bytes): if len(in_bytes) > 0: for b in in_bytes: c = chr(b) if self._parserState == ParserState.BEGIN or self._parserState == ParserState.FOUND_NEWLINE: self._body.clear() self._firstChar = c self._parserState = ParserState.FOUND_FIRST elif self._parserState == ParserState.FOUND_FIRST: if b != 0x0D: # '\r' self._body.append(b) else: self._parserState = ParserState.FOUND_CARRIAGE elif self._parserState == ParserState.FOUND_CARRIAGE: if b != 0x0A: # \n # RAISE PARSING ERROR self._parserState = ParserState.BEGIN else: self._parserState = ParserState.FOUND_NEWLINE logger.log(logging.DEBUG, f"Read a packet: {self._firstChar}, {self._body}") if self.packetCallback is not None: try: self.packetCallback(self._firstChar, self._body.decode("utf-8"), self.device_id) except Exception as e: logger.log(logging.FATAL, f"Failed calling packet callback: {e}") if not (self._firstChar == 'e' and len(self._body) == 1 and self._body[0] == ord('0')): self._packetReadEvent.set() def send_command(self, c: str, data: str): if len(c) != 1 or not c.isalpha(): raise ValueError("c must be a single character!") message = f"{c}{data}\r" # we won't add a \n, serial comms don't do that normally self._packetReadEvent.clear() d = message.encode("utf-8") if self._commandLock.acquire(timeout=1.0): if self._run: self._ftdi.write_data(d) if self._packetReadEvent.wait(3.0): self._commandLock.release() return self._body else: logger.log(logging.ERROR, "Failure to get response from board, it's down?") # raise RuntimeError() self._commandLock.release() else: self._commandLock.release() else: if self._run: raise SystemError("Sync Error") def __repr__(self): return f"{self.device_id} - {self.model} v{self.version}: {self.numScanners} scanners, {self.numRelays} relays" def Unlock(self, relay, duration, credential=None): if relay > self.numRelays: logger.error(f"Attempt to activate a relay board {self.device_id} doesn't have. {relay}") return now = time() self._unlockTimeouts[relay].append(LockTimeout(now + duration, credential)) self.send_command('c', str(relay)) self.relaystatus[relay] = True def Lock(self, relay, credential=None): if relay > self.numRelays: logger.error(f"Attempt to activate a relay board {self.device_id} doesn't have. {relay}") return if credential is None: #This only is raised by the webpanel, clear our all entries, and the relay # will be relocked by the loop self._unlockTimeouts[relay].clear() else: for tos in self._unlockTimeouts[relay]: if tos.credential == credential: # Clear all pending timeouts for the credential self._unlockTimeouts[relay].remove(tos)
def main(): ftdi = Ftdi() ftdi.open_from_url('ftdi:///?')