1"""Utility class representing a CfM USB device.
2
3This class represents actual data found by running the usb-device command.
4"""
5
6class UsbDevice(object):
7  """Utility class representing a CfM USB device."""
8
9  def __init__(self,
10               vid,
11               pid,
12               product,
13               interfaces,
14               bus,
15               port,
16               level,
17               parent=None):
18      """
19      Constructor.
20
21      @param vid: Vendor ID. String.
22      @param pid: Product ID. String.
23      @param product: Product description. String
24      @param interfaces: List of strings.
25      @param bus: The bus this device is connected to. Number.
26      @param port: The port number as specified in /sys/bus/usb/devices/usb*.
27          Number.
28      @param level: The level in the device hierarchy this device is connected
29          at. A device connected directly to a port is typically at level 1.
30      @param parent: Optional parent UsbDevice. A parent device is a device that
31          this device is connected to, typically a USB Hub.
32      """
33      self._vid = vid
34      self._pid = pid
35      self._product = product
36      self._interfaces = interfaces
37      self._bus = bus
38      self._port = port
39      self._level = level
40      self._parent = parent
41
42  @property
43  def vendor_id(self):
44      """Returns the vendor id for this USB device."""
45      return self._vid
46
47  @property
48  def product_id(self):
49      """Returns the product id for this USB device."""
50      return self._pid
51
52  @property
53  def vid_pid(self):
54      """Return the <vendor_id>:<product_id> as a string."""
55      return '%s:%s' % (self._vid, self._pid)
56
57  @property
58  def product(self):
59      """Returns the product name."""
60      return self._product
61
62  @property
63  def interfaces(self):
64      """Returns the list of interfaces."""
65      return self._interfaces
66
67  @property
68  def port(self):
69      """Returns the port this USB device is connected to."""
70      return self._port
71
72  @property
73  def bus(self):
74      """Returns the bus this USB device is connected to."""
75      return self._bus
76
77  @property
78  def level(self):
79      """Returns the level of this USB Device."""
80      return self._level
81
82  @property
83  def parent(self):
84      """
85      Returns the parent device of this device.
86
87      Or None if this is the top level device.
88      @returns the parent or None.
89      """
90      return self._parent
91
92  @parent.setter
93  def parent(self, value):
94      """
95      Sets the parent device of this device.
96
97      We allow setting parents to make it easier to create the device tree.
98      @param value the new parent.
99      """
100      self._parent = value
101
102  def interfaces_match_spec(self, usb_device_spec):
103      """
104      Checks that the interfaces of this device matches those of the given spec.
105
106      @param usb_device_spec an instance of UsbDeviceSpec
107      @returns True or False
108      """
109      # List of expected interfaces. This might be a sublist of the actual
110      # list of interfaces. Note: we have to use lists and not sets since
111      # the list of interfaces might contain duplicates.
112      expected_interfaces = sorted(usb_device_spec.interfaces)
113      length = len(expected_interfaces)
114      actual_interfaces = sorted(self.interfaces)
115      return actual_interfaces[0:length] == expected_interfaces
116
117  def get_parent(self, level):
118      """
119      Gets the parent device at the specified level.
120
121      Devices are connected in a hierarchy. Typically like this:
122      Level 0: Machine's internal USB hub
123       |
124       +--+ Level 1: Device connected to the machine's physical ports.
125         |
126         +--+ Level 2: If level 1 is a Hub, this is a device connected to
127                       that Hub.
128
129      A typical application of this method is when power cycling. Then we get a
130      device's parent at level 1 to locate the port that should be power cycled.
131
132      @param level the level of the parent to return.
133      @returns A UsbDevice instance of the parent at the specified level.
134      @raises ValueError if we did not find a device at the specified level.
135      """
136      device = self
137      while device != None:
138          if device.level < level:
139              raise ValueError(
140                  'Reached lower level without finding level %d', level)
141          if device.level == level:
142              return device
143          device = device.parent
144
145  def __str__(self):
146      return "%s (%s)" % (self._product, self.vid_pid)
147
148  def __repr__(self):
149      return "%s (%s), bus=%s, port=%s, parent=%s" % (
150              self._product, self.vid_pid, self._bus, self._port, self.parent)
151