wasatch 2.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. wasatch/AbstractUSBDevice.py +29 -0
  2. wasatch/AndorDevice.py +910 -0
  3. wasatch/AreaScanImage.py +19 -0
  4. wasatch/AutoRaman.py +457 -0
  5. wasatch/AutoRamanRequest.py +96 -0
  6. wasatch/BLEDevice.py +1032 -0
  7. wasatch/BalanceAcquisition.py +136 -0
  8. wasatch/CSVLoader.py +188 -0
  9. wasatch/CommandSettings.py +95 -0
  10. wasatch/ControlObject.py +17 -0
  11. wasatch/DetectorROI.py +36 -0
  12. wasatch/DetectorRegions.py +158 -0
  13. wasatch/DeviceFinderBLE.py +107 -0
  14. wasatch/DeviceFinderTCP.py +20 -0
  15. wasatch/DeviceFinderUSB.py +236 -0
  16. wasatch/DeviceID.py +344 -0
  17. wasatch/EEPROM.py +1450 -0
  18. wasatch/EEPROM_multical.sav +48 -0
  19. wasatch/FPGAOptions.py +125 -0
  20. wasatch/FeatureIdentificationDevice.py +3961 -0
  21. wasatch/FirmwareRequirements.py +76 -0
  22. wasatch/HardwareInfo.py +36 -0
  23. wasatch/IDSCamera.py +916 -0
  24. wasatch/IDSDevice.py +329 -0
  25. wasatch/IMX385.py +179 -0
  26. wasatch/InterfaceDevice.py +40 -0
  27. wasatch/MockUSBDevice.py +353 -0
  28. wasatch/OceanDevice.py +215 -0
  29. wasatch/PollStatus.py +15 -0
  30. wasatch/ProcessedReading.py +352 -0
  31. wasatch/ROI.py +33 -0
  32. wasatch/Reading.py +80 -0
  33. wasatch/RealUSBDevice.py +64 -0
  34. wasatch/SPIDevice.py +628 -0
  35. wasatch/SpectrometerRequest.py +15 -0
  36. wasatch/SpectrometerResponse.py +38 -0
  37. wasatch/SpectrometerSettings.py +446 -0
  38. wasatch/SpectrometerState.py +255 -0
  39. wasatch/StatusMessage.py +14 -0
  40. wasatch/TCPDevice.py +409 -0
  41. wasatch/TakeOneRequest.py +43 -0
  42. wasatch/WasatchBus.py +87 -0
  43. wasatch/WasatchDevice.py +1060 -0
  44. wasatch/WasatchDeviceWrapper.py +452 -0
  45. wasatch/WrapperWorker.py +304 -0
  46. wasatch/__init__.py +4 -0
  47. wasatch/applog.py +177 -0
  48. wasatch/simulation_protocol.py +227 -0
  49. wasatch/utils.py +731 -0
  50. wasatch-2.3.2.dist-info/LICENSE +21 -0
  51. wasatch-2.3.2.dist-info/METADATA +347 -0
  52. wasatch-2.3.2.dist-info/RECORD +53 -0
  53. wasatch-2.3.2.dist-info/WHEEL +4 -0
wasatch/DeviceID.py ADDED
@@ -0,0 +1,344 @@
1
+ import logging
2
+ import re
3
+
4
+ log = logging.getLogger(__name__)
5
+
6
+ ##
7
+ # Represents a persistent unique identifier for a spectrometer device
8
+ # (USB, BLE, TCP, whatever) which should remain valid for connected devices
9
+ # in spite of hotplug events around them.
10
+ #
11
+ # @par Statement of the Problem
12
+ #
13
+ # Assume we have four WP-785 spectrometers: A, B, C and D. We connect
14
+ # A, B and C, then launch ENLIGHTEN. A is the first on the USB chain, so
15
+ # in a positional listing A would be #1, B is #2, C is #3.
16
+ #
17
+ # Now we unplug B. That leaves us with A (1) and C (3). Except that,
18
+ # positionally C is really now 2. Now we plug in D. Is D now 4th on
19
+ # the list, or did it slip into the unoccupied 2 slot? Or was C internally
20
+ # moved back to 2, and now D is 3, but C also thinks it's 3?
21
+ #
22
+ # One solution would be to UPDATE the positional order in a master list
23
+ # on unplug events (detecting B's removal, and changing C from 3 to 2).
24
+ # ENLIGHTEN could do that, but note that it would therefore be a client-
25
+ # side operation, not within the driver itself. Or we could move the
26
+ # multi-process "Controller" ownership of the several WasatchDeviceWrapper
27
+ # processes into Wasatch.PY, such that Wasatch.PY provided a single-object
28
+ # Facade. We could possibly achieve that by maintaining a list of all
29
+ # WasatchDeviceWrapper instances in a static WasatchDeviceWrapper attribute
30
+ # (or WasatchDeviceWrappers or WasatchDeviceWrapperFactory as it were).
31
+ # But either would require some additional refactoring that I'm not diving
32
+ # into right now, although it seems an improved and reasonable architecture.
33
+ #
34
+ # Or maybe we don't need a DeviceID at all, and just track "the
35
+ # WasatchDeviceWrapper associated with this serial number", and simply
36
+ # assume that all spectrometers will have a unique spectrometer (or be
37
+ # assigned one at connection). So maybe my attempt to generate a UNIQUE
38
+ # and PERSISTENT DeviceID from the usb.device object is quixotic and
39
+ # unnecessary. That's probably the case.
40
+ #
41
+ # Part of what drove this is that historically ENLIGHTEN supported a bus_order
42
+ # command-line argument which explicitly referenced spectrometers by their
43
+ # position on the USB bus. However, we're now trying to support hotplug
44
+ # use-cases, meaning that position is no longer reliable. But since we had a
45
+ # legacy expectation of being able to explicitly identify devices at the bus
46
+ # level, I'm trying to retain that capability by habit. It probably is no
47
+ # longer needed, and we can probably replace the old --bus-order option with
48
+ # --serial-number instead.
49
+ #
50
+ # More to the point, the current architecture is such that ENLIGHTEN calls
51
+ # WasatchBus to detect hotplug events, and then ENLIGHTEN instantiates a new
52
+ # WasatchDeviceWrapper to support the new bus device. That means there really
53
+ # ought to be a way to pass the "id" of the device, which was detected by
54
+ # WasatchBus, down into WasatchDeviceWrapper (and hence WasatchDevice and
55
+ # FeatureIdentificationDevice) to be re-instantiated. I don't trust position,
56
+ # I'm not sure how to confirm "claim state" (ASSUMING we wanted the "first
57
+ # eligible unclaimed") , "serial number" seems heavy-handed (and would still
58
+ # require a way to deliberately "skip" already-connected devices, again requiring
59
+ # some sort of key).
60
+ #
61
+ # ALSO we want to eventually support BLE (which has unique UUID), and perhaps
62
+ # TCP/IP (which has IP addresses)...basically I think objects on a "bus" should
63
+ # be uniquely identifiable and addressable from their bus address, WITHOUT making
64
+ # guesses based on position or ordering or claim-state or anything like that.
65
+ #
66
+ # So yeah, I think this is useful.
67
+ #
68
+ # @par USB
69
+ #
70
+ # - .vid and .pid are stored as ints
71
+ #
72
+ # @par BLE
73
+ #
74
+ # - populates .bleak_ble_device (bleak.BLEDevice from BleakScanner)
75
+ # - populates .serial_number
76
+ #
77
+ # Adding the non-pickleable .bleak_ble_device kind of defeats some of the
78
+ # original intent of this class, as ideally serialized (stringified) DeviceIDs
79
+ # were intended to be passable between processes and "re-instantiated" on the
80
+ # receiving end.
81
+ #
82
+ # However, now that ENLIGHTEN is multi-threaded (instead of multi-process), we
83
+ # don't have to worry about that, and it seems convenient to simply stash a
84
+ # handle to the Bleak Device directly in the DeviceID, where it can be passed
85
+ # from DeviceFinderBLE to BLEManager to WasatchDeviceWrapper/WrapperWorker to
86
+ # BLEDevice.
87
+ #
88
+ # Serial number is tracked in the DeviceID (rather than DiscoveredBLEDevice)
89
+ # because the current DeviceID architecture is to dynamically generate its
90
+ # stringified form, based on internal native types.
91
+ #
92
+ # @see BLEDevice for consolidated BLE docs
93
+ #
94
+ # @par TCP
95
+ #
96
+ # - uses .address and .port
97
+ #
98
+ class DeviceID:
99
+
100
+ ##
101
+ # Instantiates a DeviceID object from either a usb.device or an
102
+ # existing device_id string representation.
103
+ #
104
+ # @param device is a usb.device from pyusb
105
+ # @param directory is used with FILE spectrometers
106
+ # @param device_type: this seems to be currently exclusively used to
107
+ # distinguish between RealUSBDevice and MockUSBDevice (both extending
108
+ # AbstractUSBDevice).
109
+ def __init__(self, device=None, label=None, directory=None, device_type=None, overrides=None, spectra_options=None, bleak_ble_device=None):
110
+
111
+ self.type = None # "USB", "FILE", "MOCK", "BLE", "TCP"
112
+
113
+ # USB
114
+ self.vid = None # USB
115
+ self.pid = None # USB
116
+ self.bus = None # USB
117
+ self.address = None # USB, TCP
118
+
119
+ # MOCK
120
+ self.name = None # MOCK?
121
+ self.device_type = device_type
122
+
123
+ # FILE
124
+ self.directory = None # FILE
125
+
126
+ # BLE
127
+ self.serial_number = None
128
+ self.bleak_ble_device = bleak_ble_device
129
+
130
+ self.overrides = overrides # MZ: what is this?
131
+ self.spectra_options = spectra_options # MZ: what is this?
132
+
133
+ self.bleak_device = None
134
+
135
+ if label is not None:
136
+ # instantiate from an existing string id
137
+ if label.startswith("USB:"):
138
+ tok = label.split(":")
139
+ self.type = tok[0]
140
+ self.vid = int(tok[1][2:])
141
+ self.pid = int(tok[2][2:])
142
+ self.bus = int(tok[3])
143
+ self.address = int(tok[4])
144
+ elif label.startswith("FILE:"):
145
+ tok = label.split(":")
146
+ self.type = tok[0]
147
+ self.directory = tok[1]
148
+ elif label.startswith("BLE:"):
149
+ tok = label.split(":")
150
+ self.type = tok[0]
151
+ self.serial_number = tok[1]
152
+ elif label.startswith("TCP:"):
153
+ tok = label.split(":")
154
+ self.type = "TCP"
155
+ self.address = tok[1]
156
+ self.port = int(tok[2])
157
+ elif label.startswith("IDSPeak"):
158
+ self.type = "IDSPeak"
159
+ elif label.startswith("MOCK:"):
160
+ tok = label.split(":")
161
+ self.type = tok[0]
162
+ self.name = tok[1]
163
+ self.directory = tok[2]
164
+ self.vid = int(str(hash(self.name)))
165
+ self.pid = 0x4000
166
+ self.bus = 111111
167
+ self.address = 111111
168
+ else:
169
+ raise Exception("DeviceID: invalid device_id label %s" % label)
170
+
171
+ elif device is not None:
172
+ # MZ: This needs redesigned. Currently it assumes that any "device"
173
+ # is implicitly USB.
174
+
175
+ # instantiate from a PyUSB Device
176
+ log.debug(f"instantiating from USB device")
177
+ self.type = "USB"
178
+ self.vid = int(device.idVendor)
179
+ self.pid = int(device.idProduct)
180
+ self.determine_bus_and_address(device)
181
+
182
+ elif directory is not None:
183
+ # instantiate from a file spec
184
+ self.type = "FILE"
185
+ self.directory = directory
186
+
187
+ else:
188
+ raise Exception("DeviceID: needs usb.device OR device_id label OR directory")
189
+
190
+ log.debug("instantiated DeviceID: %s", str(self))
191
+
192
+ def determine_bus_and_address(self, device):
193
+ """
194
+ This is for USB devices. It seems to work on tested platforms, but is not
195
+ guaranteed by the protocol or library.
196
+ """
197
+ if hasattr(device, "dev"):
198
+ self.bus = int(device.dev.bus)
199
+ self.address = int(device.dev.address)
200
+ self.pid = int(device.dev.idproduct)
201
+ self.vid = int(device.dev.idvendor)
202
+ if device.dev.product is not None:
203
+ self.product = device.dev.product.rstrip('\x00')
204
+ if device.dev.serial_number is not None:
205
+ self.serial_number = device.dev.serial_number.rstrip('\x00')
206
+ # serial number has ascii null chars that must be removed
207
+ return
208
+ else:
209
+ self.bus = int(device.bus)
210
+ self.address = int(device.address)
211
+ self.pid = int(device.idProduct)
212
+ self.vid = int(device.idVendor)
213
+ try:
214
+ if device.product is not None:
215
+ self.product = device.product.rstrip('\x00')
216
+ if device.serial_number is not None:
217
+ self.serial_number = device.serial_number.rstrip('\x00')
218
+ # serial number has ascii null chars that must be removed
219
+ except Exception as e:
220
+ log.error(f"While creating device id encountered {e}")
221
+ return
222
+
223
+
224
+ # if the above fails, try to parse from string representation, e.g.:
225
+ # "DEVICE ID 24aa:1000 on Bus 000 Address 001 ================="
226
+ s = str(device)
227
+ m = re.match(r"Bus\s+(\d+)\s+Address\s+(\d+)", s, re.IGNORECASE)
228
+ if m:
229
+ self.bus = int(m.group(1))
230
+ self.address = int(m.group(2))
231
+ return
232
+
233
+ # Give up. Shouldn't be a problem unless we talking to multiple devices
234
+ # with the same PID at once (Raman Rainbow etc).
235
+ log.error("can't determine bus or address of USB device from:\n%s", s)
236
+ self.bus = -1
237
+ self.address = -1
238
+
239
+ def is_file(self):
240
+ return self.type.upper() == "FILE"
241
+
242
+ def is_usb(self):
243
+ return self.type.upper() == "USB"
244
+
245
+ def is_mock(self):
246
+ return self.type.upper() == "MOCK"
247
+
248
+ def is_ble(self):
249
+ return self.type.upper() == "BLE"
250
+
251
+ def is_tcp(self):
252
+ return self.type.upper() == "TCP"
253
+
254
+ def is_ids_peak(self):
255
+ return self.type == "IDSPeak"
256
+
257
+ def is_andor(self):
258
+ return self.vid == 0x136e
259
+
260
+ # Surely there is a better way to obtain the 'bus' and 'address' attributes
261
+ # than rendering the usb.device as a string and then parsing it. I tried
262
+ # dumping the __dict__ and didn't see the address anywhere...must be in a
263
+ # sub-object I didn't traverse.
264
+ #
265
+ # @note I'm not sure this is guaranteed to work on all libusb implementations?
266
+ # But it seems to work on Ubuntu and Win10-64, so...
267
+ #
268
+ # @see https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst#user-content-dealing-with-multiple-identical-devices
269
+ # def get_bus_and_address_NOT_USED(self, device):
270
+ # log.debug("parsing bus and address from device")
271
+ #
272
+ # # device.dev is what is returned by usb.core.find(). This is what you
273
+ # # get if you just print device.dev to stdout
274
+ # s = str(device.dev)
275
+ #
276
+ # # ARE YOU SURE you can't just read device.dev.bus and .address?
277
+ # log.debug("MZ: s = %s", s)
278
+ # log.debug("MZ: bus = %s", str(device.dev.bus))
279
+ # log.debug("MZ: addr = %s", str(device.dev.address))
280
+ #
281
+ # device = None
282
+ # del device
283
+ #
284
+ # # extract the "hidden fields" from the first line of the ASCII dump
285
+ # m = re.match(r"DEVICE ID ([0-9a-f]{4}):([0-9a-f]{4}) on Bus (\d+) Address (\d+)", s, re.IGNORECASE)
286
+ # if m:
287
+ # # 1 and 2 are hex VID and PID respectively
288
+ # bus = int(m.group(3))
289
+ # address = int(m.group(4))
290
+ # log.debug("get_bus_and_address: parsed bus = %d, address = %d", bus, address)
291
+ # else:
292
+ # bus = -1
293
+ # address = -1
294
+ # log.critical("get_bus_and_address: failed to parse bus and address")
295
+ # return (bus, address)
296
+
297
+ def get_pid_hex(self):
298
+ if self.type in ["BLE", "TCP", "IDSPeak"]:
299
+ return None
300
+ return "%04x" % self.pid
301
+
302
+ def get_vid_hex(self):
303
+ if self.type in ["BLE", "TCP", "IDSPeak"]:
304
+ return None
305
+ return "%04x" % self.vid
306
+
307
+ ##
308
+ # Whether a given device is USB, FILE or otherwise, render the DeviceID
309
+ # as a string containing all the relevant bits neccessary to reconstruct
310
+ # the object into a parsed structure while providing a concise, readable
311
+ # and hashable unique key.
312
+ def __repr__(self):
313
+ if self.type == "USB": return f"<DeviceID {self.type}:0x{self.vid:04x}:0x{self.pid:04x}:{self.bus}:{self.address}>"
314
+ elif self.type == "FILE": return f"<DeviceID {self.type}:{self.directory}>"
315
+ elif self.type == "MOCK": return f"<DeviceID {self.type}:{self.name}:{self.directory}>"
316
+ elif self.type == "BLE": return f"<DeviceID {self.type}:{self.serial_number}>"
317
+ elif self.type == "TCP": return f"<DeviceID {self.type}:{self.address}:{self.port}>"
318
+ elif self.type == "IDSPeak": return f"<DeviceID {self.type}>"
319
+ else: raise Exception("unsupported DeviceID type %s" % self.type)
320
+
321
+ # def __deepcopy__(self, memo):
322
+ # return str(self)
323
+
324
+ def __eq__(self, other):
325
+ return str(self) == str(other)
326
+
327
+ def __ne__(self, other):
328
+ return str(self) != str(other)
329
+
330
+ def __lt__(self, other):
331
+ return str(self) < str(other)
332
+
333
+ def __hash__(self):
334
+ return hash(str(self))
335
+
336
+ ## So that dict() can return a clean __dict__ without any "private" attributes
337
+ # (which we should probably __prefix or something)
338
+ def to_dict(self):
339
+ d = {}
340
+ for k, v in self.__dict__.items():
341
+ if k not in ["device"]:
342
+ d[k] = v
343
+ d["device_type"] = str(self.device_type)
344
+ return d