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.
- wasatch/AbstractUSBDevice.py +29 -0
- wasatch/AndorDevice.py +910 -0
- wasatch/AreaScanImage.py +19 -0
- wasatch/AutoRaman.py +457 -0
- wasatch/AutoRamanRequest.py +96 -0
- wasatch/BLEDevice.py +1032 -0
- wasatch/BalanceAcquisition.py +136 -0
- wasatch/CSVLoader.py +188 -0
- wasatch/CommandSettings.py +95 -0
- wasatch/ControlObject.py +17 -0
- wasatch/DetectorROI.py +36 -0
- wasatch/DetectorRegions.py +158 -0
- wasatch/DeviceFinderBLE.py +107 -0
- wasatch/DeviceFinderTCP.py +20 -0
- wasatch/DeviceFinderUSB.py +236 -0
- wasatch/DeviceID.py +344 -0
- wasatch/EEPROM.py +1450 -0
- wasatch/EEPROM_multical.sav +48 -0
- wasatch/FPGAOptions.py +125 -0
- wasatch/FeatureIdentificationDevice.py +3961 -0
- wasatch/FirmwareRequirements.py +76 -0
- wasatch/HardwareInfo.py +36 -0
- wasatch/IDSCamera.py +916 -0
- wasatch/IDSDevice.py +329 -0
- wasatch/IMX385.py +179 -0
- wasatch/InterfaceDevice.py +40 -0
- wasatch/MockUSBDevice.py +353 -0
- wasatch/OceanDevice.py +215 -0
- wasatch/PollStatus.py +15 -0
- wasatch/ProcessedReading.py +352 -0
- wasatch/ROI.py +33 -0
- wasatch/Reading.py +80 -0
- wasatch/RealUSBDevice.py +64 -0
- wasatch/SPIDevice.py +628 -0
- wasatch/SpectrometerRequest.py +15 -0
- wasatch/SpectrometerResponse.py +38 -0
- wasatch/SpectrometerSettings.py +446 -0
- wasatch/SpectrometerState.py +255 -0
- wasatch/StatusMessage.py +14 -0
- wasatch/TCPDevice.py +409 -0
- wasatch/TakeOneRequest.py +43 -0
- wasatch/WasatchBus.py +87 -0
- wasatch/WasatchDevice.py +1060 -0
- wasatch/WasatchDeviceWrapper.py +452 -0
- wasatch/WrapperWorker.py +304 -0
- wasatch/__init__.py +4 -0
- wasatch/applog.py +177 -0
- wasatch/simulation_protocol.py +227 -0
- wasatch/utils.py +731 -0
- wasatch-2.3.2.dist-info/LICENSE +21 -0
- wasatch-2.3.2.dist-info/METADATA +347 -0
- wasatch-2.3.2.dist-info/RECORD +53 -0
- 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
|