wasatch 2.3.12__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.12 → wasatch-2.4.0}/CHANGELOG.md +149 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/PKG-INFO +1 -1
- {wasatch-2.3.12 → wasatch-2.4.0}/WasatchShell/wasatch-shell.py +4 -4
- {wasatch-2.3.12 → wasatch-2.4.0}/pyproject.toml +1 -1
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/AbstractUSBDevice.py +4 -2
- wasatch-2.4.0/wasatch/AccessoryConnector.py +75 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/AndorDevice.py +94 -82
- wasatch-2.4.0/wasatch/AssemblyRevision.py +56 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/AutoRaman.py +233 -101
- wasatch-2.4.0/wasatch/BLEDevice.py +1428 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/CSVLoader.py +3 -4
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DeviceFinderBLE.py +1 -1
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DeviceFinderUSB.py +4 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DeviceID.py +3 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/EEPROM.py +257 -118
- wasatch-2.4.0/wasatch/EtalonCorrection.py +73 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/FeatureIdentificationDevice.py +371 -305
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/FirmwareRequirements.py +5 -4
- wasatch-2.4.0/wasatch/HardwareInfo.py +15 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/IDSCamera.py +7 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/IDSDevice.py +244 -53
- {wasatch-2.3.12 → 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.12 → wasatch-2.4.0}/wasatch/MockUSBDevice.py +3 -6
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/OceanDevice.py +2 -41
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/ProcessedReading.py +29 -1
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/Reading.py +20 -2
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/RealUSBDevice.py +5 -0
- {wasatch-2.3.12 → 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.12 → wasatch-2.4.0}/wasatch/SpectrometerSettings.py +95 -44
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/SpectrometerState.py +12 -2
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/TCPDevice.py +1 -2
- wasatch-2.4.0/wasatch/USBCPowerConnectionState.py +92 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/WasatchBus.py +10 -12
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/WasatchDevice.py +139 -214
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/WasatchDeviceWrapper.py +115 -50
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/WrapperWorker.py +51 -67
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/__init__.py +1 -1
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/utils.py +14 -0
- wasatch-2.3.12/wasatch/BLEDevice.py +0 -1032
- wasatch-2.3.12/wasatch/CommandSettings.py +0 -95
- wasatch-2.3.12/wasatch/HardwareInfo.py +0 -36
- wasatch-2.3.12/wasatch/InterfaceDevice.py +0 -40
- wasatch-2.3.12/wasatch/PollStatus.py +0 -15
- wasatch-2.3.12/wasatch/SpectrometerRequest.py +0 -15
- wasatch-2.3.12/wasatch/SpectrometerResponse.py +0 -38
- {wasatch-2.3.12 → wasatch-2.4.0}/.example_pypirc +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/.flake8 +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/.gitignore +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/.pylintrc +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/Doxyfile +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/LICENSE +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/MANIFEST.in +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/Makefile +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/README.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/README_PYPI.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/README_RPI.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/README_SETTINGS.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/README_VIRTUAL.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/WasatchShell/README.md +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/WasatchShell/laser-test.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/WasatchShell/load-test.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/WasatchShell/one-shot.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo-auto-raman.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo-ids-raman.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo-simple.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo-virtual.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo-workflow.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/demo.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/environments/conda-linux.yml +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/environments/conda-macos.yml +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/environments/conda-rpi.yml +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/environments/conda-win10.yml +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/screenshots/multiplatform.png +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/scripts/dark-vs-integ.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/scripts/deploy +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/tests/existing_implementations.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/tests/noise-by-setpoint.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/tests/queues.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/tests/unit.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/udev/10-wasatch.rules +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/AreaScanImage.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/AutoRamanRequest.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/BalanceAcquisition.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/ControlObject.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DetectorROI.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DetectorRegions.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/DeviceFinderTCP.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/EEPROM_multical.sav +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/FPGAOptions.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/ROI.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/StatusMessage.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/TakeOneRequest.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/applog.py +0 -0
- {wasatch-2.3.12 → wasatch-2.4.0}/wasatch/simulation_protocol.py +0 -0
|
@@ -1,5 +1,154 @@
|
|
|
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
|
|
84
|
+
- 2026-04-18 2.3.24
|
|
85
|
+
- add avg_resolution to IDSDevice EEPROM
|
|
86
|
+
- change XS attenuator default from 127 to 40
|
|
87
|
+
- 2026-04-16 2.3.23
|
|
88
|
+
- XS BLE
|
|
89
|
+
- tidied BLEDevice
|
|
90
|
+
- added USBCPowerConnectionState
|
|
91
|
+
- added SpectrometerState.power_connection_state
|
|
92
|
+
- added SpectrometerSettings.cpu_unique_id
|
|
93
|
+
- added SET_POWER_OFF, SET_RESET_UNIT, GET/SET_AUTO_RAMAN_PARAMS,
|
|
94
|
+
GET_IMAGE_SENSOR_STATE, GET_CPU_UNIQUE_ID, GET_USB_ADAPTER_INFO,
|
|
95
|
+
GET/SET_LASER_OFF_DELAY_MS
|
|
96
|
+
- suffixed all async methods with _async
|
|
97
|
+
- provided synchronous wrappers around user-facing asynchronous calls
|
|
98
|
+
- added BLEDevice.testing for enlighten.plugins.RnD.BluetoothTester
|
|
99
|
+
- battery percentage matches USB
|
|
100
|
+
- ambient_temperature_deg_c now flowing up to ENLIGHTEN
|
|
101
|
+
- POWER_OFF working
|
|
102
|
+
- decoding STM32 CPU_UNIQUE_ID (debug-only)
|
|
103
|
+
- ignore 1-line vertical ROI
|
|
104
|
+
- SET_AUTO_RAMAN_PARAMS bugfix
|
|
105
|
+
- 2026-04-10 2.3.22
|
|
106
|
+
- added Reading.protocol
|
|
107
|
+
- XS
|
|
108
|
+
- disable detector timeout during area scan
|
|
109
|
+
- adding Auto-Raman to BLEDevice
|
|
110
|
+
- only use one ACQUIRE to start Area Scan readout
|
|
111
|
+
- send extra ACQUIRE on occasional timeout
|
|
112
|
+
- 2026-04-09 2.3.21
|
|
113
|
+
- XS Area Scan
|
|
114
|
+
- fixed line increment
|
|
115
|
+
- disable area scan while changing line increment
|
|
116
|
+
- XS BLE
|
|
117
|
+
- move firmware revisions to SpectrometerSettings
|
|
118
|
+
- fix gain
|
|
119
|
+
- fix laser enable
|
|
120
|
+
- fixed heartbeat
|
|
121
|
+
- working on LASER_STATE and BATTERY_STATE
|
|
122
|
+
- 2026-04-09 2.3.20
|
|
123
|
+
- XS BLE
|
|
124
|
+
- fix None in utils.to_hex
|
|
125
|
+
- only parse first 8 EEPROM fields until BLE FW updated to support 9
|
|
126
|
+
- stop checking for old FX2 code string in corrupted EEPROMs
|
|
127
|
+
- support V2.EVT prefix
|
|
128
|
+
- add new ACQUIRE status codes
|
|
129
|
+
- add new SPECTRA Characteristic
|
|
130
|
+
- read as many EEPROM bytes per page as supported by Peripheral
|
|
131
|
+
- 2026-04-06 2.3.18
|
|
132
|
+
- replaced WrapperWorker.is_FOO with .class_name
|
|
133
|
+
- XL / Andor
|
|
134
|
+
- only warn once on missing driver
|
|
135
|
+
- more robust support for blank model
|
|
136
|
+
- default EEPROM.model
|
|
137
|
+
- XS
|
|
138
|
+
- support is_laser_firing on all XS
|
|
139
|
+
- 2026-04-01 2.3.17
|
|
140
|
+
- adding Reading.is_auto_raman()
|
|
141
|
+
- temporarily bumping XS spectra timeout
|
|
142
|
+
- filling in standard device meta-data in Auto-Raman Readings
|
|
143
|
+
- 2026-03-30 2.3.16
|
|
144
|
+
- assume all XS have interlock feedback
|
|
145
|
+
- 2026-03-13 2.3.15
|
|
146
|
+
- capture changes to vertical ROI in IDSDevice's EEPROM
|
|
147
|
+
- 2026-03-09 2.3.14
|
|
148
|
+
- save IDS detector in EEPROM
|
|
149
|
+
- 2026-03-02 2.3.13
|
|
150
|
+
- add experimental FPGA reset for InGaAs
|
|
151
|
+
- add Reading.image_format
|
|
3
152
|
- 2026-02-17 2.3.12
|
|
4
153
|
- added laser_password, startup_scans_to_average, laser_attenuator
|
|
5
154
|
- added [un]pack_field
|
|
@@ -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}"
|
|
@@ -10,7 +10,7 @@ from ctypes import *
|
|
|
10
10
|
|
|
11
11
|
from .SpectrometerSettings import SpectrometerSettings
|
|
12
12
|
from .SpectrometerResponse import SpectrometerResponse
|
|
13
|
-
from .InterfaceDevice import InterfaceDevice
|
|
13
|
+
from .InterfaceDevice import InterfaceDevice, InterfaceDeviceClassUnavailable
|
|
14
14
|
from .StatusMessage import StatusMessage
|
|
15
15
|
from .DeviceID import DeviceID
|
|
16
16
|
from .Reading import Reading
|
|
@@ -24,54 +24,24 @@ 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()
|
|
52
30
|
SHUTTER_SPEED_MS = 50 #!< allow time for mechanical shutter to stabilize
|
|
53
31
|
TEMPERATURE_CACHE_SEC = 1.0
|
|
54
32
|
|
|
33
|
+
DRIVER_NOT_INSTALLED = False
|
|
34
|
+
|
|
55
35
|
def __init__(self, device_id, message_queue=None, alert_queue=None):
|
|
56
|
-
|
|
57
|
-
super().__init__()
|
|
58
|
-
if type(device_id) is str:
|
|
59
|
-
device_id = DeviceID(label=device_id)
|
|
36
|
+
super().__init__(device_id=device_id, message_queue=message_queue, alert_queue=alert_queue)
|
|
60
37
|
|
|
61
|
-
self.
|
|
62
|
-
|
|
63
|
-
self.alert_queue = alert_queue
|
|
38
|
+
if self.DRIVER_NOT_INSTALLED:
|
|
39
|
+
raise InterfaceDeviceClassUnavailable("Andor driver not installed")
|
|
64
40
|
|
|
65
41
|
self.load_error_codes()
|
|
66
42
|
|
|
67
43
|
self.connected = False
|
|
68
44
|
|
|
69
|
-
# Receives ENLIGHTEN's 'change settings' commands in the spectrometer
|
|
70
|
-
# process.
|
|
71
|
-
self.command_queue = []
|
|
72
|
-
|
|
73
|
-
self.immediate_mode = False
|
|
74
|
-
|
|
75
45
|
# An AndorDevice has-a SpectrometerSettings, which has-a EEPROM, which
|
|
76
46
|
# has-a MultiWavelengthCalibration
|
|
77
47
|
self.settings = SpectrometerSettings(self.device_id)
|
|
@@ -119,8 +89,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
119
89
|
break
|
|
120
90
|
|
|
121
91
|
if self.driver is None:
|
|
122
|
-
|
|
123
|
-
|
|
92
|
+
# only need to display / log this once per session
|
|
93
|
+
self.DRIVER_NOT_INSTALLED = True
|
|
94
|
+
raise InterfaceDeviceClassUnavailable(f"could not find {filename} in search path: {dll_paths}")
|
|
124
95
|
|
|
125
96
|
# "serial_number", "model" etc are ambiguous in an Andor configuration
|
|
126
97
|
# file -- do they refer to the camera (Andor), or the spectrometer
|
|
@@ -135,8 +106,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
135
106
|
# (all but has_cooling can be overridden via config file)
|
|
136
107
|
|
|
137
108
|
# Andor API doesn't(?) seem to have access to detector info.
|
|
138
|
-
# Note that we use non-iDus cameras, including the Newton.
|
|
109
|
+
# Note that we use non-iDus cameras, including the Newton and iVac.
|
|
139
110
|
self.settings.eeprom.detector = "iDus"
|
|
111
|
+
self.settings.eeprom.model = "WP-XL"
|
|
140
112
|
|
|
141
113
|
self.settings.eeprom.has_cooling = True
|
|
142
114
|
self.settings.eeprom.startup_integration_time_ms = 10
|
|
@@ -171,40 +143,39 @@ class AndorDevice(InterfaceDevice):
|
|
|
171
143
|
except:
|
|
172
144
|
log.error(f"failed to enqueue StatusMessage {msg}", exc_info=1)
|
|
173
145
|
|
|
174
|
-
def not_implemented(self):
|
|
146
|
+
def not_implemented(self, arg):
|
|
175
147
|
pass
|
|
176
148
|
|
|
177
149
|
def _init_process_funcs(self):
|
|
178
150
|
process_f = {}
|
|
179
151
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
191
176
|
process_f["vertical_binning"] = self.set_vertical_binning
|
|
192
177
|
process_f["take_one_request"] = self.set_take_one_request
|
|
193
178
|
|
|
194
|
-
process_f["reset_scan_averaging"] = self.not_implemented
|
|
195
|
-
process_f["heartbeat"] = lambda x: None
|
|
196
|
-
|
|
197
|
-
##################################################################
|
|
198
|
-
# What follows is the old init-lambdas that are squashed into process_f
|
|
199
|
-
# Long term, the upstream requests should be changed to match the new format
|
|
200
|
-
# This is an easy fix for the time being to make things behave
|
|
201
|
-
##################################################################
|
|
202
|
-
process_f["integration_time_ms"] = lambda x: self.set_integration_time_ms(x)
|
|
203
|
-
process_f["fan_enable"] = lambda x: self.set_fan_enable(bool(x))
|
|
204
|
-
process_f["shutter_enable"] = lambda x: self.set_shutter_enable(bool(x))
|
|
205
|
-
process_f["detector_tec_enable"] = lambda x: self.toggle_tec(bool(x))
|
|
206
|
-
process_f["detector_tec_setpoint_degC"] = lambda x: self.set_tec_setpoint(int(round(x)))
|
|
207
|
-
|
|
208
179
|
return process_f
|
|
209
180
|
|
|
210
181
|
def high_gain_mode_enable(self, enabled):
|
|
@@ -219,9 +190,13 @@ class AndorDevice(InterfaceDevice):
|
|
|
219
190
|
log.debug(f"for {enabled} setting gain to {self.gain_options[0]}")
|
|
220
191
|
return
|
|
221
192
|
|
|
222
|
-
def set_fan_enable(self,
|
|
223
|
-
|
|
224
|
-
|
|
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)
|
|
225
200
|
|
|
226
201
|
def _get_default_data_dir(self):
|
|
227
202
|
if os.name == "nt":
|
|
@@ -263,6 +238,12 @@ class AndorDevice(InterfaceDevice):
|
|
|
263
238
|
if not spectrum.any():
|
|
264
239
|
self._queue_message("marquee_error", "Andor camera is saturated")
|
|
265
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
|
+
|
|
266
247
|
# log.debug(f"_get_spectrum_raw: returning spectrum {spectrum}")
|
|
267
248
|
return spectrum
|
|
268
249
|
|
|
@@ -381,13 +362,29 @@ class AndorDevice(InterfaceDevice):
|
|
|
381
362
|
return self.temperature_cache_value
|
|
382
363
|
|
|
383
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
|
|
384
380
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 2), "SetShutterEx(2)")
|
|
385
|
-
self.settings.state.
|
|
381
|
+
self.settings.state.shutter_open = False
|
|
386
382
|
return SpectrometerResponse(True)
|
|
387
383
|
|
|
388
384
|
def _open_ex_shutter(self):
|
|
385
|
+
# set TTL HIGH="open", internalMode PermanentlyOpen, externalMode PermanentlyOpen
|
|
389
386
|
self.check_result(self.driver.SetShutterEx(1, 1, self.SHUTTER_SPEED_MS, self.SHUTTER_SPEED_MS, 1), "SetShutterEx(1)")
|
|
390
|
-
self.settings.state.
|
|
387
|
+
self.settings.state.shutter_open = True
|
|
391
388
|
return SpectrometerResponse(True)
|
|
392
389
|
|
|
393
390
|
###############################################################
|
|
@@ -453,8 +450,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
453
450
|
self.init_detector_speed() # step 12+13
|
|
454
451
|
|
|
455
452
|
# step 14
|
|
453
|
+
# set internal shutter to PermanentlyOpen, external shutter to Automatic
|
|
456
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)")
|
|
457
|
-
self.settings.state.
|
|
455
|
+
self.settings.state.shutter_open = True
|
|
458
456
|
|
|
459
457
|
# step 15
|
|
460
458
|
self.set_integration_time_ms(self.settings.eeprom.startup_integration_time_ms)
|
|
@@ -552,8 +550,9 @@ class AndorDevice(InterfaceDevice):
|
|
|
552
550
|
"""
|
|
553
551
|
Loads configuration from file `self.config_file` and populates `self.settings.eeprom` with members.
|
|
554
552
|
"""
|
|
555
|
-
|
|
556
|
-
|
|
553
|
+
with open(self.config_file) as f:
|
|
554
|
+
self.config_values = json.load(f)
|
|
555
|
+
|
|
557
556
|
log.debug(f"loaded {self.config_file}: {self.config_values}")
|
|
558
557
|
|
|
559
558
|
# handle wp_ prefixes
|
|
@@ -562,6 +561,7 @@ class AndorDevice(InterfaceDevice):
|
|
|
562
561
|
self.settings.eeprom.multi_wavelength_calibration.set(v, self.config_values[k])
|
|
563
562
|
|
|
564
563
|
# same spelling
|
|
564
|
+
# consider just using self.settings.eeprom.fields.keys()
|
|
565
565
|
for k in [ 'model',
|
|
566
566
|
'stubbed',
|
|
567
567
|
'detector',
|
|
@@ -589,17 +589,20 @@ class AndorDevice(InterfaceDevice):
|
|
|
589
589
|
if 'startup_temp_degC' in self.config_values:
|
|
590
590
|
self.set_tec_setpoint(self.settings.eeprom.startup_temp_degC)
|
|
591
591
|
|
|
592
|
+
# pick up Pixel Corrections like EtalonCorrection, InGaAsCorrection etc
|
|
593
|
+
self.settings.augment_from_json_data(data)
|
|
594
|
+
|
|
592
595
|
def acquire_data(self):
|
|
593
596
|
# handle TakeOneRequest.take_dark
|
|
594
597
|
dark_reading = None
|
|
595
598
|
tor = self.take_one_request
|
|
596
599
|
log.debug(f"acquire_data: tor {tor}")
|
|
597
600
|
if tor and tor.take_dark:
|
|
598
|
-
self.
|
|
601
|
+
self.set_shutter_open(False)
|
|
599
602
|
dark_reading = self._take_one_averaged_reading()
|
|
600
|
-
if
|
|
603
|
+
if dark_reading is None:
|
|
601
604
|
return SpectrometerResponse(False, error_msg="failed to collect dark")
|
|
602
|
-
self.
|
|
605
|
+
self.set_shutter_open(True)
|
|
603
606
|
|
|
604
607
|
# get spectrum (potentially averaged)
|
|
605
608
|
reading = self._take_one_averaged_reading()
|
|
@@ -639,8 +642,8 @@ class AndorDevice(InterfaceDevice):
|
|
|
639
642
|
log.debug(f"acquire_data: reading {reading}")
|
|
640
643
|
return SpectrometerResponse(data=reading)
|
|
641
644
|
|
|
642
|
-
def
|
|
643
|
-
if
|
|
645
|
+
def set_shutter_open(self, flag):
|
|
646
|
+
if flag:
|
|
644
647
|
return self._open_ex_shutter()
|
|
645
648
|
else:
|
|
646
649
|
return self._close_ex_shutter()
|
|
@@ -688,16 +691,17 @@ class AndorDevice(InterfaceDevice):
|
|
|
688
691
|
|
|
689
692
|
return SpectrometerResponse(True)
|
|
690
693
|
|
|
691
|
-
def
|
|
692
|
-
|
|
693
|
-
self.tec_enabled =
|
|
694
|
-
if
|
|
694
|
+
def set_detector_tec_enable(self, flag):
|
|
695
|
+
flag = True if flag else False
|
|
696
|
+
self.tec_enabled = flag
|
|
697
|
+
if flag:
|
|
695
698
|
self.check_result(self.driver.CoolerON(), "CoolerON")
|
|
696
699
|
else:
|
|
697
700
|
self.check_result(self.driver.CoolerOFF(), "CoolerOFF")
|
|
698
701
|
return SpectrometerResponse(True)
|
|
699
702
|
|
|
700
703
|
def set_tec_setpoint(self, set_temp):
|
|
704
|
+
set_temp = int(round(set_temp, 0))
|
|
701
705
|
if set_temp < self.settings.eeprom.min_temp_degC or set_temp > self.settings.eeprom.max_temp_degC:
|
|
702
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)")
|
|
703
707
|
return
|
|
@@ -768,6 +772,14 @@ class AndorDevice(InterfaceDevice):
|
|
|
768
772
|
log.debug(f"set AD channel {ADnumber} with horizontal speed {HSnumber} ({STemp})")
|
|
769
773
|
return SpectrometerResponse(True)
|
|
770
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
|
+
|
|
771
783
|
def set_scans_to_average(self, value):
|
|
772
784
|
value = int(value)
|
|
773
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
|