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:
@@ -210,6 +210,38 @@ class HeliosLaser:
|
||||
logger.error(f"Failed to read the reply to '{command}': {e}")
|
||||
return True
|
||||
|
||||
# Section 6 of the operator's manual, under Syntax:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# (The command table repeats it: "Query the command to confirm it was
|
||||
# accepted.") A setter that only writes therefore cannot report whether
|
||||
# it worked, and the panel's next status poll reads back the old value —
|
||||
# which looks exactly like the GUI refusing the operator's number.
|
||||
SET_RETRIES = 3
|
||||
SET_SETTLE_S = 0.05 # let the controller store it before reading
|
||||
|
||||
def _write_verified(self, mnemonic: str, value: int) -> bool:
|
||||
"""Write `value` to `mnemonic`, and confirm the controller took it.
|
||||
|
||||
Returns False if the read-back never matches, leaving the controller
|
||||
holding whatever value it kept — the caller is expected to say so
|
||||
rather than let the discarded write pass for a successful one.
|
||||
"""
|
||||
for attempt in range(1, self.SET_RETRIES + 1):
|
||||
if not self._write_command(f"{mnemonic} {value}"):
|
||||
return False
|
||||
time.sleep(self.SET_SETTLE_S)
|
||||
readback = self._query_int(mnemonic)
|
||||
if readback == value:
|
||||
return True
|
||||
logger.warning(
|
||||
f"'{mnemonic} {value}' not accepted: controller reports "
|
||||
f"{readback} (attempt {attempt}/{self.SET_RETRIES})")
|
||||
return False
|
||||
|
||||
def _query_int(self, command: str) -> Optional[int]:
|
||||
"""Query a value that should parse as an int; None if absent/unparseable."""
|
||||
raw = self._query(command)
|
||||
@@ -235,20 +267,28 @@ class HeliosLaser:
|
||||
logger.error(f"Period {period_ns} ns out of range (8000-60000)")
|
||||
return False
|
||||
|
||||
command = f"LDF {period_ns}"
|
||||
return self._write_command(command)
|
||||
return self._write_verified("LDF", period_ns)
|
||||
|
||||
def set_current_ma(self, current: int) -> bool:
|
||||
"""Set pump diode current in mA."""
|
||||
"""Set pump diode pulse current (LDS) in mA.
|
||||
|
||||
False means the controller did not take the value — see
|
||||
_write_verified. The manual's range for LDS is 0-7000 mA; the
|
||||
2000 mA ceiling here is this rig's limit, not the protocol's.
|
||||
"""
|
||||
if not (0 <= current <= 2000):
|
||||
logger.error(f"Current {current} mA out of range (0-2000)")
|
||||
return False
|
||||
|
||||
command = f"LDS {current}"
|
||||
return self._write_command(command)
|
||||
return self._write_verified("LDS", current)
|
||||
|
||||
def set_pulse_mode(self, mode: PulseMode) -> bool:
|
||||
"""Set pulse mode."""
|
||||
"""Set pulse mode.
|
||||
|
||||
Note from the manual's LDG entry: "LDF has to be set again after LDG
|
||||
is changed, except for single pulse triggering" — so a caller that
|
||||
changes the mode has to re-send the frequency.
|
||||
"""
|
||||
command = f"LDG {mode.value}"
|
||||
return self._write_command(command)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user