wasatch 2.3.24__tar.gz → 2.4.0__tar.gz
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-2.3.24 → wasatch-2.4.0}/CHANGELOG.md +81 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/PKG-INFO +1 -1
- {wasatch-2.3.24 → wasatch-2.4.0}/WasatchShell/wasatch-shell.py +4 -4
- {wasatch-2.3.24 → wasatch-2.4.0}/pyproject.toml +1 -1
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/AbstractUSBDevice.py +4 -2
- wasatch-2.4.0/wasatch/AccessoryConnector.py +75 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/AndorDevice.py +84 -80
- wasatch-2.4.0/wasatch/AssemblyRevision.py +56 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/AutoRaman.py +213 -105
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/BLEDevice.py +52 -35
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/CSVLoader.py +3 -4
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DeviceFinderUSB.py +4 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/EEPROM.py +247 -108
- wasatch-2.4.0/wasatch/EtalonCorrection.py +73 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/FeatureIdentificationDevice.py +303 -273
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/FirmwareRequirements.py +5 -4
- wasatch-2.4.0/wasatch/HardwareInfo.py +15 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/IDSCamera.py +5 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/IDSDevice.py +238 -52
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/IMX385.py +42 -0
- wasatch-2.4.0/wasatch/InGaAsCorrection.py +79 -0
- wasatch-2.4.0/wasatch/InterfaceDevice.py +255 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/MockUSBDevice.py +3 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/OceanDevice.py +2 -41
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/ProcessedReading.py +29 -1
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/Reading.py +3 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/RealUSBDevice.py +5 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/SPIDevice.py +1 -39
- wasatch-2.4.0/wasatch/SpectrometerRequest.py +41 -0
- wasatch-2.4.0/wasatch/SpectrometerResponse.py +41 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/SpectrometerSettings.py +75 -22
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/SpectrometerState.py +5 -3
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/TCPDevice.py +1 -2
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/USBCPowerConnectionState.py +34 -9
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/WasatchBus.py +10 -12
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/WasatchDevice.py +126 -234
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/WasatchDeviceWrapper.py +34 -6
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/WrapperWorker.py +17 -35
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/__init__.py +1 -1
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/utils.py +12 -0
- wasatch-2.3.24/wasatch/CommandSettings.py +0 -95
- wasatch-2.3.24/wasatch/HardwareInfo.py +0 -36
- wasatch-2.3.24/wasatch/InterfaceDevice.py +0 -48
- wasatch-2.3.24/wasatch/PollStatus.py +0 -15
- wasatch-2.3.24/wasatch/SpectrometerRequest.py +0 -15
- wasatch-2.3.24/wasatch/SpectrometerResponse.py +0 -38
- {wasatch-2.3.24 → wasatch-2.4.0}/.example_pypirc +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/.flake8 +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/.gitignore +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/.pylintrc +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/Doxyfile +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/LICENSE +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/MANIFEST.in +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/Makefile +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/README.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/README_PYPI.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/README_RPI.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/README_SETTINGS.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/README_VIRTUAL.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/WasatchShell/README.md +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/WasatchShell/laser-test.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/WasatchShell/load-test.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/WasatchShell/one-shot.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo-auto-raman.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo-ids-raman.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo-simple.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo-virtual.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo-workflow.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/demo.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/environments/conda-linux.yml +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/environments/conda-macos.yml +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/environments/conda-rpi.yml +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/environments/conda-win10.yml +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/screenshots/multiplatform.png +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/scripts/dark-vs-integ.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/scripts/deploy +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/tests/existing_implementations.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/tests/noise-by-setpoint.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/tests/queues.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/tests/unit.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/udev/10-wasatch.rules +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/AreaScanImage.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/AutoRamanRequest.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/BalanceAcquisition.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/ControlObject.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DetectorROI.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DetectorRegions.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DeviceFinderBLE.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DeviceFinderTCP.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/DeviceID.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/EEPROM_multical.sav +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/FPGAOptions.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/ROI.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/StatusMessage.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/TakeOneRequest.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/applog.py +0 -0
- {wasatch-2.3.24 → wasatch-2.4.0}/wasatch/simulation_protocol.py +0 -0
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
_Note: in the following, FID = wasatch.FeatureInterfaceDevice_
|
|
4
|
+
|
|
5
|
+
- 2026-05-27 2.4.0 (enlighten-4.2.2)
|
|
6
|
+
- InterfaceDevice
|
|
7
|
+
- This was a major change. The InterfaceDevice base class has existed for
|
|
8
|
+
years, but didn't contribute anything beyond handle_requests. I've
|
|
9
|
+
moved message_queue and alert_queue up from WasatchDevice/FID so all
|
|
10
|
+
InterfaceDevice subclasses can use that functionality directly.
|
|
11
|
+
- Also, the handle_requests([ SpectrometerRequest(cmd, args=[value]) ])[0]
|
|
12
|
+
syntax was neurose-inducing, so I've added convenience shortcuts for
|
|
13
|
+
handle_request(SpectrometerRequest) (singular), and handle_cmd(name[, value]).
|
|
14
|
+
- WasatchDevice
|
|
15
|
+
- deprecated command_queue (pending_commands)
|
|
16
|
+
- handle_requests now directly peeks into FID to see which commands can
|
|
17
|
+
be handled by "the hardware layer," and processes those commands in
|
|
18
|
+
real-time, accumulating the list of responses. This allows callers to
|
|
19
|
+
use gettor methods like GET_LASER_WARNING_TIME_SEC and actually receive
|
|
20
|
+
the response value, which was previously impossible when such commands
|
|
21
|
+
were shoved onto the "pending command queue" (which offered no method
|
|
22
|
+
for response delivery).
|
|
23
|
+
- FeatureInterfaceDevice
|
|
24
|
+
- deprecated SpectrumAndRow (legacy Area Scan design)
|
|
25
|
+
- removed handle_requests overload (not sure why this was there)
|
|
26
|
+
- added get_power_connection_state
|
|
27
|
+
- added periodic check for late-arriving attributes like ble_firmware_version
|
|
28
|
+
- enabled get_microcontroller_serial_number
|
|
29
|
+
- tweak laser password validation logic
|
|
30
|
+
- add default laser warning delay sec for old FW
|
|
31
|
+
- IDSDevice
|
|
32
|
+
- paired laser_device
|
|
33
|
+
- A key goal for this release was to allow IDSDevice "sensor
|
|
34
|
+
control" objects to "pair" with WasatchDevice "laser control"
|
|
35
|
+
objects, for the new case where an IDS sensor was being combined
|
|
36
|
+
with a Wasatch laser driver to create a 2-device Raman spectrometer.
|
|
37
|
+
- Note that once "paired," they essentially merge EEPROMs (starting
|
|
38
|
+
with whatever WasatchDevice loaded from USB, then folding-in
|
|
39
|
+
whatever IDSDevice found in JSON).
|
|
40
|
+
- In this first implementation, I chose to connect the two (make one
|
|
41
|
+
device "discoverable" by the other) through WasatchDeviceWrapper.
|
|
42
|
+
That created a weird import loop and maybe was higher than it
|
|
43
|
+
needed to be. I may refactor this later so they're discoverable /
|
|
44
|
+
joined at the InterfaceDevice level itself, which resolves the loop
|
|
45
|
+
and makes the pair usable without WasatchDeviceWrapper (a class
|
|
46
|
+
essentially designed by and for ENLIGHTEN).
|
|
47
|
+
- Part of the point of the above was to support a subtype of Auto-Raman,
|
|
48
|
+
tentatively called Auto-Collection, which skips the integ/gain
|
|
49
|
+
optimization and goes straight for averaging dark-collected Raman
|
|
50
|
+
within a measurement window.
|
|
51
|
+
- EEPROM
|
|
52
|
+
- A third key goal of this release was to support all the new EEPROM fields
|
|
53
|
+
added from Rev18 to Rev19 of ENG-0034, including:
|
|
54
|
+
- startup_laser_tec_setpoint
|
|
55
|
+
- light_source_type
|
|
56
|
+
- assembly_revision (and AssemblyRevision)
|
|
57
|
+
- feature_mask_xs
|
|
58
|
+
- max_battery_temp_deg_c
|
|
59
|
+
- max_laser_temp_deg_c
|
|
60
|
+
- acc_state
|
|
61
|
+
- acc_state_gpio1
|
|
62
|
+
- acc_state_gpio2
|
|
63
|
+
- acc_cont_strobe_count
|
|
64
|
+
- acc_cont_strobe_delay_us
|
|
65
|
+
- acc_cont_strobe_period_us
|
|
66
|
+
- acc_cont_strobe_width_us
|
|
67
|
+
- pixel_correction_type
|
|
68
|
+
- deprecated baud_rate, linearity_coeffs
|
|
69
|
+
- made EEPROM subclasses JSON serializable (hopefully)
|
|
70
|
+
- fixed EEPROM.pack for binary types ("*") for our growing set of "binary"
|
|
71
|
+
(marshalled) fields
|
|
72
|
+
- moved further into using the new un/pack_field functions, automatically
|
|
73
|
+
pulling offset/size/type from eeprom_fields table
|
|
74
|
+
- revisited user_text / user_data relationship
|
|
75
|
+
- consolidating backup location when writing EEPROM (can probably just
|
|
76
|
+
get rid of this)
|
|
77
|
+
- BLEDevice
|
|
78
|
+
- added laser PWM
|
|
79
|
+
- AndorDevice
|
|
80
|
+
- renamed "shutter_enable" (whatever that meant) to "shutter_open"
|
|
81
|
+
- SpectrometerResponse
|
|
82
|
+
- deprecated progress and incomplete
|
|
83
|
+
- starting to consider Safe Mode
|
|
3
84
|
- 2026-04-18 2.3.24
|
|
4
85
|
- add avg_resolution to IDSDevice EEPROM
|
|
5
86
|
- change XS attenuator default from 127 to 40
|
|
@@ -105,7 +105,7 @@ class WasatchShell:
|
|
|
105
105
|
"get_selected_adc", # SR
|
|
106
106
|
"get_selected_laser", # SR
|
|
107
107
|
"get_sensor_line_length", # SR
|
|
108
|
-
"
|
|
108
|
+
"get_shutter_open", # SR
|
|
109
109
|
"get_strobe_enabled", # SR
|
|
110
110
|
"get_tec_enabled", # SR
|
|
111
111
|
"get_trigger_delay", # SR
|
|
@@ -146,7 +146,7 @@ class WasatchShell:
|
|
|
146
146
|
|
|
147
147
|
set_fan_enable - takes bool argument
|
|
148
148
|
set_lamp_enable - takes bool argument
|
|
149
|
-
|
|
149
|
+
set_shutter_open - takes bool argument
|
|
150
150
|
set_strobe_enable - takes bool argument
|
|
151
151
|
set_mod_enable - takes bool argument
|
|
152
152
|
set_mod_period_us - takes int argument
|
|
@@ -394,8 +394,8 @@ class WasatchShell:
|
|
|
394
394
|
self.device.change_setting("lamp_enable", self.read_bool())
|
|
395
395
|
self.display(1)
|
|
396
396
|
|
|
397
|
-
elif command == "
|
|
398
|
-
self.device.change_setting("
|
|
397
|
+
elif command == "set_shutter_open":
|
|
398
|
+
self.device.change_setting("shutter_open", self.read_bool())
|
|
399
399
|
self.display(1)
|
|
400
400
|
|
|
401
401
|
elif command == "set_strobe_enable":
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class GPIOState:
|
|
4
|
+
MODE_FUNCTION = 0
|
|
5
|
+
MODE_MANUAL = 1
|
|
6
|
+
|
|
7
|
+
DIR_INPUT = 0
|
|
8
|
+
DIR_OUTPUT = 1
|
|
9
|
+
|
|
10
|
+
GPIO_FUNCTION_DISABLED = 0
|
|
11
|
+
|
|
12
|
+
GPIO1_FUNCTION_EXT_TRIGGER = 1
|
|
13
|
+
GPIO1_FUNCTION_LASER_OVERRIDE = 2
|
|
14
|
+
|
|
15
|
+
GPIO2_FUNCTION_CONT_STROBE = 1
|
|
16
|
+
GPIO2_FUNCTION_DATA_READY = 2
|
|
17
|
+
GPIO2_FUNCTION_LASER_MIRROR = 3
|
|
18
|
+
GPIO2_FUNCTION_LASER_ERROR = 4
|
|
19
|
+
|
|
20
|
+
def __init__(self, num):
|
|
21
|
+
self.num = num
|
|
22
|
+
self.mode = GPIOState.MODE_FUNCTION
|
|
23
|
+
self.dir = GPIOState.DIR_INPUT
|
|
24
|
+
self.function = GPIOState.GPIO_FUNCTION_DISABLED
|
|
25
|
+
self.value = 0
|
|
26
|
+
|
|
27
|
+
def serialize(self):
|
|
28
|
+
mask = 0x00
|
|
29
|
+
mask |= 0x01 if self.mode == self.MODE_MANUAL else 0
|
|
30
|
+
mask |= 0x02 if self.dir == self.DIR_OUTPUT else 0
|
|
31
|
+
if self.dir == self.DIR_OUTPUT:
|
|
32
|
+
mask |= 0x04 if self.value else 0
|
|
33
|
+
# bit 3 reserved
|
|
34
|
+
mask |= (self.function << 4)
|
|
35
|
+
|
|
36
|
+
def deserialize(self, mask):
|
|
37
|
+
self.mode = mask & 0x01
|
|
38
|
+
self.dir = mask & 0x02
|
|
39
|
+
if self.dir == self.DIR_INPUT:
|
|
40
|
+
self.value = mask & 0x04
|
|
41
|
+
self.function = (mask >> 4) & 0xf
|
|
42
|
+
|
|
43
|
+
def __repr__(self):
|
|
44
|
+
return f"GPIO{self.num}< mode {self.mode}, dir {self.dir}, func {self.function}, value {self.value} >"
|
|
45
|
+
|
|
46
|
+
class AccessoryConnector:
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
self.acc_gpio_enabled = False
|
|
50
|
+
self.acc_5v_enabled = False
|
|
51
|
+
self.acc_5v_good = False # read-only
|
|
52
|
+
|
|
53
|
+
self.state_gpio1 = GPIOState(1)
|
|
54
|
+
self.state_gpio2 = GPIOState(2)
|
|
55
|
+
|
|
56
|
+
self.strobe_period_us = None
|
|
57
|
+
self.strobe_width_us = None
|
|
58
|
+
self.strobe_delay_us = None
|
|
59
|
+
self.strobe_count = None
|
|
60
|
+
|
|
61
|
+
def serialize_acc_state(self):
|
|
62
|
+
mask = 0x0000
|
|
63
|
+
mask |= 0x0001 if self.acc_gpio_enabled else 0
|
|
64
|
+
mask |= 0x0002 if self.acc_5v_enabled else 0
|
|
65
|
+
# acc_5v_good read-only
|
|
66
|
+
# bits 3-15 reserved
|
|
67
|
+
return mask
|
|
68
|
+
|
|
69
|
+
def deserialize_acc_state(self, mask):
|
|
70
|
+
self.acc_gpio_enabled = 0 != mask & 0x01
|
|
71
|
+
self.acc_5v_enabled = 0 != mask & 0x02
|
|
72
|
+
self.acc_5v_good = 0 != mask & 0x04 # read-only
|
|
73
|
+
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
return f"AccessoryConnector < gpio_enabled {self.acc_gpio_enabled}, 5v_enabled {self.acc_5v_enabled}, 5v_good {self.acc_5v_good}"
|
|
@@ -24,28 +24,6 @@ class AndorDevice(InterfaceDevice):
|
|
|
24
24
|
|
|
25
25
|
@todo have check_result return a SpectrometerResponse
|
|
26
26
|
@todo try to auto-detect whether x-axis needs inverted via DLL.GetImageFlip()
|
|
27
|
-
|
|
28
|
-
##########################################################################
|
|
29
|
-
This class adopts the external device interface structure.
|
|
30
|
-
This involves receiving a request through the handle_request function.
|
|
31
|
-
A request is processed based on the key in the request.
|
|
32
|
-
The processing function passes the commands to the requested device.
|
|
33
|
-
Once it receives a response from the connected device it then passes that
|
|
34
|
-
back up the chain.
|
|
35
|
-
@verbatim
|
|
36
|
-
Enlighten Request
|
|
37
|
-
|
|
|
38
|
-
handle_requests
|
|
39
|
-
|
|
|
40
|
-
-----------
|
|
41
|
-
| | | | |
|
|
42
|
-
{ get_laser status, acquire, set_laser_watchdog, etc....}
|
|
43
|
-
| | | | |
|
|
44
|
-
-----------
|
|
45
|
-
|
|
|
46
|
-
{self.driver.some_andor_sdk_call}
|
|
47
|
-
@endverbatim
|
|
48
|
-
############################################################################
|
|
49
27
|
"""
|
|
50
28
|
|
|
51
29
|
SUCCESS = 20002 #!< see load_error_codes()
|
|
@@ -55,29 +33,15 @@ class AndorDevice(InterfaceDevice):
|
|
|
55
33
|
DRIVER_NOT_INSTALLED = False
|
|
56
34
|
|
|
57
35
|
def __init__(self, device_id, message_queue=None, alert_queue=None):
|
|
58
|
-
|
|
59
|
-
super().__init__()
|
|
36
|
+
super().__init__(device_id=device_id, message_queue=message_queue, alert_queue=alert_queue)
|
|
60
37
|
|
|
61
38
|
if self.DRIVER_NOT_INSTALLED:
|
|
62
39
|
raise InterfaceDeviceClassUnavailable("Andor driver not installed")
|
|
63
40
|
|
|
64
|
-
if type(device_id) is str:
|
|
65
|
-
device_id = DeviceID(label=device_id)
|
|
66
|
-
|
|
67
|
-
self.device_id = device_id
|
|
68
|
-
self.message_queue = message_queue
|
|
69
|
-
self.alert_queue = alert_queue
|
|
70
|
-
|
|
71
41
|
self.load_error_codes()
|
|
72
42
|
|
|
73
43
|
self.connected = False
|
|
74
44
|
|
|
75
|
-
# Receives ENLIGHTEN's 'change settings' commands in the spectrometer
|
|
76
|
-
# process.
|
|
77
|
-
self.command_queue = []
|
|
78
|
-
|
|
79
|
-
self.immediate_mode = False
|
|
80
|
-
|
|
81
45
|
# An AndorDevice has-a SpectrometerSettings, which has-a EEPROM, which
|
|
82
46
|
# has-a MultiWavelengthCalibration
|
|
83
47
|
self.settings = SpectrometerSettings(self.device_id)
|
|
@@ -179,40 +143,39 @@ class AndorDevice(InterfaceDevice):
|
|
|
179
143
|
except:
|
|
180
144
|
log.error(f"failed to enqueue StatusMessage {msg}", exc_info=1)
|
|
181
145
|
|
|
182
|
-
def not_implemented(self):
|
|
146
|
+
def not_implemented(self, arg):
|
|
183
147
|
pass
|
|
184
148
|
|
|
185
149
|
def _init_process_funcs(self):
|
|
186
150
|
process_f = {}
|
|
187
151
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
152
|
+
# setting and function have same name
|
|
153
|
+
for fn_name in [
|
|
154
|
+
"connect",
|
|
155
|
+
"acquire_data",
|
|
156
|
+
"set_shutter_open",
|
|
157
|
+
"set_integration_time_ms",
|
|
158
|
+
"get_serial_number",
|
|
159
|
+
"init_tec_setpoint",
|
|
160
|
+
"set_tec_setpoint",
|
|
161
|
+
"init_detector_area",
|
|
162
|
+
"scans_to_average",
|
|
163
|
+
"high_gain_mode_enable",
|
|
164
|
+
"save_config",
|
|
165
|
+
"set_vertical_binning",
|
|
166
|
+
"set_ingaas_correction_enable",
|
|
167
|
+
"set_etalon_correction_enable" ]:
|
|
168
|
+
process_f[fn_name] = getattr(self, fn_name)
|
|
169
|
+
|
|
170
|
+
# setting and function have different names
|
|
171
|
+
process_f["integration_time_ms"] = self.set_integration_time_ms
|
|
172
|
+
process_f["fan_enable"] = self.set_fan_enable
|
|
173
|
+
process_f["shutter_open"] = self.set_shutter_open
|
|
174
|
+
process_f["detector_tec_enable"] = self.set_detector_tec_enable
|
|
175
|
+
process_f["detector_tec_setpoint_degC"] = self.set_tec_setpoint
|
|
199
176
|
process_f["vertical_binning"] = self.set_vertical_binning
|
|
200
177
|
process_f["take_one_request"] = self.set_take_one_request
|
|
201
178
|
|
|
202
|
-
process_f["reset_scan_averaging"] = self.not_implemented
|
|
203
|
-
process_f["heartbeat"] = lambda x: None
|
|
204
|
-
|
|
205
|
-
##################################################################
|
|
206
|
-
# What follows is the old init-lambdas that are squashed into process_f
|
|
207
|
-
# Long term, the upstream requests should be changed to match the new format
|
|
208
|
-
# This is an easy fix for the time being to make things behave
|
|
209
|
-
##################################################################
|
|
210
|
-
process_f["integration_time_ms"] = lambda x: self.set_integration_time_ms(x)
|
|
211
|
-
process_f["fan_enable"] = lambda x: self.set_fan_enable(bool(x))
|
|
212
|
-
process_f["shutter_enable"] = lambda x: self.set_shutter_enable(bool(x))
|
|
213
|
-
process_f["detector_tec_enable"] = lambda x: self.toggle_tec(bool(x))
|
|
214
|
-
process_f["detector_tec_setpoint_degC"] = lambda x: self.set_tec_setpoint(int(round(x)))
|
|
215
|
-
|
|
216
179
|
return process_f
|
|
217
180
|
|
|
218
181
|
def high_gain_mode_enable(self, enabled):
|
|
@@ -227,9 +190,13 @@ class AndorDevice(InterfaceDevice):
|
|
|
227
190
|
log.debug(f"for {enabled} setting gain to {self.gain_options[0]}")
|
|
228
191
|
return
|
|
229
192
|
|
|
230
|
-
def set_fan_enable(self,
|
|
231
|
-
|
|
232
|
-
|
|
193
|
+
def set_fan_enable(self, flag):
|
|
194
|
+
flag = True if flag else False
|
|
195
|
+
try:
|
|
196
|
+
self.check_result(self.driver.SetFanMode(int(x)), f"Andor Fan On {x}")
|
|
197
|
+
except:
|
|
198
|
+
return SpectrometerResponse(False)
|
|
199
|
+
return SpectrometerResponse(True)
|
|
233
200
|
|
|
234
201
|
def _get_default_data_dir(self):
|
|
235
202
|
if os.name == "nt":
|
|
@@ -271,6 +238,12 @@ class AndorDevice(InterfaceDevice):
|
|
|
271
238
|
if not spectrum.any():
|
|
272
239
|
self._queue_message("marquee_error", "Andor camera is saturated")
|
|
273
240
|
|
|
241
|
+
if self.settings.ingaas_correction:
|
|
242
|
+
spectrum = self.settings.ingaas_correction.apply(spectrum)
|
|
243
|
+
|
|
244
|
+
if self.settings.etalon_correction:
|
|
245
|
+
spectrum = self.settings.etalon_correction.apply(spectrum)
|
|
246
|
+
|
|
274
247
|
# log.debug(f"_get_spectrum_raw: returning spectrum {spectrum}")
|
|
275
248
|
return spectrum
|
|
276
249
|
|
|
@@ -389,13 +362,29 @@ class AndorDevice(InterfaceDevice):
|
|
|
389
362
|
return self.temperature_cache_value
|
|
390
363
|
|
|
391
364
|
def _close_ex_shutter(self):
|
|
365
|
+
# SetShutterEx(typ, internalMode, closingTimeMS, openingTimeMS, externalMode)
|
|
366
|
+
# where:
|
|
367
|
+
# - typ:
|
|
368
|
+
# 0 = output TTL LOW to open shutter
|
|
369
|
+
# 1 = output TTL HIGH to open shutter
|
|
370
|
+
#
|
|
371
|
+
# - mode:
|
|
372
|
+
# 0 = Fully Auto
|
|
373
|
+
# 1 = Permanently Open
|
|
374
|
+
# 2 = Permanently Closed
|
|
375
|
+
# 3 = UNDEFINED
|
|
376
|
+
# 4 = Open for FVB series
|
|
377
|
+
# 5 = Open for any series
|
|
378
|
+
|
|
379
|
+
# set TTL HIGH="open", internalMode PermanentlyOpen, externalMode PermanentlyClosed
|
|
392
380
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 2), "SetShutterEx(2)")
|
|
393
|
-
self.settings.state.
|
|
381
|
+
self.settings.state.shutter_open = False
|
|
394
382
|
return SpectrometerResponse(True)
|
|
395
383
|
|
|
396
384
|
def _open_ex_shutter(self):
|
|
385
|
+
# set TTL HIGH="open", internalMode PermanentlyOpen, externalMode PermanentlyOpen
|
|
397
386
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 1), "SetShutterEx(1)")
|
|
398
|
-
self.settings.state.
|
|
387
|
+
self.settings.state.shutter_open = True
|
|
399
388
|
return SpectrometerResponse(True)
|
|
400
389
|
|
|
401
390
|
###############################################################
|
|
@@ -461,8 +450,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
461
450
|
self.init_detector_speed() # step 12+13
|
|
462
451
|
|
|
463
452
|
# step 14
|
|
453
|
+
# set internal shutter to PermanentlyOpen, external shutter to Automatic
|
|
464
454
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 0), "SetShutterEx(fully automatic external with internal always open)")
|
|
465
|
-
self.settings.state.
|
|
455
|
+
self.settings.state.shutter_open = True
|
|
466
456
|
|
|
467
457
|
# step 15
|
|
468
458
|
self.set_integration_time_ms(self.settings.eeprom.startup_integration_time_ms)
|
|
@@ -560,8 +550,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
560
550
|
"""
|
|
561
551
|
Loads configuration from file `self.config_file` and populates `self.settings.eeprom` with members.
|
|
562
552
|
"""
|
|
563
|
-
|
|
564
|
-
|
|
553
|
+
with open(self.config_file) as f:
|
|
554
|
+
self.config_values = json.load(f)
|
|
555
|
+
|
|
565
556
|
log.debug(f"loaded {self.config_file}: {self.config_values}")
|
|
566
557
|
|
|
567
558
|
# handle wp_ prefixes
|
|
@@ -570,6 +561,7 @@ class AndorDevice(InterfaceDevice):
|
|
|
570
561
|
self.settings.eeprom.multi_wavelength_calibration.set(v, self.config_values[k])
|
|
571
562
|
|
|
572
563
|
# same spelling
|
|
564
|
+
# consider just using self.settings.eeprom.fields.keys()
|
|
573
565
|
for k in [ 'model',
|
|
574
566
|
'stubbed',
|
|
575
567
|
'detector',
|
|
@@ -597,17 +589,20 @@ class AndorDevice(InterfaceDevice):
|
|
|
597
589
|
if 'startup_temp_degC' in self.config_values:
|
|
598
590
|
self.set_tec_setpoint(self.settings.eeprom.startup_temp_degC)
|
|
599
591
|
|
|
592
|
+
# pick up Pixel Corrections like EtalonCorrection, InGaAsCorrection etc
|
|
593
|
+
self.settings.augment_from_json_data(data)
|
|
594
|
+
|
|
600
595
|
def acquire_data(self):
|
|
601
596
|
# handle TakeOneRequest.take_dark
|
|
602
597
|
dark_reading = None
|
|
603
598
|
tor = self.take_one_request
|
|
604
599
|
log.debug(f"acquire_data: tor {tor}")
|
|
605
600
|
if tor and tor.take_dark:
|
|
606
|
-
self.
|
|
601
|
+
self.set_shutter_open(False)
|
|
607
602
|
dark_reading = self._take_one_averaged_reading()
|
|
608
|
-
if
|
|
603
|
+
if dark_reading is None:
|
|
609
604
|
return SpectrometerResponse(False, error_msg="failed to collect dark")
|
|
610
|
-
self.
|
|
605
|
+
self.set_shutter_open(True)
|
|
611
606
|
|
|
612
607
|
# get spectrum (potentially averaged)
|
|
613
608
|
reading = self._take_one_averaged_reading()
|
|
@@ -647,8 +642,8 @@ class AndorDevice(InterfaceDevice):
|
|
|
647
642
|
log.debug(f"acquire_data: reading {reading}")
|
|
648
643
|
return SpectrometerResponse(data=reading)
|
|
649
644
|
|
|
650
|
-
def
|
|
651
|
-
if
|
|
645
|
+
def set_shutter_open(self, flag):
|
|
646
|
+
if flag:
|
|
652
647
|
return self._open_ex_shutter()
|
|
653
648
|
else:
|
|
654
649
|
return self._close_ex_shutter()
|
|
@@ -696,16 +691,17 @@ class AndorDevice(InterfaceDevice):
|
|
|
696
691
|
|
|
697
692
|
return SpectrometerResponse(True)
|
|
698
693
|
|
|
699
|
-
def
|
|
700
|
-
|
|
701
|
-
self.tec_enabled =
|
|
702
|
-
if
|
|
694
|
+
def set_detector_tec_enable(self, flag):
|
|
695
|
+
flag = True if flag else False
|
|
696
|
+
self.tec_enabled = flag
|
|
697
|
+
if flag:
|
|
703
698
|
self.check_result(self.driver.CoolerON(), "CoolerON")
|
|
704
699
|
else:
|
|
705
700
|
self.check_result(self.driver.CoolerOFF(), "CoolerOFF")
|
|
706
701
|
return SpectrometerResponse(True)
|
|
707
702
|
|
|
708
703
|
def set_tec_setpoint(self, set_temp):
|
|
704
|
+
set_temp = int(round(set_temp, 0))
|
|
709
705
|
if set_temp < self.settings.eeprom.min_temp_degC or set_temp > self.settings.eeprom.max_temp_degC:
|
|
710
706
|
log.error(f"requested temp of {set_temp}, but it is outside range ({self.settings.eeprom.min_temp_degC}C, {self.settings.eeprom.max_temp_degC}C)")
|
|
711
707
|
return
|
|
@@ -776,6 +772,14 @@ class AndorDevice(InterfaceDevice):
|
|
|
776
772
|
log.debug(f"set AD channel {ADnumber} with horizontal speed {HSnumber} ({STemp})")
|
|
777
773
|
return SpectrometerResponse(True)
|
|
778
774
|
|
|
775
|
+
def set_ingaas_correction_enable(self, flag):
|
|
776
|
+
if self.settings.ingaas_correction:
|
|
777
|
+
self.settings.ingaas_correction.enable = flag
|
|
778
|
+
|
|
779
|
+
def set_etalon_correction_enable(self, flag):
|
|
780
|
+
if self.settings.etalon_correction:
|
|
781
|
+
self.settings.etalon_correction.enable = flag
|
|
782
|
+
|
|
779
783
|
def set_scans_to_average(self, value):
|
|
780
784
|
value = int(value)
|
|
781
785
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from . import utils
|
|
3
|
+
|
|
4
|
+
class AssemblyRevision(dict):
|
|
5
|
+
"""
|
|
6
|
+
Making this a standalone class because might end up using from both FID (USB)
|
|
7
|
+
and BLEDevice.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, data=None):
|
|
10
|
+
dict.__init__(self) # https://stackoverflow.com/a/31207881
|
|
11
|
+
self.partnumber = None
|
|
12
|
+
self.variant = None
|
|
13
|
+
self.revision = None
|
|
14
|
+
|
|
15
|
+
if isinstance(data, str):
|
|
16
|
+
data = utils.hex_string_to_data(data)
|
|
17
|
+
|
|
18
|
+
self.parse_data(data)
|
|
19
|
+
|
|
20
|
+
def parse_data(self, data):
|
|
21
|
+
"""
|
|
22
|
+
This is based on the data structure defined in ENG-0120 Rev 10.
|
|
23
|
+
"""
|
|
24
|
+
if data is None or len(data) != 6:
|
|
25
|
+
log.error(f"parse_data: invalid length {data}")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
self.partnumber = (data[0] << 8) + data[1] + 140_000
|
|
29
|
+
self.variant = (data[2] << 8) + data[3]
|
|
30
|
+
self.revision = (data[4] << 8) + data[5]
|
|
31
|
+
|
|
32
|
+
def serialize(self):
|
|
33
|
+
""" probably a simpler way to do this with struct """
|
|
34
|
+
buf = [ 0 ] * 6
|
|
35
|
+
if self.partnumber:
|
|
36
|
+
n = self.partnumber - 140_000
|
|
37
|
+
buf[0] = (n >> 8) & 0xff
|
|
38
|
+
buf[1] = n & 0xff
|
|
39
|
+
if self.variant:
|
|
40
|
+
n = self.variant
|
|
41
|
+
buf[2] = (n >> 8) & 0xff
|
|
42
|
+
buf[3] = n & 0xff
|
|
43
|
+
if self.revision:
|
|
44
|
+
n = self.revision
|
|
45
|
+
buf[4] = (n >> 8) & 0xff
|
|
46
|
+
buf[5] = n & 0xff
|
|
47
|
+
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
s = ""
|
|
50
|
+
if self.partnumber and self.partnumber != 140_000:
|
|
51
|
+
s += f"{self.partnumber}"
|
|
52
|
+
if self.variant:
|
|
53
|
+
s += f"-{self.variant}"
|
|
54
|
+
if self.revision:
|
|
55
|
+
s += f" Rev{self.revision}"
|
|
56
|
+
return s
|