2016-08-04 31 views
0

Story

Ich programmiere einen Treiber für eine wissenschaftliche Kamera. Es verwendet den Cypress FX3 USB-Peripheriecontroller. Um damit zu kommunizieren benutze ich libusb1 für python, speziell das Modul usb1. Mein Betriebssystem ist Ubuntu 16.04 LTS.Python libusb1: asynchroner TRANSFER_NO_DEVICE-Status kurz nach erfolgreichen synchronen Übertragungen

Die Kommunikation hat zwei Stufen:

  • Die Kamera konfiguriert ist. Der Computer sendet synchron Anweisungen zum Programmieren der Kamera und nach jedem Befehl antwortet die Kamera auf ein Statuswort, das synchron gelesen wird.

  • Ein Foto ist gemacht. Der Computer sendet synchron eine Anweisung und die Kamera beginnt mit dem Streaming der Daten. Der Computer liest diese Daten asynchron.

Die asynchrone Kommunikation erfolgt im Hauptthread. Selbst wenn die Kommunikation selbst asynchron ist, blockiert die Operation.

Problem

Ich bin für jeden asynchronen Transfer TRANSFER_NO_DEVICE Status bekommen, die, dass ich gerade mit der Kamera im Konfigurationsschritt kommunizierte gegeben seltsam. Ich habe einen ähnlichen Code in C# in Windows mit der Zypressen-Bibliothek und es funktioniert richtig, so kann ich die Kamera ausschließen. Außerdem erscheint ein Teil der Bilddaten im FX3-Puffer, nachdem versucht wurde, ein Foto aufzunehmen, das ich mit einer von cypress bereitgestellten Beispielanwendung wiederherstellen kann.

Ich habe ein Minimum Beispielskript erstellt. Beachten Sie die configure und take_picture Funktionen:

#!/usr/bin/env python 
# -*- coding: UTF-8 -*- 
# 
# StackOverflow.py 

import usb1 as usb1 # Libusb, needed to provide a usb context 

import GuideCamLib.binary as binary # Handles bytecode formatting 
import GuideCamLib.ccd as ccd  # Generates configuration information 
import GuideCamLib.usb as usb  # Manages communication 

# Camera usb parameters 
vid = 0x04B4; 
pid = 0x00F1; 

read_addr = 0x81; 
write_addr = 0x01; 

interface = 0; 

success = [0x55, 0x55, 0x55, 0x55] + [0]*(512 - 4); # A successful response 

# Objects that generate instructions for the camera 
ccd0 = ccd.CCD_47_10(); 
formatter = binary.ByteCode(); 

def configure(context): 
    # Generating bytes to transfer, outputs a list of int lists 
    bytecode_lines = ccd0.create_configuration_bytecode(formatter); 

    # Opens device 
    with usb.Device(vid=vid, pid=pid, context= context) as dev: 

    # Opens read/write ports 
    port_write = dev.open_port(write_addr); 
    port_read = dev.open_port(read_addr); 

    print(" Start configuration...") 
    # Transfer synchronously 
    for line in bytecode_lines: 
     written_bytes = port_write.write_sync(line); 
     response = port_read.read_sync(512); 
     if(response != success): 
     raise RuntimeError(" Configuration failed. (" + str(response) + ")"); 
    print(" End configuration") 

def take_picture(context): 
    # Generating bytes to transfer, generates a list of ints 
    take_photo_bytecode = formatter.seq_take_photo(ccd0.get_take_photo_mode_address()); 

    # Opens device 
    with usb.Device(vid=vid, pid=pid, context= context) as dev: 

    # Opens read/write ports 
    port_write = dev.open_port(write_addr); 
    port_read = dev.open_port(read_addr, 10000); # 10 sec timeout 

    # Prepare asynchronous read 
    print(" Prepare read") 
    with port_read.read_async(512) as data_collector: 
     print(" Writing") 
     written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously 
     print(" Reading...") 
     recieved_image = data_collector(); # Read asynchronously (but in a blocking manner) 

    print " Recieved: " + str(len(recieved_image)) + " bytes."; 

with usb1.USBContext() as context: 
    print "Configuring camera:" 
    configure(context);  # Configure camera 
    print "Taking picture:" 
    take_picture(context); # Take picture 
    print "Done." 

Hier GuideCamLib/usb.py für die benötigten Kontextualisierung ist. Die Klasse _TransferCollector erledigt die meiste Arbeit, während _AsyncReader nur eine Funktion mit Status ist. Port- und Geräte sind nur Hilfsklassen, Standardcode in jeder Übertragung zu reduzieren:

#!/usr/bin/env python 
# -*- coding: UTF-8 -*- 
# 
# GuideCamLib/usb.py 

import usb1 as usb 
import six as six 

import traceback 

# For human-readable printing 
transfer_status_dict = \ 
{ \ 
    usb.TRANSFER_COMPLETED : "TRANSFER_COMPLETED", 
    usb.TRANSFER_ERROR  : "TRANSFER_ERROR", 
    usb.TRANSFER_TIMED_OUT : "TRANSFER_TIMED_OUT", 
    usb.TRANSFER_CANCELLED : "TRANSFER_CANCELLED", 
    usb.TRANSFER_STALL  : "TRANSFER_STALL", 
    usb.TRANSFER_NO_DEVICE : "TRANSFER_NO_DEVICE", 
    usb.TRANSFER_OVERFLOW : "TRANSFER_OVERFLOW" \ 
}; 

# Callback to accumulate succesive transfer calls 
class _AsyncReader: 
    def __init__(self): 
    self.transfers = []; 

    def __call__(self, transfer): 
    print "Status: " + transfer_status_dict[transfer.getStatus()]; # Prints the status of the transfer 
    if(transfer.getStatus() != usb.TRANSFER_COMPLETED): 
     return; 
    else: 
     self.transfers.append(transfer.getBuffer()[:transfer.getActualLength()]); 
     transfer.submit(); 

# A collector of asyncronous transfer's data. 
# Stops collection after port.timeout time of recieving the last transfer. 
class _TransferCollector: 
    # Prepare data collection 
    def __init__(self, transfer_size, pararell_transfers, port): 
    self.interface_handle = port.device.dev.claimInterface(port.interface); 
    self.reader = _AsyncReader(); 
    self.port = port; 
    transfers = []; 

    # Queue transfers 
    for ii in range(pararell_transfers): 
     transfer = port.device.dev.getTransfer(); 
     transfer.setBulk(
     port.address, 
     transfer_size, 
     callback=self.reader, 
     timeout=port.timeout); 
     transfer.submit(); 
     transfers.append(transfer); 
    self.transfers = transfers; 

    def __enter__(self): 
    self.interface_handle.__enter__(); 
    return self; 

    def __exit__(self, exception_type, exception_value, traceback): 
    self.interface_handle.__exit__(exception_type, exception_value, traceback); 

    # Activate data collection 
    def __call__(self): 
    # Collect tranfers with _AsyncReader while there are active transfers. 
    while any(x.isSubmitted() for x in self.transfers): 
     try: 
     self.port.device.context.handleEvents(); 
     except usb.USBErrorInterrupted: 
     pass; 
    return [six.byte2int(d) for data in self.reader.transfers for d in data]; 

# Port class for creating syncronous/asyncronous transfers 
class Port: 
    def __init__(self, device, address, timeout = None): 
    self.device = device; 
    self.address = address; 
    self.interface = self.device.interface; 
    self.timeout = timeout; 
    if(timeout is None): 
     self.timeout = 0; 

    def read_sync(self, length): 
    with self.device.dev.claimInterface(self.interface): 
     data = self.device.dev.bulkRead(self.address, length, timeout=self.timeout); 
     return [six.byte2int(d) for d in data]; 

    def write_sync(self, data): 
    data = [six.int2byte(d) for d in data]; 
    with self.device.dev.claimInterface(self.interface): 
     return self.device.dev.bulkWrite(self.address, data, timeout=self.timeout); 

    # Make asyncronous transfers blocking. Collects data as long as the device 
    # sends data more frecuently than self.timeout or all the transfers fails 
    def read_async(self, length, pararell_transfers = 32): 
    return _TransferCollector(length, pararell_transfers, self); 

# Device class for creating ports 
class Device: 
    def __init__(self, vid = None, pid = None, context = None, interface = 0): 

    if(not context): 
     self.backend = usb.USBContext(); 
     context = self.backend.__enter__(); 

    self.context = context; 
    self.interface = interface; 

    self.dev = context.openByVendorIDAndProductID(vid, pid, skip_on_error = False);  
    if self.dev is None: 
     raise RuntimeError('Device not found'); 

    def __enter__(self): 
    return self; 

    def __exit__(self, exception_type, exception_value, traceback): 
    if(hasattr(self, "backend")): 
     self.backend.__exit__(exception_type, exception_value, traceback); 

    def open_port(self, address, timeout = None): 
    return Port(self, address, timeout); 

Das Skript folgende Ausgänge, die eindeutig die synchronen Übertragungen zeigen sind erfolgreich, aber jeder der Warteschlange asynchronen Übertragungen nicht mit einem NO_DEVICE:

>>> python StackOverflow.py 
Configuring camera: 
Start configuration... 
End configuration 
Taking picture: 
Prepare read 
Writing 
Reading... 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Status: TRANSFER_NO_DEVICE 
Traceback (most recent call last): 
    File "StackOverflow.py", line 70, in <module> 
    take_picture(context); # Take picture 
    File "StackOverflow.py", line 62, in take_picture 
    recieved_image = data_collector(); 
    File "/media/jabozzo/Data/user_data/jabozzo/desktop/sigmamin/code/workspace_Python/USB/USB wxglade libusb1/GuideCamLib/usb.py", line 62, in __exit__ 
    self.interface_handle.__exit__(exception_type, exception_value, traceback); 
    File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 1036, in __exit__ 
    self._handle.releaseInterface(self._interface) 
    File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 1171, in releaseInterface 
    libusb1.libusb_release_interface(self.__handle, interface), 
    File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 121, in mayRaiseUSBError 
    raiseUSBError(value) 
    File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 117, in raiseUSBError 
    raise STATUS_TO_EXCEPTION_DICT.get(value, USBError)(value) 
usb1.USBErrorNotFound: LIBUSB_ERROR_NOT_FOUND [-5] 

aktualisieren

ich die Geräte- und Port Klassen geändert haben, so dass die Schnittstelle geöffnet wird, wenn das Gerät openned wird. Auf diese Weise die Schnittstelle nur openned wird (und geschlossen) einmal, unabhängig von der Anzahl der Ports openned:

# Port class for creating syncronous/asyncronous transfers 
class Port: 
    def __init__(self, device, address, timeout = None): 
    self.device = device; 
    self.address = address; 
    self.timeout = timeout; 
    if(timeout is None): 
     self.timeout = 0; 

    def read_sync(self, length): 
    data = self.device.dev.bulkRead(self.address, length, timeout=self.timeout); 
    return [six.byte2int(d) for d in data]; 

    def write_sync(self, data): 
    data = [six.int2byte(d) for d in data]; 
    return self.device.dev.bulkWrite(self.address, data, timeout=self.timeout); 

    # Make asyncronous transfers blocking. Collects data as long as the device 
    # sends data more frecuently than self.timeout or all the transfers fails 
    def read_async(self, length, pararell_transfers = 32): 
    return _TransferCollector(length, pararell_transfers, self); 

# Device class for creating ports 
class Device: 
    def __init__(self, vid = None, pid = None, context = None, interface = 0): 

    if(not context): 
     self.backend = usb.USBContext(); 
     context = self.backend.__enter__(); 

    self.context = context; 
    self.interface = interface; 

    self.dev = context.openByVendorIDAndProductID(vid, pid, skip_on_error = False);  
    if self.dev is None: 
     raise RuntimeError('Device not found'); 

    self.interface_handle = self.dev.claimInterface(self.interface); 

    def __enter__(self): 
    self.interface_handle.__enter__(); 
    return self; 

    def __exit__(self, exception_type, exception_value, traceback): 
    self.interface_handle.__exit__(exception_type, exception_value, traceback); 
    if(hasattr(self, "backend")): 
     self.backend.__exit__(exception_type, exception_value, traceback); 

    def open_port(self, address, timeout = None): 
    return Port(self, address, timeout); 

ich immer noch den gleichen Fehler haben. Aber der Druck zeigt ich es nicht früher, bei der Lesevorbereitung:

>>> python StackOverflow.py 
Configuring camera: 
Start configuration... 
End configuration 
Taking picture: 
Prepare read 
Traceback (most recent call last): 
... 

Ich fange an, ich brauche nicht zu vermuten, dass eine Schnittstelle, um zu öffnen, um asynchrone Übertragungen durchzuführen.

+0

Ich verstehe nicht ganz Code Ich fürchte, weil ich in Python nicht fließend bin. Aber der Fehler am Ende besagt eindeutig, dass das Gerät schon weg ist. Sind Sie sicher, dass auf die Übertragungen gewartet wird, um Daten zurückzugeben, bevor das Gerät freigegeben wird? Da Sie C# wissen, würde ich vorschlagen, dass es das Python-Äquivalent eines bereits vorhandenen Objekts ist. Wie ich es gelesen habe, ist "recieved_image" eine Instanz von _TransferCollector, nicht die empfangenen Daten, aber vielleicht interpretiere ich falsch, wie Python dies behandelt. – dryman

+0

@dryman Du hast wahrscheinlich recht. Jetzt, wo du es erwähnst, nehme ich die Schnittstelle zweimal. Zuerst im 'with port_read' und dann im' port_write.write_sync'. Dann wird der zweite Aufruf beendet und die Schnittstelle geschlossen, bevor 'data_collector()' aufgerufen wird (was dem 'data_collector .__ call __()' entspricht). Im Moment bin ich nicht mit der Kamera. Ich werde mit den Ergebnissen zurück sein. – jabozzo

+0

Ich habe den obigen Fix versucht und nicht funktioniert. Ich werde meine Frage aktualisieren. – jabozzo

Antwort

0

Als dryman wies darauf hin, ich befreie den Kontext vor dem Beenden (weil ich den Kontext zweimal geöffnet).Wenn wir die read_async und write_sync Anrufe im Code-Extrakt inline:

print(" Prepare read") 
with port_read.read_async(512) as data_collector: 
    print(" Writing") 
    written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously 
    print(" Reading...") 
    recieved_image = data_collector(); # Read asynchronously (but in a blocking manner) 

würden wir bekommen etwas wie das folgende Pseudo-Code:

print(" Prepare read") 
with port_read.claimInterface(0) as ?: 
    # Read preparation code 
    print(" Writing") 
    with port_write.claimInterface(0) as ?: 
    written_bytes = # Write synchronously 
    # Here the port_write.claimInterface context has exited, 
    # leaving the prepared read transfers in a invalid state. 
    print(" Reading...") 
    recieved_image = # Read asynchronously (Fails, out of interface context) 

In meiner Frage Update habe ich vergessen, auf die Schnittstelle Anspruch zu entfernen _TransferCollector, also hatte ich ein ähnliches Problem. Anwenden der Frage aktualisieren und _TransferCollector definieren als:

# A collector of asyncronous transfer's data. 
# Stops collection after port.timeout time of recieving the last transfer. 
class _TransferCollector: 
    # Prepare data collection 
    def __init__(self, transfer_size, pararell_transfers, port): 
    self.reader = _AsyncReader(); 
    self.port = port; 
    transfers = []; 

    # Queue transfers 
    for ii in range(pararell_transfers): 
     transfer = port.device.dev.getTransfer(); 
     transfer.setBulk(
     port.address, 
     transfer_size, 
     callback=self.reader, 
     timeout=port.timeout); 
     transfer.submit(); 
     transfers.append(transfer); 
    self.transfers = transfers; 

    # Activate data collection 
    def __call__(self): 
    # Collect tranfers with _AsyncReader while there are active transfers. 
    while any(x.isSubmitted() for x in self.transfers): 
     try: 
     self.port.device.context.handleEvents(); 
     except usb.USBErrorInterrupted: 
     pass; 
    return [six.byte2int(d) for data in self.reader.transfers for d in data]; 

Behebt das Problem.

Beachten Sie, dass eine kleine Änderung jetzt nennen read_async gemacht werden muss:

# Prepare asynchronous read 
print(" Prepare read") 
data_collector = port_read.read_async(512): 
print(" Writing") 
written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously 
print(" Reading...") 
recieved_image = data_collector(); # Read asynchronously (but in a blocking manner) 


print " Recieved: " + str(len(recieved_image)) + " bytes.";