Skip to content

TCube laser: send the setpoint after enabling, zero it before disabling (0.2.4 safety patch) - #68

Merged
kalidke merged 7 commits into
mainfrom
tcube-setpoint-0.2.4
Sep 29, 2026
Merged

kalidke merged 7 commits into
mainfrom
tcube-setpoint-0.2.4

Conversation

@kalidke

@kalidke kalidke commented Sep 29, 2026

Copy link
Copy Markdown
Member

Safety patch, 0.2.3 -> 0.2.4. v0.2.3 and every earlier tag are affected.

The hazard

The Thorlabs TLD001 ignores LD_SetLaserSetPoint while its output is disabled and, on the next LD_EnableOutput, runs on whatever setpoint it had stored. v0.2.3's setpower sent the setpoint whenever it was called and light_on only enabled the output, so the ordinary setpower(l, x); light_on(l) ran at the stale stored value. Observed on the 642 nm rig's TLD001 (64849775) on 2026-09-28 and recorded in #66; on 2026-09-29 a script in that order drove the diode for about 13 s at the controller's ~160 mA limit.

The fix (TCubeLaserControl)

  • light_on re-checks drive_current against the ceiling (nothing is sent if it fails), enables, then sends the setpoint. If that setpoint fails it disables the output again and throws.
  • light_off and shutdown zero the setpoint while the output is still on, then disable, so the next enable starts dark. A failed zero is logged; a failed disable still throws.
  • setpower still sends on every call (harmless while off, and it covers an output enabled elsewhere); it now says when the current will be applied at light_on.
  • Limitation, documented: between the enable and the setpoint that follows it (one USB round trip) the controller runs on its stored setpoint: 0 after this driver's light_off/shutdown, but anything up to its limit if other software left it there.

Behaviour changes, each in the CHANGELOG: light_on before any setpower now enables at setpoint 0 with a warning (it used to run at the stored value, which is the hazard); a failed zeroing in light_off logs instead of throwing.

Decision 0035: this release changes no public signature and no config type. The TCubeLaser struct, its fields and both constructors are unchanged, and so are the signatures of setpower, light_on, light_off and shutdown. The one new function, zero_setpoint, is internal and unexported.

Release process (same PR, per T16)

  • Lab decision 0033: TagOnMerge skips a -DEV version quietly, and CI's version check accepts X.Y.Z-DEV. The rules and their selftest are in .github/scripts/versions.py, shared by both workflows. The selftest (16 cases) passes; the read path was checked on Python 3.12.
  • Decision 0009 amendment: TagOnMerge tags only when the merged commit's tree is byte-identical to that of a commit with a passing lab/tests (the commit itself or the merged PR's head); otherwise it fails and says why. The coverage block was dry-run against real squash merges here (TCube laser: refuse out-of-range currents, keep the caller's ceiling, report the drive current #62, Trim CI to the minimum useful signal; make the local suite the gate #61, PI stage: refuse to finish initialize unreferenced #64): each squash commit's tree equalled its PR head's tree.
  • One separate commit (391a3e4), per admiral's ruling on the record_tests.jl refusal: a minimal test/test_groups.toml (the whole suite as Core), dev/output/ gitignored (decision 0028), plus the two further things record_tests.jl turned out to need. First, test/lab_summary.jl writes the LAB_TEST_SUMMARY it reads; it wraps the existing top-level testset in two lines and changes no test. Second, DAQmx's [sources] is committed in the rev = "main" form Pkg.test() rewrites it to, so a run leaves the tree clean.

Tests

  • New test/tcube_output_order.jl (8 cases) against a fake controller that models the ignore-while-off behaviour (test/tcube_fake_sdk.jl, extended). On v0.2.3's driver, cases a to g fail (g only on its log assertion); h is a regression guard that also passes on v0.2.3. On this head: 28/28.
  • Two existing assertions updated (the shutdown call order now starts with the zeroing setpoint); no other test changed.
  • Full local suite on the tested tree: xvfb-run -a julia --project -e 'using Pkg; Pkg.test()': 987 pass, 8 broken, 0 fail, 0 error, 122 s wall. The head differs from that tree only in the [sources] line and CHANGELOG text. The lab/tests record on this head follows.
  • Hardware verification: not done in this repository. No hardware result is claimed.

Not to be merged without Keith's word.

🤖 Generated with Claude Code

kalidke and others added 7 commits September 29, 2026 10:26
Lab decision 0033: main carries X.Y.Z-DEV between releases. TagOnMerge now
skips a -DEV version quietly (exit 0, no tag), and CI's version check
accepts X.Y.Z-DEV: against a released base the core must rise, against a
-DEV base it may stay or rise, never fall, and the X.Y.Z it names must be
untagged. The rules live in .github/scripts/versions.py, shared by both
workflows, with a selftest CI runs first.

Decision 0009's amendment: a merge or squash makes a commit the record
never saw, so TagOnMerge tags only when the merged commit's tree is
byte-identical to that of a commit with a passing lab/tests status (the
commit itself or the merged pull request's head). Otherwise it does not
tag, says why, and fails so the re-record is visible.

LidkeLab/.github has no shared tag workflow; the header says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ev/output ignored

Admiral's ruling on the lab/tests refusal, as one separate commit:
test/test_groups.toml declares the whole current suite as the group Core,
and dev/output/ is gitignored (lab decision 0028). Two more things
record_tests.jl needs, found running it:

- It reads the run's result from LAB_TEST_SUMMARY, which this suite never
  wrote. test/lab_summary.jl wraps the existing top-level testset (two
  lines in runtests.jl) and writes that summary; a failing suite still
  fails Pkg.test() exactly as before. No test changes.
- It refuses a tree that changes during the run, and Pkg.test() on Julia
  1.13 rewrites DAQmx's [sources] entry to {rev = "main", url = ...} on
  every run. Committing that form makes the rewrite a no-op. It names the
  branch the bare url already tracked, and [sources] is read only in the
  root project, so no dependent sees it.

Full decision 0008 adoption waits for the package-standard pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG [0.2.4] names v0.2.3 and earlier as affected by the TCube
ignored-setpoint hazard, gives pinned rigs the call-order workaround, and
lists the behaviour changes and the release-process changes. Project.toml
0.2.4; README and mc-system-design install pins v0.2.4. rig-causes gains
the symptom row, with the potentiometer control source as the other cause
of the same symptom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TLD001 ignores LD_SetLaserSetPoint while its output is off and enables on
its stored setpoint, so setpower then light_on ran at a stale value.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CHANGELOG [0.2.4]: UPGRADE WARNING. 0.2.4 applies setpower values that
  v0.2.3 never applied, and light_on re-sends drive_current while on; rigs
  check their setpower values, set max_current to the diode rating and the
  current-limit pot at or below it, and check on hardware before repinning.
  The enable-window limitation names the pot as the only hardware bound.
- Four places that said every merge to main is tagged (CLAUDE.md, README,
  mc-extend SKILL.md twice) and the CHANGELOG preamble now describe -DEV
  skips and the untested-tree refusal.
- The TLD001 ignore-while-off fact is stated once, on TCubeLaser; the
  function docstrings refer to it.
- light_off logs a failed zero before the disable, so a failing disable
  cannot swallow it.
- Tests: setpoint then disable both failing (status and throw) leave
  is_on true on the "may still be ON" path; a failed zero is logged even
  when the disable then fails.
- lab_summary.jl drops the unused selection key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kalidke
kalidke merged commit e8fd550 into main Sep 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant