Skip to content

TCube laser: pass Kinesis boolean arguments as four bytes - #70

Merged
kalidke merged 4 commits into
mainfrom
cppbool-into-main
Sep 29, 2026
Merged

kalidke merged 4 commits into
mainfrom
cppbool-into-main

Conversation

@kalidke

@kalidke kalidke commented Sep 29, 2026

Copy link
Copy Markdown
Member

Keith's fix/revert-cppbool work, into main: the Kinesis boolean binding split, plus the manuals/ archive note.

What changes

  • Kinesis booleans are split by role. Every Kinesis boolean was bound as a one-byte Bool. The headers declare
    boolean arguments as a four-byte type, so an argument is now a zero-extended Cuint (KBOOL_ARG), and
    returns stay one byte (KBOOL_RET). The affected arguments are LD_EnableMaxCurrentAdjust,
    LD_EnableTIAGainAdjust and LD_EnableLastMsgTimer. No driver method calls those three yet, so no current
    behaviour changes; not yet run on hardware. The vendor facts are in manuals/Thorlabs/TLD001/BINDING.md.
  • manuals/ is documented in CLAUDE.md as a local-only, gitignored symlink to the lab's instrument archive on the
    NAS, and it is added to .gitignore.
  • CHANGELOG [Unreleased] → Fixed has the entry.

How this branch was made

Compatibility (decisions 0033 and 0035)

Tests

  • Local suite on this tree (kitt, Julia 1.13, xvfb-run -a): 998 pass, 8 broken, 0 fail.
  • The lab/tests record follows on the pushed head (decision 0012).

🤖 Generated with Claude Code

kalidke and others added 4 commits September 29, 2026 12:22
v0.2.3 retyped nine returns and three arguments from `BOOL` (`Cuint`) to a
one-byte `Bool`, on a second-hand report that the Kinesis header declares
C++ `bool`. The header says otherwise, and it was on the lab NAS the whole
time:

    Thorlabs.MotionControl.TCube.LaserDiode.h:37
        typedef unsigned int BOOL;

27 uppercase `BOOL`, zero lowercase `bool`. The original binding was correct
and the change was a regression.

It is wrong in the dangerous direction for ARGUMENTS: passing one byte where
the callee reads four leaves the upper three undefined, so a `false` can
arrive as true. The call that matters is
`LD_EnableMaxCurrentAdjust(serial, enableAdjust, enableDiode)`, whose second
flag enables the laser diode during a max-current adjustment — the call the
closed-loop clamp sequence depends on being able to pass as false. Four bytes
is safe under either ABI for an argument; one byte is not.

The same report also drove a `[limitation]` warning on `TLI_DeviceInfo`
saying it was packed to 100 bytes and misaligned from `PID` onward. The
header's `#pragma pack(1)` is COMMENTED OUT, and the fields are the same
4-byte `BOOL`. Verified: our declaration is 120 bytes with `PID` at offset
88, which is exactly the header's default-alignment arithmetic. That warning
was false and is deleted — it would have sent the next reader hunting a
defect that does not exist.

Both docstrings now record the history, so nobody re-applies either "fix".

Nothing in this package calls the affected functions, so the regression is
latent here; a downstream calls `LD_CheckConnection`.

Suite: 959 passed, 8 broken, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rig repo diffed the installed vendor header against the copy on the lab
NAS and settled this properly. The NAS copy is a Clang.jl generation input,
hand-edited to parse without Windows headers: the vendor preamble was
replaced with local typedefs including `typedef unsigned int BOOL`, `OaIdl.h`
and `__declspec` were removed, both pack pragmas were commented out, and
every lowercase `bool` was rewritten to `BOOL`. It carries no ABI information
of its own, and I treated it as authoritative. The INSTALLED header has 32
lowercase `bool`, zero `BOOL`, and an active `#pragma pack(1)`.

So v0.2.3 was half right and my revert was half wrong, and neither pure type
is correct. The two roles differ:

- `KBOOL_ARG = Cuint`, zero-extended 0 or 1, for arguments. Robust under
  either ABI: a `bool` callee reads the low byte, a `BOOL` callee reads four,
  both see 0 or 1. A 1-byte argument is NOT robust — the upper three bytes are
  undefined, so a `false` can arrive as true, and
  `LD_EnableMaxCurrentAdjust`'s second flag enables the diode.
- `KBOOL_RET = Bool`, one byte, for returns. A `bool` return sets only `AL`
  and leaves the rest of `EAX` undefined, so reading four bytes can turn
  `false` into nonzero — `LD_CheckConnection` reporting a disconnected
  controller as connected. Reading the low byte is right under `bool` and
  still right under `BOOL` returning 0 or 1.

Nine returns and four argument positions, split accordingly.

`TLI_DeviceInfo`'s warning is restored and corrected rather than deleted: the
real layout is 100 bytes with `PID` at 85 under `pack(1)` with 1-byte flags,
against this declaration's verified 120 and 88. Still not fixed — correcting
it needs field types AND packing, and a controller to read a device list back
from. The docstring now names the NAS copy as untrustworthy so nobody
relitigates this from it a third time.

Suite: 959 passed, 8 broken, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 405 rig read its installed Kinesis 1.14.10 header and confirmed the 642
rig's reading independently: 32 lowercase C++ `bool`, zero `BOOL`, no `BOOL`
typedef, and `#pragma pack(1)` ACTIVE around `TLI_DeviceInfo`. It also found
something neither of us had — the two versions differ in an ARRAY LENGTH:

    1.14.10        char serialNo[9]
    the 642 rig    char serialNo[16]

So the struct layout varies by SDK version in more than one dimension, and no
single declaration can be right for both. Ours matches neither: 120 bytes with
`PID` at 88, against a packed 100-with-85 on one and something different again
on the other.

The docstring now records both observed variants as a table, states the policy
(do not call `TLI_GetDeviceInfo` through this declaration; declare it per SDK
version beside the header it came from, and assert `sizeof`), and confirms by
inspection that `initialize` touches only the two counting `TLI_` functions,
so nothing reaches the struct.

It also names the NAS copy as untrustworthy, with the reason, so this is not
relitigated from that file a fourth time.

The boolean split stands and both rigs endorse it: 4-byte zero-extended
arguments, low-byte returns.

Suite: 959 passed, 8 broken, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two rigs have now read their own vendor-installed Kinesis header --
1.14.10 on seq-sr, 1.14.47.22504 on quickbeam -- and both declare
lowercase C++ `bool` with `#pragma pack(1)` active. The `BOOL`
docstring still asserted the opposite, on the strength of a copy on the
NAS that turns out to be a Clang.jl generation input hand-edited to
parse without the Windows SDK. That claim also contradicted the
`KBOOL_RET` docstring three lines above it.

The docstring was additionally unattached: it sat between
`const KBOOL_RET` and `struct tagSAFEARRAYBOUND`, so Julia bound it to
the SAFEARRAY bound struct rather than to `BOOL`. Moved above
`const BOOL = Cuint`, where it belongs.

No binding changes. `BOOL` is still four bytes and still used only by
`TLI_DeviceInfo`, which nothing calls; it stays because the two vendor
versions disagree on `serialNo`'s length, so there is no single layout
to change it to.

Also record the instrument archive: `manuals/` is a local-only,
gitignored symlink to the lab-wide archive on the NAS, documented in
CLAUDE.md with the reason it must never be committed.

Local suite: 959 pass, 8 broken, 0 fail.

Rebased onto main for its pull request: the two dev/output plan files are
dropped (decision 0028; they stay in fix/revert-cppbool's history), CLAUDE.md
and .gitignore keep main's text beside this commit's, and CHANGELOG gains the
[Unreleased] entry for the Kinesis boolean split.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kalidke
kalidke merged commit ac8c3e3 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