wasatch 2.3.2__tar.gz → 2.3.6__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.
Files changed (93) hide show
  1. {wasatch-2.3.2 → wasatch-2.3.6}/CHANGELOG.md +10 -0
  2. {wasatch-2.3.2 → wasatch-2.3.6}/Makefile +4 -0
  3. {wasatch-2.3.2 → wasatch-2.3.6}/PKG-INFO +3 -2
  4. {wasatch-2.3.2 → wasatch-2.3.6}/pyproject.toml +1 -1
  5. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/AndorDevice.py +1 -1
  6. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/EEPROM.py +3 -0
  7. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/FeatureIdentificationDevice.py +94 -22
  8. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/SpectrometerSettings.py +2 -2
  9. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/SpectrometerState.py +3 -0
  10. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/WasatchDeviceWrapper.py +1 -1
  11. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/__init__.py +1 -1
  12. {wasatch-2.3.2 → wasatch-2.3.6}/.example_pypirc +0 -0
  13. {wasatch-2.3.2 → wasatch-2.3.6}/.flake8 +0 -0
  14. {wasatch-2.3.2 → wasatch-2.3.6}/.gitignore +0 -0
  15. {wasatch-2.3.2 → wasatch-2.3.6}/.pylintrc +0 -0
  16. {wasatch-2.3.2 → wasatch-2.3.6}/Doxyfile +0 -0
  17. {wasatch-2.3.2 → wasatch-2.3.6}/LICENSE +0 -0
  18. {wasatch-2.3.2 → wasatch-2.3.6}/MANIFEST.in +0 -0
  19. {wasatch-2.3.2 → wasatch-2.3.6}/README.md +0 -0
  20. {wasatch-2.3.2 → wasatch-2.3.6}/README_PYPI.md +0 -0
  21. {wasatch-2.3.2 → wasatch-2.3.6}/README_RPI.md +0 -0
  22. {wasatch-2.3.2 → wasatch-2.3.6}/README_SETTINGS.md +0 -0
  23. {wasatch-2.3.2 → wasatch-2.3.6}/README_VIRTUAL.md +0 -0
  24. {wasatch-2.3.2 → wasatch-2.3.6}/WasatchShell/README.md +0 -0
  25. {wasatch-2.3.2 → wasatch-2.3.6}/WasatchShell/laser-test.py +0 -0
  26. {wasatch-2.3.2 → wasatch-2.3.6}/WasatchShell/load-test.py +0 -0
  27. {wasatch-2.3.2 → wasatch-2.3.6}/WasatchShell/one-shot.py +0 -0
  28. {wasatch-2.3.2 → wasatch-2.3.6}/WasatchShell/wasatch-shell.py +0 -0
  29. {wasatch-2.3.2 → wasatch-2.3.6}/demo-auto-raman.py +0 -0
  30. {wasatch-2.3.2 → wasatch-2.3.6}/demo-simple.py +0 -0
  31. {wasatch-2.3.2 → wasatch-2.3.6}/demo-virtual.py +0 -0
  32. {wasatch-2.3.2 → wasatch-2.3.6}/demo-workflow.py +0 -0
  33. {wasatch-2.3.2 → wasatch-2.3.6}/demo.py +0 -0
  34. {wasatch-2.3.2 → wasatch-2.3.6}/environments/conda-linux.yml +0 -0
  35. {wasatch-2.3.2 → wasatch-2.3.6}/environments/conda-macos.yml +0 -0
  36. {wasatch-2.3.2 → wasatch-2.3.6}/environments/conda-rpi.yml +0 -0
  37. {wasatch-2.3.2 → wasatch-2.3.6}/environments/conda-win10.yml +0 -0
  38. {wasatch-2.3.2 → wasatch-2.3.6}/screenshots/multiplatform.png +0 -0
  39. {wasatch-2.3.2 → wasatch-2.3.6}/scripts/dark-vs-integ.py +0 -0
  40. {wasatch-2.3.2 → wasatch-2.3.6}/scripts/deploy +0 -0
  41. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/SiG_785/eeprom/EEPROM-EM-9c65d19f4c.json +0 -0
  42. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/SiG_785/readings/enlighten-20210329-094722-799520-WP-00686.csv +0 -0
  43. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/SiG_785/readings/enlighten-20210329-094755-288958-WP-00686.csv +0 -0
  44. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/WP-00887/eeprom/WP-00887-mock.json +0 -0
  45. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/WP-00904/eeprom/WP-00904-mock.json +0 -0
  46. {wasatch-2.3.2 → wasatch-2.3.6}/testSpectrometers/WP-00913/eeprom/WP-00913-mock.json +0 -0
  47. {wasatch-2.3.2 → wasatch-2.3.6}/tests/existing_implementations.py +0 -0
  48. {wasatch-2.3.2 → wasatch-2.3.6}/tests/noise-by-setpoint.py +0 -0
  49. {wasatch-2.3.2 → wasatch-2.3.6}/tests/queues.py +0 -0
  50. {wasatch-2.3.2 → wasatch-2.3.6}/tests/unit.py +0 -0
  51. {wasatch-2.3.2 → wasatch-2.3.6}/udev/10-wasatch.rules +0 -0
  52. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/AbstractUSBDevice.py +0 -0
  53. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/AreaScanImage.py +0 -0
  54. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/AutoRaman.py +0 -0
  55. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/AutoRamanRequest.py +0 -0
  56. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/BLEDevice.py +0 -0
  57. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/BalanceAcquisition.py +0 -0
  58. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/CSVLoader.py +0 -0
  59. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/CommandSettings.py +0 -0
  60. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/ControlObject.py +0 -0
  61. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DetectorROI.py +0 -0
  62. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DetectorRegions.py +0 -0
  63. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DeviceFinderBLE.py +0 -0
  64. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DeviceFinderTCP.py +0 -0
  65. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DeviceFinderUSB.py +0 -0
  66. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/DeviceID.py +0 -0
  67. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/EEPROM_multical.sav +0 -0
  68. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/FPGAOptions.py +0 -0
  69. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/FirmwareRequirements.py +0 -0
  70. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/HardwareInfo.py +0 -0
  71. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/IDSCamera.py +0 -0
  72. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/IDSDevice.py +0 -0
  73. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/IMX385.py +0 -0
  74. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/InterfaceDevice.py +0 -0
  75. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/MockUSBDevice.py +0 -0
  76. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/OceanDevice.py +0 -0
  77. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/PollStatus.py +0 -0
  78. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/ProcessedReading.py +0 -0
  79. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/ROI.py +0 -0
  80. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/Reading.py +0 -0
  81. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/RealUSBDevice.py +0 -0
  82. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/SPIDevice.py +0 -0
  83. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/SpectrometerRequest.py +0 -0
  84. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/SpectrometerResponse.py +0 -0
  85. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/StatusMessage.py +0 -0
  86. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/TCPDevice.py +0 -0
  87. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/TakeOneRequest.py +0 -0
  88. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/WasatchBus.py +0 -0
  89. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/WasatchDevice.py +0 -0
  90. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/WrapperWorker.py +0 -0
  91. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/applog.py +0 -0
  92. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/simulation_protocol.py +0 -0
  93. {wasatch-2.3.2 → wasatch-2.3.6}/wasatch/utils.py +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ - 2025-11-06 2.3.6
4
+ - resolve hotplug race condition when laser power set in percent
5
+ - cache certain device getters to avoid I2C overruns
6
+ - stop trying to read laser temperature on FX2, it's never worked right
7
+ - 2025-10-22 2.3.5
8
+ - don't loop on spectral read errors on XS
9
+ - 2025-10-22 2.3.4
10
+ - ignore heartbeat on Andor
11
+ - 2025-06-30 2.3.3
12
+ - Hamamatsu 2048px area scan
3
13
  - 2025-06-18 2.3.2
4
14
  - XS area scan
5
15
  - fixed off-by-one stop line
@@ -28,7 +28,11 @@ pip-install-local:
28
28
  pip install $$PWD
29
29
 
30
30
  publish-test:
31
+ @echo REMINDER to keep .toml in sync with __init__.py!
32
+ @echo
31
33
  flit publish --repository testpypi
32
34
 
33
35
  publish:
36
+ @echo REMINDER to keep .toml in sync with __init__.py!
37
+ @echo
34
38
  flit publish
@@ -1,9 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: wasatch
3
- Version: 2.3.2
3
+ Version: 2.3.6
4
4
  Summary: Application-level driver for Wasatch Photonics spectrometers
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
7
+ License-File: LICENSE
7
8
  Requires-Dist: numpy
8
9
  Requires-Dist: six
9
10
  Requires-Dist: psutil
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wasatch"
3
- version = "2.3.2"
3
+ version = "2.3.6"
4
4
  description= "Application-level driver for Wasatch Photonics spectrometers"
5
5
  requires-python = ">=3.9"
6
6
  readme = "README.md"
@@ -188,7 +188,7 @@ class AndorDevice(InterfaceDevice):
188
188
  process_f["take_one_request"] = self.set_take_one_request
189
189
 
190
190
  process_f["reset_scan_averaging"] = self.not_implemented
191
- process_f["heartbeat"] = self.not_implemented
191
+ process_f["heartbeat"] = lambda x: None
192
192
 
193
193
  ##################################################################
194
194
  # What follows is the old init-lambdas that are squashed into process_f
@@ -1446,5 +1446,8 @@ class EEPROMField:
1446
1446
  self.offset = pos[1]
1447
1447
  self.length = pos[2]
1448
1448
 
1449
+ def __repr__(self):
1450
+ return str(self.__dict__)
1451
+
1449
1452
  def toJSON(self):
1450
1453
  return str(self.__dict__)
@@ -308,11 +308,23 @@ class FeatureIdentificationDevice(InterfaceDevice):
308
308
  return result
309
309
 
310
310
  # ######################################################################
311
- # Automatically disable laser at connection
311
+ # Laser defaults
312
312
  # ######################################################################
313
313
 
314
- # if self.settings.eeprom.has_laser:
315
- # self.set_laser_enable(False)
314
+ if self.settings.eeprom.has_laser:
315
+ log.debug("post_connect: setting laser defaults")
316
+ # MZ: why did we stop doing this?
317
+ # self.set_laser_enable(False)
318
+
319
+ self.settings.state.laser_power_mW = self.settings.eeprom.max_laser_power_mW
320
+ self.settings.state.laser_power_perc = 100
321
+ self.settings.state.use_mW = self.settings.eeprom.has_laser_power_calibration() and self.settings.is_mml()
322
+
323
+ log.debug(f" laser_power_mW {self.settings.state.laser_power_mW}")
324
+ log.debug(f" laser_power_perc {self.settings.state.laser_power_perc}")
325
+ log.debug(f" use_mW {self.settings.state.use_mW}")
326
+
327
+ log.debug("post_connect: done setting laser defaults")
316
328
 
317
329
  # ######################################################################
318
330
  # TEC Setpoint
@@ -816,20 +828,21 @@ class FeatureIdentificationDevice(InterfaceDevice):
816
828
 
817
829
  try:
818
830
  self._wait_for_usb_available()
831
+ log.debug("%s_get_code: request 0x%02x value 0x%04x index 0x%04x length %d", prefix, bRequest, wValue, wIndex, wLength)
819
832
  result = self.device_type.ctrl_transfer(self.device,
820
833
  0xc0, # DEVICE_TO_HOST
821
834
  bRequest,
822
835
  wValue,
823
836
  wIndex,
824
- wLength)
837
+ wLength) # add TIMEOUT_MS?
825
838
  except Exception as exc:
826
839
  log.critical(f"Hardware Failure FID Get Code Problem with ctrl transfer (bRequest 0x{bRequest:02x}, wValue 0x{wValue:04x}, wIndex 0x{wIndex:04x}, label {label})", exc_info=1)
827
840
  self._schedule_disconnect(exc)
828
841
  return SpectrometerResponse(poison_pill=True)
829
842
 
830
843
  result_hex = " ".join([f"{v:02x}" for v in result])
831
- log.debug("%s_get_code: request 0x%02x value 0x%04x index 0x%04x = [%s]",
832
- prefix, bRequest, wValue, wIndex, result_hex)
844
+ log.debug("%s_get_code: request 0x%02x value 0x%04x index 0x%04x length %d = [%s]",
845
+ prefix, bRequest, wValue, wIndex, wLength, result_hex)
833
846
 
834
847
  if result is None:
835
848
  log.critical("_get_code[%s, %s]: received null", label, self.device_id)
@@ -1135,10 +1148,10 @@ class FeatureIdentificationDevice(InterfaceDevice):
1135
1148
  self.settings.state.area_scan_line_step = n
1136
1149
  return self.i2c_write(0x17, [n], label="SET_AREA_SCAN_LINE_STEP")
1137
1150
 
1138
- # MZ: I don't remember what boards / firmware supports this?
1151
+ # MZ: I don't remember what boards / firmware supports this? How does this differ from interval?
1139
1152
  def set_area_scan_line_count(self, n):
1140
1153
  self.debug(f"setting line count to {n}")
1141
- self.dev.ctrl_transfer(H2D, 0xa6, n, 0, Z, TIMEOUT)
1154
+ self.dev.ctrl_transfer(H2D, 0xa6, n, 0, Z)
1142
1155
  return self._send_code(0xa6, n, label="SET_AREA_SCAN_LINE_COUNT")
1143
1156
 
1144
1157
  # MZ: I don't remember what boards / firmware supports this?
@@ -1401,7 +1414,12 @@ class FeatureIdentificationDevice(InterfaceDevice):
1401
1414
  else:
1402
1415
  errors += 1
1403
1416
  log.error(f"Encountered error on read of {exc}", exc_info=1)
1404
- if errors < 3:
1417
+
1418
+ # Don't loop on errors on XS, so we can experimentally
1419
+ # use an XS board as a detector-less laser/TEC driver
1420
+ # board. (A better solution would be to add FeatureMask.
1421
+ # has_detector or similar, this works for now.)
1422
+ if errors < 3 and not self.settings.is_xs():
1405
1423
  log.error(f"ignoring error number {errors}")
1406
1424
  else:
1407
1425
  response.error_msg = "Encountered error on read"
@@ -1608,12 +1626,17 @@ class FeatureIdentificationDevice(InterfaceDevice):
1608
1626
  self.remaining_throwaways += 1
1609
1627
 
1610
1628
  def set_onboard_scans_to_average(self, n):
1629
+ """
1630
+ This is provided to let ENLIGHTEN *turn off* onboard averaging settings
1631
+ which might have been generated, and (deliberately) left in place after
1632
+ an Auto-Raman measurement.
1633
+ """
1611
1634
  if not self.settings.is_xs():
1612
1635
  return SpectrometerResponse(False)
1613
1636
 
1614
1637
  retval = self._send_code(0xff, 0x62, n, label="SET_ONBOARD_SCANS_TO_AVERAGE")
1615
1638
  self.settings.state.scans_to_average = n
1616
- self.settings.state.onboard_averaging = True
1639
+ self.settings.state.onboard_averaging = n > 1
1617
1640
  return retval
1618
1641
 
1619
1642
  def get_scans_to_average(self):
@@ -1673,6 +1696,7 @@ class FeatureIdentificationDevice(InterfaceDevice):
1673
1696
  return result
1674
1697
 
1675
1698
  def i2c_write(self, address, buf, label=""):
1699
+ # return log.debug(f"i2c_write: declining to send cmd 0x90, wValue {address}, wIndex {len(buf)}, data_or_wLength {buf}, label {label}")
1676
1700
  return self._send_code(0x90, wValue=address, wIndex=len(buf), data_or_wLength=buf, label=label)
1677
1701
 
1678
1702
  # ##########################################################################
@@ -1849,8 +1873,16 @@ class FeatureIdentificationDevice(InterfaceDevice):
1849
1873
  marker = 0xffff
1850
1874
  clamp = 0xfffe
1851
1875
  lines = 69
1852
- line_len = self.settings.pixels() * 2
1853
- timeout_ms = self.generate_timeout_ms()
1876
+ line_index = -1
1877
+ pixels = self.settings.pixels()
1878
+ line_len = pixels * 2
1879
+ timeout_ms = self.generate_timeout_ms() + 10
1880
+
1881
+ endpoints = [0x82]
1882
+ block_len_bytes = line_len
1883
+ if pixels == 2048 and not self.settings.is_arm():
1884
+ endpoints = [0x82, 0x86]
1885
+ block_len_bytes = 2048 # 1024 pixels apiece from two endpoints
1854
1886
 
1855
1887
  # request a frame
1856
1888
  self._send_code(0xad, label="ACQUIRE_SPECTRUM")
@@ -1866,10 +1898,14 @@ class FeatureIdentificationDevice(InterfaceDevice):
1866
1898
  try:
1867
1899
  while len(data) < line_len:
1868
1900
  bytes_remaining = line_len - len(data)
1869
- # @todo expand this to 0x86 for S16011-1101
1870
- latest_data = self.device_type.read(self.device, 0x82, bytes_remaining, timeout=timeout_ms)
1901
+ bytes_to_read = min(bytes_remaining, block_len_bytes)
1902
+ ep_index = 1 if len(endpoints) > 1 and len(data) >= 1024 else 0
1903
+
1904
+ # log.debug(f"get_area_scan_hamamatsu: attempting to read {bytes_to_read} from 0x{endpoints[ep_index]:02x}")
1905
+ latest_data = self.device_type.read(self.device, endpoints[ep_index], bytes_to_read, timeout=timeout_ms)
1871
1906
  data.extend(latest_data)
1872
1907
  except:
1908
+ # this still happens periodically
1873
1909
  log.error(f"get_area_scan_hamamatsu {prefix}: error reading line {line}", exc_info=1)
1874
1910
  break
1875
1911
 
@@ -1903,8 +1939,11 @@ class FeatureIdentificationDevice(InterfaceDevice):
1903
1939
  spectrum[1] = spectrum[2] # stomp
1904
1940
 
1905
1941
  # store line in image
1906
- self.area_scan_frame[line_index] = spectrum
1907
- # self.debug(f"{prefix}: stored line {line_index}: {spectrum[:5]}")
1942
+ if 0 <= line_index < len(self.area_scan_frame):
1943
+ log.debug(f"get_area_scan_hamamatsu {prefix}: storing line {line_index}: {spectrum[:5]}")
1944
+ self.area_scan_frame[line_index] = spectrum
1945
+ else:
1946
+ log.warn(f"get_area_scan_hamamatsu {prefix}: dropping line {line_index}: {spectrum[:5]}")
1908
1947
 
1909
1948
  line_count += 1
1910
1949
 
@@ -1915,7 +1954,9 @@ class FeatureIdentificationDevice(InterfaceDevice):
1915
1954
  # store full-frame image data for Area Scan
1916
1955
  data = np.array(self.area_scan_frame, dtype=np.float32, copy=True)
1917
1956
  data -= data.min() # remove baseline
1918
- asi = AreaScanImage(data=data, width=len(data[0]), height=len(data))
1957
+ if self.settings.eeprom.invert_x_axis:
1958
+ data = np.rot90(data, k=2)
1959
+ asi = AreaScanImage(data=data, width=len(data[0]), height=len(data), line_index=line_index)
1919
1960
 
1920
1961
  # vertically bin within vertical ROI for live graph
1921
1962
  start = self.settings.eeprom.roi_vertical_region_1_start
@@ -1983,7 +2024,7 @@ class FeatureIdentificationDevice(InterfaceDevice):
1983
2024
  if self.settings.state.selected_adc is None or self.settings.state.selected_adc != 1:
1984
2025
  self.select_adc(1)
1985
2026
 
1986
- result = self._get_code(0xd5, wLength=2, label="GET_ADC", lsb_len=2)
2027
+ result = self._get_code(0xd5, wLength=2, label="GET_ADC (secondary)", lsb_len=2)
1987
2028
  log.debug("secondary_adc_raw: 0x%04x", result)
1988
2029
  return result
1989
2030
 
@@ -2178,7 +2219,18 @@ class FeatureIdentificationDevice(InterfaceDevice):
2178
2219
 
2179
2220
  ## @note big-endian, reverse of get_laser_temperature_raw
2180
2221
  def get_detector_temperature_raw(self):
2181
- return self._get_code(0xd7, label="GET_CCD_TEMP", msb_len=2)
2222
+ # - don't poll detector temperature faster than 10Hz...analyzing performance on FX2
2223
+ last = self.settings.state.detector_temperature_raw_last_refreshed
2224
+ now = datetime.datetime.now()
2225
+ if last is not None and (now - last).total_seconds() < 0.1:
2226
+ raw = self.settings.state.detector_temperature_raw
2227
+ log.debug("get_detector_temperature_raw: using cached {raw}")
2228
+ return raw
2229
+ raw = self._get_code(0xd7, wLength=2, label="GET_CCD_TEMP", msb_len=2)
2230
+ self.settings.state.detector_temperature_raw = raw
2231
+ self.settings.state.detector_temperature_raw_last_refreshed = now
2232
+ log.debug("get_detector_temperature_raw: storing {raw}")
2233
+ return raw
2182
2234
 
2183
2235
  def get_detector_temperature_degC(self, raw: float = None):
2184
2236
  if raw is None:
@@ -2339,12 +2391,16 @@ class FeatureIdentificationDevice(InterfaceDevice):
2339
2391
 
2340
2392
  def get_laser_temperature_raw(self):
2341
2393
  """ @note little-endian, reverse of get_detector_temperature_raw """
2394
+ # this is currently unreliable on most (all?) Hamamatsu units, so why bother
2395
+ if not self.settings.is_xs():
2396
+ return SpectrometerResponse(0)
2397
+
2342
2398
  # flip to primary ADC if needed
2343
2399
  if self.settings.state.selected_adc is None or self.settings.state.selected_adc != 0:
2344
2400
  # MZ: unclear if this is needed on SiG
2345
2401
  self.select_adc(0)
2346
2402
 
2347
- result = self._get_code(0xd5, wLength=2, label="GET_ADC", lsb_len=2)
2403
+ result = self._get_code(0xd5, wLength=2, label="GET_ADC (laser)", lsb_len=2)
2348
2404
  if not result.data:
2349
2405
  log.debug("Unable to read laser temperature")
2350
2406
  return SpectrometerResponse(0)
@@ -2381,11 +2437,21 @@ class FeatureIdentificationDevice(InterfaceDevice):
2381
2437
  # MZ: get_laser_enabled not currently robust on SiG :-(
2382
2438
  return SpectrometerResponse(data=self.settings.state.laser_enabled)
2383
2439
 
2384
- res = self._get_code(0xe2, label="GET_LASER_ENABLED", msb_len=1)
2440
+ # - don't poll laser faster than 100Hz...analyzing performance on FX2
2441
+ # - seems to be double-polled due to is_laser_firing()
2442
+ last = self.settings.state.laser_enabled_last_refreshed
2443
+ now = datetime.datetime.now()
2444
+ if last is not None and (now - last).total_seconds() < 0.01:
2445
+ flag = self.settings.state.laser_enabled
2446
+ log.debug(f"get_laser_enabled: using cached {flag}")
2447
+ return SpectrometerResponse(data=flag)
2448
+
2449
+ res = self._get_code(0xe2, wLength=1, label="GET_LASER_ENABLED", msb_len=1)
2385
2450
  flag = 0 != res.data
2386
2451
 
2387
- log.debug(f"get_laser_enabled: {flag} (storing to {self.settings.state})")
2452
+ log.debug(f"get_laser_enabled: {flag} (storing to self.settings.state)")
2388
2453
  self.settings.state.laser_enabled = flag
2454
+ self.settings.state.laser_enabled_last_refreshed = now
2389
2455
  return SpectrometerResponse(data=flag)
2390
2456
 
2391
2457
  def set_laser_enable(self, flag: bool):
@@ -2912,6 +2978,8 @@ class FeatureIdentificationDevice(InterfaceDevice):
2912
2978
  self.set_vertical_roi(roi)
2913
2979
 
2914
2980
  def set_vertical_roi(self, roi):
2981
+ log.debug(f"set_vertical_roi: {roi}")
2982
+
2915
2983
  # check for legacy SiG-VIS since it didn't like vertical binning
2916
2984
  if self.settings.fpga_firmware_version == "000-008" and self.settings.microcontroller_firmware_version == "0.1.0.7":
2917
2985
  return SpectrometerResponse(data=False)
@@ -2928,6 +2996,7 @@ class FeatureIdentificationDevice(InterfaceDevice):
2928
2996
  else:
2929
2997
  log.error("set_vertical_roi: requires an ROI object or tuple of (start, stop) lines")
2930
2998
  return SpectrometerResponse(data=False, error_msg="invalid start and stop lines")
2999
+ log.debug(f"set_vertical_roi: start {start}, end {end}")
2931
3000
 
2932
3001
  if start < 0 or end < 0:
2933
3002
  log.error("set_vertical_roi: requires POSITIVE (start, stop) lines")
@@ -2947,6 +3016,9 @@ class FeatureIdentificationDevice(InterfaceDevice):
2947
3016
  # FX2 lacks USB opcodes for start/stop line, so use I2C Poke (clamp to 1 byte)
2948
3017
  start = int(max(0, min(255, start)))
2949
3018
  end = int(max(0, min(255, end )))
3019
+
3020
+ # start = max(1, start) # kludge
3021
+
2950
3022
  self.i2c_write(0x29, [start], label="I2C_POKE(SET_CCD_START_LINE)")
2951
3023
  self.i2c_write(0x2A, [end], label="I2C_POKE(SET_CCD_STOP_LINE)")
2952
3024
 
@@ -237,7 +237,7 @@ class SpectrometerSettings:
237
237
  # order in the current codebase."
238
238
 
239
239
  coeffs = self.eeprom.multi_wavelength_calibration.get("raman_intensity_coeffs")
240
- log.debug("update_raman_intensity_factors: coeffs {coeffs}")
240
+ log.debug(f"update_raman_intensity_factors: coeffs {coeffs}")
241
241
  if coeffs is None or coeffs == []:
242
242
  return
243
243
 
@@ -256,7 +256,7 @@ class SpectrometerSettings:
256
256
  except:
257
257
  log.error(f"exception generating Raman intensity factors (coeffs {coeffs})", exc_info=1)
258
258
  self.raman_intensity_factors = None
259
- log.debug("generated {len(self.raman_intensity_factors)} Raman intensity factors")
259
+ log.debug(f"generated {len(self.raman_intensity_factors)} Raman intensity factors")
260
260
 
261
261
  def set_linear_pixel_calibration(self, data):
262
262
  self.linear_pixel_calibration = None
@@ -29,12 +29,15 @@ class SpectrometerState:
29
29
  # TEC @todo rename detector_tec...
30
30
  self.tec_setpoint_degC = 15 # that's a very strange default...
31
31
  self.tec_enabled = False
32
+ self.detector_temperature_raw = None
33
+ self.detector_temperature_raw_last_refreshed = None
32
34
 
33
35
  # high gain mode (InGaAs only)
34
36
  self.high_gain_mode_enabled = False
35
37
 
36
38
  # laser
37
39
  self.laser_enabled = False
40
+ self.laser_enabled_last_refreshed = None
38
41
  self.laser_power_mW = None # if last set in mW, will contain setpoint in mW; else will be None
39
42
  self.laser_power_perc = 0 # should always contain a number [0, 100]
40
43
  self.use_mW = False # bool
@@ -233,7 +233,7 @@ class WasatchDeviceWrapper:
233
233
 
234
234
  self.wrapper_worker.daemon = True
235
235
 
236
- log.debug("deivce wrapper: starting WrapperWorker thread")
236
+ log.debug("device wrapper: starting WrapperWorker thread")
237
237
  self.wrapper_worker.start()
238
238
 
239
239
  # expect to read a single post-initialization SpectrometerSettings object off the queue
@@ -1,4 +1,4 @@
1
1
  """This package provides control of Wasatch Photonics spectrometers"""
2
2
 
3
- __version__ = "2.3.2" # used by flit and other pypi things
3
+ __version__ = "2.3.6" # used by flit and other pypi things
4
4
  version = __version__ # legacy alias
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes