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
@@ -0,0 +1,107 @@
1
+ import logging
2
+ import asyncio
3
+
4
+ from datetime import datetime
5
+ from bleak import BleakScanner
6
+
7
+ from wasatch.BLEDevice import BLEDevice
8
+ from wasatch.DeviceID import DeviceID
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+ class DiscoveredBLEDevice:
13
+ def __init__(self, device_id, rssi):
14
+ self.device_id = device_id
15
+ self.rssi = rssi
16
+
17
+ class DeviceFinderBLE:
18
+ """ @see consolidated BLE docs in wasatch.BLEDevice """
19
+
20
+ def __init__(self, discovery_queue=None, discovery_callback=None, search_timeout_sec=30):
21
+
22
+ self.discovery_queue = discovery_queue
23
+ self.discovery_callback = discovery_callback
24
+ self.search_timeout_sec = search_timeout_sec
25
+
26
+ self.keep_scanning = True
27
+ self.stop_scanning_event = asyncio.Event()
28
+
29
+ async def search_for_devices(self):
30
+ log.debug("search_for_devices: start")
31
+ self.start_time = datetime.now()
32
+
33
+ # for some reason asyncio.timeout() isn't in my Python 3.10.15, so kludging
34
+ async def cancel_task(sec):
35
+ await asyncio.sleep(sec)
36
+ self.stop_scanning()
37
+
38
+ try:
39
+ log.debug(f"search_for_devices: scheduling cancel_task in {self.search_timeout_sec}sec")
40
+ task = asyncio.create_task(cancel_task(self.search_timeout_sec))
41
+
42
+ cb = self.detection_callback
43
+ uuid = BLEDevice.WASATCH_SERVICE
44
+ log.debug(f"search_for_devices: instantiating BleakScanner with callback {cb}, uuid {uuid}")
45
+ async with BleakScanner(detection_callback=self.detection_callback, service_uuids=[BLEDevice.WASATCH_SERVICE]) as scanner:
46
+ log.debug(f"search_for_devices: awaiting stop_scanning_event")
47
+ await self.stop_scanning_event.wait()
48
+ except:
49
+ log.error("exception while scanning for devices", exc_info=1)
50
+
51
+ # scanner stops when block exits
52
+ log.debug("search_for_devices: done")
53
+
54
+ def detection_callback(self, bleak_ble_device, advertisement_data):
55
+ log.debug("detection_callback: start")
56
+ if not self.keep_scanning:
57
+ log.debug("detection_callback: no longer scanning...done")
58
+ return
59
+
60
+ elapsed_sec = (datetime.now() - self.start_time).total_seconds()
61
+ if elapsed_sec >= self.search_timeout_sec:
62
+ log.debug(f"detection_callback: timeout expired ({elapsed_sec}sec > {self.search_timeout_sec})...done")
63
+ self.stop_scanning()
64
+ return
65
+
66
+ log.debug(f"detection_callback: discovered device {bleak_ble_device}, advertisement_data {advertisement_data}")
67
+ if not self.is_xs(bleak_ble_device):
68
+ log.debug(f"detection_callback: not XS...ignoring")
69
+ return
70
+
71
+ serial_number = advertisement_data.local_name
72
+ rssi = advertisement_data.rssi
73
+ device_id = DeviceID(label=f"BLE:{serial_number}", bleak_ble_device=bleak_ble_device)
74
+
75
+ discovered_device = DiscoveredBLEDevice(
76
+ device_id = device_id,
77
+ rssi = rssi)
78
+ log.debug(f"detection_callback: instantiated {discovered_device}")
79
+
80
+ if self.discovery_queue:
81
+ log.debug(f"detection_callback: returning DiscoveredBLEDevice via queue")
82
+ self.discovery_queue.put_nowait(discovered_device)
83
+
84
+ if self.discovery_callback:
85
+ log.debug(f"detection_callback: returning DiscoveredBLEDevice via callback")
86
+ self.discovery_callback(discovered_device)
87
+
88
+ if not (self.discovery_queue or self.discovery_callback):
89
+ log.error("have neither queue nor callback to return DeviceID...?")
90
+
91
+ log.debug("detection_callback: done")
92
+
93
+ def stop_scanning(self):
94
+ log.debug("stop_scanning")
95
+ self.keep_scanning = False
96
+ self.stop_scanning_event.set()
97
+
98
+ # send a "poison-pill" upstream to notify caller that we're no longer scanning
99
+ self.discovery_queue.put_nowait(None)
100
+
101
+ def is_xs(self, device, advertisement_data=None):
102
+ if device is None:
103
+ return
104
+ elif advertisement_data:
105
+ return BLEDevice.WASATCH_SERVICE.lower() in advertisement_data.service_uuids.lower()
106
+ else:
107
+ return "WP-" in device.name.upper()
@@ -0,0 +1,20 @@
1
+ import logging
2
+
3
+ log = logging.getLogger(__name__)
4
+
5
+ class DeviceFinderTCP:
6
+ """
7
+ I started this class, but I'm not sure it's actually needed yet. Using
8
+ enlighten.Controller.other_device_ids for the moment.
9
+ """
10
+
11
+ def __init__(self):
12
+ pass
13
+
14
+ def find_tcp_devices(self):
15
+ device_ids = []
16
+
17
+ # for addr in self.addresses:
18
+ # ...
19
+
20
+ return device_ids
@@ -0,0 +1,236 @@
1
+ import re
2
+ import usb
3
+ import logging
4
+ import platform
5
+ from functools import partial
6
+
7
+ if platform.system() == "Darwin":
8
+ import usb.backend.libusb1 as backend
9
+ else:
10
+ import usb.backend.libusb0 as backend
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ from .DeviceID import DeviceID
15
+
16
+ class DeviceFinderUSB:
17
+ """
18
+ Generates a list of DeviceID objects for all connected USB Wasatch Photonics
19
+ spectrometers.
20
+
21
+ Probably only works with libusb (which rules-out IDS cameras).
22
+ """
23
+
24
+ # VIDs
25
+ WASATCH_VID = 0x24aa
26
+ OCEAN_VID = 0x2457
27
+ ANDOR_VID = 0x136e
28
+ FT232_SPI_VID = 0x0403
29
+
30
+ VALID_VID_LIST = [ WASATCH_VID, OCEAN_VID, ANDOR_VID, FT232_SPI_VID ]
31
+ STR_VALID_VIDS = [ f"{vid:04x}" for vid in VALID_VID_LIST ]
32
+
33
+ # PIDs
34
+ WP_HAMA_SILICON_PID = 0x1000
35
+ WP_HAMA_INGAAS_PID = 0x2000
36
+ WP_ARM_PID = 0x4000
37
+
38
+ USE_MONITORING = True
39
+ MIN_POLLING_SCANS = 10
40
+
41
+ def __init__(self):
42
+ self.startup_scan = 0
43
+ if platform.system() == "Windows":
44
+ log.debug("configuring for Windows")
45
+ import win32com.client
46
+
47
+ # see https://docs.microsoft.com/en-us/windows/win32/wmisdk/creating-a-wmi-script
48
+ obj_WMI_service = win32com.client.GetObject("winmgmts:")
49
+
50
+ # see https://docs.microsoft.com/en-us/windows/win32/wmisdk/querying-with-wql
51
+ raw_wql = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA \'Win32_PnPEntity\'"
52
+
53
+ # see https://docs.microsoft.com/en-us/windows/win32/wmisdk/monitoring-events
54
+ # while it removes polling the usb bus
55
+ # the polling now shifts to WMI events as the query is a polling operation
56
+ self.obj_events = obj_WMI_service.ExecNotificationQuery(raw_wql)
57
+ elif platform.system() == "Linux":
58
+ log.debug("configuring for Linux")
59
+ import pyudev
60
+ self.context = pyudev.Context()
61
+ self.monitor = pyudev.Monitor.from_netlink(self.context)
62
+ elif platform.system() == "Darwin":
63
+ log.debug("configuring for MacOS")
64
+ else:
65
+ pass
66
+
67
+ ##
68
+ # Iterates over each supported PID, searching for any Wasatch devices
69
+ # with a known VID/PID and generating a list of DeviceIDs.
70
+ #
71
+ # Note that DeviceID internally pulls more attributes from the Device object.
72
+ # def find_usb_devices_alternate_unused(self):
73
+ # vid = 0x24aa
74
+ # device_ids = []
75
+ # count = 0
76
+ # for pid in [0x1000, 0x2000, 0x4000]:
77
+ # # we could also remove iProduct and do one find on vid
78
+ # devices = usb.core.find(find_all=True, idVendor=vid, idProduct=pid)
79
+ # for device in devices:
80
+ # count += 1
81
+ # log.debug("DeviceFinderUSB: discovered vid 0x%04x, pid 0x%04x (count %d)", vid, pid, count)
82
+ #
83
+ # device_id = DeviceID(device=device)
84
+ # device_ids.append(device_id)
85
+ # return device_ids
86
+
87
+ ##
88
+ # Iterates over each USB bus, and each device on the bus, retaining any
89
+ # with a Wasatch Photonics VID and supported PID and instantiating a
90
+ # DeviceID for each.
91
+ #
92
+ # Note that DeviceID internally pulls more attributes from the Device object.
93
+ def bus_polling(self): # -> list[DeviceID]
94
+ device_ids = []
95
+ count = 0
96
+ devices = usb.core.find(find_all=True, backend=backend.get_backend())
97
+ for device in devices:
98
+ count += 1
99
+ vid = int(device.idVendor)
100
+ pid = int(device.idProduct)
101
+ log.debug("DeviceFinderUSB: discovered vid 0x%04x, pid 0x%04x (count %d), address %s", vid, pid, count, device.address)
102
+
103
+ if vid not in self.VALID_VID_LIST:
104
+ continue
105
+
106
+ if vid == self.WASATCH_VID and pid not in [ self.WP_HAMA_SILICON_PID, self.WP_HAMA_INGAAS_PID, self.WP_ARM_PID ]:
107
+ continue
108
+
109
+ device_id = DeviceID(device=device)
110
+ device_ids.append(device_id)
111
+ return device_ids
112
+
113
+ def linux_monitoring(self): # -> list[DeviceID]
114
+ device_ids = []
115
+ for device in iter(partial(self.monitor.poll, 0.001), None):
116
+ # sometimes I see events with None for the vendor id, those should be skipped
117
+ if device is not None and device.action == "add" and device.get('ID_VENDOR_ID') is not None:
118
+ device_ids.append(device)
119
+ log.debug(f"got a udev device add event")
120
+ valid_devices = [dev for dev in device_ids if dev.get('ID_VENDOR_ID').lower() in self.STR_VALID_VIDS]
121
+ pyusb_devices = [usb.core.find(idVendor=int(dev.get('ID_VENDOR_ID'), 16), idProduct=int(dev.get('ID_MODEL_ID'), 16)) for dev in valid_devices]
122
+ # if there is an error/can't find pyusb returns none
123
+ # filter those out
124
+ if None in pyusb_devices:
125
+ log.error(f"pyudev notified of a matching device, but error when doing pyusb query")
126
+ pyusb_devices = [dev for dev in pyusb_devices if dev is not None]
127
+ if pyusb_devices != []:
128
+ log.debug(f"pyudev returned devices of {pyusb_devices}")
129
+ return [DeviceID(device) for device in pyusb_devices]
130
+
131
+ # map across the STR_VALID_VIDS checking if it is in current id string, resulting in a bool array
132
+ # if any true in that bool array then any() is true and thus it will be in the list
133
+ def id_in_valid_vids(self, dev_id):
134
+ valid_vid_present = map(lambda valid: valid in dev_id, self.STR_VALID_VIDS)
135
+ return any(valid_vid_present)
136
+
137
+ def windows_monitoring(self): # -> list[DeviceID]
138
+ device_ids = []
139
+ try:
140
+ # the next event raises an error on timeout
141
+ # so if a connection has occured then add it to device ids
142
+ # when we stop seeing devices then pass on to the processing
143
+ while True:
144
+ obj_received = self.obj_events.NextEvent(0.001)
145
+ device_id = obj_received.Properties_("TargetInstance").Value.DeviceID
146
+ device_ids.append(device_id.lower())
147
+ except:
148
+ pass
149
+ if device_ids != []:
150
+ log.debug(f"found a WMI event of {device_ids}")
151
+ valid_vids = [dev_id for dev_id in device_ids if self.id_in_valid_vids(dev_id)]
152
+ pids = [re.findall(r'pid_(....)', dev) for dev in valid_vids]
153
+ pids = [id_num[0] for id_num in pids if id_num is not []]
154
+ vids = [re.findall(r'vid_(....)', dev) for dev in valid_vids]
155
+ vids = [id_num[0] for id_num in vids if id_num is not []]
156
+ # end by querying just the desired Wasatch Devices via pyusb
157
+ # this provides an easy meshing with our current setup using pyusb devices
158
+ pyusb_devices = [usb.core.find(idVendor=int(vid, 16), idProduct=int(pid, 16), backend=backend.get_backend()) for vid, pid in zip(vids, pids)]
159
+ # if there is an error/can't find pyusb returns none
160
+ # filter those out
161
+ if None in pyusb_devices:
162
+ log.error(f"WMI notified of a matching device, but error when doing pyusb query")
163
+ pyusb_devices = [dev for dev in pyusb_devices if dev is not None]
164
+ if pyusb_devices != []:
165
+ log.debug(f"WMI returned devices of {pyusb_devices}")
166
+ return [DeviceID(device) for device in pyusb_devices]
167
+
168
+ def find_usb_devices(self, poll = False):
169
+ # log.debug("DeviceFinderUSB.find_usb_devices: starting")
170
+ device_ids = []
171
+
172
+ # MZ/ED: If USE_MONITORING is True, I had to disable the call to remove_all in enlighten.Controller
173
+ if self.startup_scan < self.MIN_POLLING_SCANS or not self.USE_MONITORING or poll:
174
+ # our first few scans should always be a bus poll
175
+ # this is because no events will be registered
176
+ # log.debug(f"DeviceFinderUSB.find_usb_devices: just doing a bus poll for startup_scan {self.startup_scan}")
177
+ device_ids = self.bus_polling()
178
+ self.startup_scan += 1
179
+ elif platform.system() == "Windows":
180
+ device_ids = self.windows_monitoring()
181
+ elif platform.system() == "Linux":
182
+ device_ids = self.linux_monitoring()
183
+ else:
184
+ device_ids = self.bus_polling()
185
+ # log.debug(f"DeviceFinderUSB.find_usb_devices: returning {len(device_ids)} devices")
186
+ return device_ids
187
+
188
+ def mac_monitoring(self):
189
+ """
190
+ The challenge of implementing Mac usb events was decided not to be worth it
191
+ since it is such a small porition of users. I've included my code below the not implemented error
192
+ for reference in case we decide to implement later. This is incomplete and shouldn't be called.
193
+ """
194
+ raise NotImplementedError
195
+
196
+ from ctypes import cdll, util
197
+
198
+ #iokit = cdll.LoadLibrary(util.find_library('IOKit'))
199
+
200
+ # See ctypes for what these calls are doing
201
+ # For where iokit info is coming from
202
+ # Apple docs, which imo are bad with clear examples, if any,
203
+ # so I'm structuring this
204
+ # in a how to actually create and do way
205
+ # IO notifications in python using ctypes
206
+ # https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Introduction/Introduction.html
207
+ # AddMatchingNotification is our goal since that provides the iterator of devices
208
+ # https://developer.apple.com/documentation/iokit/1514362-ioserviceaddmatchingnotification?language=objc
209
+ # So our requirements are to create the following args
210
+ # IONotificationPortRef, io_name_t, CFDictionaryRef, IOServiceMatchingCallback, io_iterator_t
211
+
212
+ # For the IONotificationPortRef we call IONotificationPortCreate, which requires a mach_port_t
213
+ # the primary port comes from a constant used in the dylib
214
+ # https://developer.apple.com/documentation/iokit/1514480-ionotificationportcreate?language=objc
215
+ kIOMasterPortDefault = c_void_p.in_dll(iokit, 'kIOMasterPortDefault')
216
+ _ = iokit.IONotificationPortCreate(kIOMasterPortDefault)
217
+
218
+ # For the io_name_t that comes from a constant in the dll
219
+ # See the parameter description in the AddMatchinServiceNotification link at the top
220
+ # We want publish because it means whenever a connection occurs
221
+ _ = c_int.in_dll(iokit, 'kIOPublishNotification') # this throws an error that it doesn't have this symbol, idk why. It should export that
222
+
223
+ # CFDictionary creation call info, along with args to pass, can be found here
224
+ # https://developer.apple.com/documentation/corefoundation/1516791-cfdictionarycreatemutable?language=objc
225
+ # the pyobjc docs weren't helpful and just say this call exists but not how to call it
226
+ # we choose mutable when it could just be CFDictionaryCreate because it's easier to work with
227
+ # after creation this can be interacted with like a python dict
228
+ # the nonmutable requires passing c arrays for keys, values, which why bother when we can use python syntax
229
+ # expected keys example can be seen in xml doc in
230
+ # Figure 4-2 I found more helpful since it is for USB
231
+ # https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Matching/Matching.html
232
+ # XML docs say every dict requires IOProviderClass
233
+ # The value for the key can be found in the appendix of the driver docs
234
+ matching_dict = CFDictionaryCreateMutable(None, 0, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks)
235
+ matching_dict["IOProviderClass"] = "IOUSBDevice"
236
+ matching_dict["idVendor"] = 0x24aa # we can have the driver pre filter for wasatch, so I'm utilizing that