Helios diode current: stop the status poll overwriting the setpoint

The current spin box could not hold a typed value: the 1 Hz status poll
read LDS and wrote it straight into the spin box, so the operator's number
was replaced by the laser's within a second — before Set could be pressed.
The spin box is now seeded once on connect and belongs to the operator
after that; the poll's reading goes to a read-back label beside the Set
button, so the value about to be sent and the value the laser holds are
separate readouts.

The write itself was also unverified. Section 6 of the operator's manual:
"Commands or set values can be discarded by the controller unintentionally.
It is recommended to query the set value after the command is entered to
confirm the actual value." set_current_ma() wrote LDS and returned True
regardless, so a discarded write looked exactly like a good one.
_write_verified() now writes, reads back, and retries up to three times;
set_current_ma() and set_frequency_hz() use it, and HeliosWorker reports a
refusal on the status line instead of echoing the requested value.

Also from the manual, recorded but not acted on: LDS accepts 0-7000 mA
(the driver's 2000 mA ceiling is this rig's, not the protocol's), LDF has
to be re-sent after LDG changes, and the power-monitor mnemonic is HMP —
which this laser does not implement, per the operator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Ales
2026-09-08 07:37:10 -05:00
parent aeac5fe4d6
commit c7eb33891c
5 changed files with 186 additions and 18 deletions
+32 -5
View File
@@ -399,8 +399,19 @@ class HeliosWorker(PollingQueueWorker):
def _do_set_current(self, current_ma: int):
if not self._laser:
return
self._laser.set_current_ma(current_ma)
self.current_updated.emit(current_ma)
if self._laser.set_current_ma(current_ma):
self.current_updated.emit(current_ma)
return
# The manual warns the controller drops set values unintentionally,
# so the driver verifies the write. Say when it did not take: this
# used to emit the requested value regardless, and the next poll
# then quietly restored the old one.
actual = self._laser.get_current_ma()
if actual is not None:
self.current_updated.emit(actual)
self.error_occurred.emit(
f"Diode current: controller kept {actual} mA, "
f"did not accept {current_ma} mA")
def _poll_once(self):
"""Read the full status set. Individual reads are allowed to fail
@@ -982,6 +993,7 @@ class HeliosWindow(QWidget):
self._worker = worker
# Polling is driven by the worker itself (self-rescheduling), so
# there is no timer here to outpace the device.
self._seed_current_spin = False
self._wire_signals()
self._update_controls(False)
self._set_emission_indicator(False)
@@ -1029,6 +1041,8 @@ class HeliosWindow(QWidget):
self._worker.queue_disconnect()
def _on_connected(self):
# Take the setpoint from the laser once, on connect.
self._seed_current_spin = True
self.helios_connect_toggle.setEnabled(True)
self.helios_connect_toggle.setText("Disconnect")
self.helios_status_label.setText("Connected")
@@ -1077,9 +1091,20 @@ class HeliosWindow(QWidget):
self._worker.queue_set_current(self.helios_current_spin.value())
def _on_current_updated(self, current_ma: int):
self.helios_current_spin.blockSignals(True)
self.helios_current_spin.setValue(current_ma)
self.helios_current_spin.blockSignals(False)
"""Show what the laser reports; leave the spin box to the operator.
The 1 Hz poll used to write its reading straight into the spin box,
so a number typed there was overwritten within a second — the
setpoint appeared to snap back to the controller's value before it
could be sent. The spin box is now seeded once per connection and
is the operator's alone after that; the label is the read-back.
"""
self.helios_current_readback_label.setText(f"laser: {current_ma} mA")
if self._seed_current_spin:
self._seed_current_spin = False
self.helios_current_spin.blockSignals(True)
self.helios_current_spin.setValue(current_ma)
self.helios_current_spin.blockSignals(False)
def _reset_temperatures(self):
self.helios_temp_diode_label.setText("--- °C")
@@ -1101,6 +1126,8 @@ class HeliosWindow(QWidget):
self.helios_enable_btn.setText("Enable Laser")
self.helios_enable_btn.blockSignals(False)
self._set_emission_indicator(False)
self._seed_current_spin = False
self.helios_current_readback_label.setText("laser: --- mA")
self._reset_temperatures()
self.helios_ler_label.setText("---")
self.helios_lce_label.setText("---")