Affected unit
One of REEF lab's four deployed Squid microscopes (microscope received around December 2023)
Variant: Squid v2 simplified (is_squid_plus = false)
Camera: Daheng MER-1220-32U3M (USB-3 industrial mono)
Microcontroller: Teensyduino USB Serial, USB ID 16c0:0483, bcdDevice 2.80, iSerial 12769040
Host: Raspberry Pi 5, Debian 13 trixie, kernel-bundled cdc_acm driver, /dev/ttyACM0
Software: squid_control (REEF-vendored from squid-control repo, git 66cf391-dirty)
Symptom
microcontroller.is_busy() returns True indefinitely. Every code path that polls it in a while self.microcontroller.is_busy(): time.sleep(...) hangs (we saw this in LaserAutofocusController.wait_till_operation_is_completed, AutofocusWorker.wait_till_operation_is_completed, MultiPointWorker.wait_till_operation_is_completed).
Observable from outside
/health returns controller_ready=True, microcontroller_ok=True (the busy state is NOT surfaced)
Stage motion (/move/to, /move/handoff, /move/return) works normally → the busy poll lives only inside the AF/multi-point loops; the navigation controller path does not gate on it
Every autofocus call (/autofocus/contrast, /autofocus/laser, /autofocus/hybrid) hangs indefinitely on the busy-poll loop until our newly-added 10 s timeout fires
What does NOT recover it
Restarting the squid-server Python process (closes & re-opens /dev/ttyACM0)
Restarting the host's cdc_acm USB binding (we could not test this directly today — needs sudo on the host — but on similar incidents in May 2025 the binding cycle alone did not clear it)
The microcontroller.reset() SDK call (sends CMD_SET.RESET) — untested today because the wedge prevents safely entering the reset path
What DOES recover it
Unplugging the Teensy's USB cable for ~30 s and replugging — i.e. a full Teensy power-cycle from the USB bus. The Teensy's onboard 5 V drops, MCU resets cleanly, is_busy() returns to False on next session
Reproduction conditions (not deterministic — hours of normal operation between events)
Has occurred multiple times across our 4 Squid units (squid-1, squid-2, squid+3) over the past 6 weeks
Strongly correlates with rapid back-to-back AF calls — particularly when laser AF runs first and a contrast AF or /plate/probe follows within ~1 s on the same busy_lock. We documented this exact race in plate.py:280 as "return_stage → get_image sequence wedged the laser AF capture (busy_lock held indefinitely)"
Today's instance (2026-06-09 ~15:50) wedged after 3 sequential AF calls on a freshly-loaded plate (deployed our new hybrid AF, ran contrast → laser → hybrid back-to-back)
Specific question(s) for the vendor
Is there a firmware-level health flag the Teensy exposes for "busy state has been True for > N seconds with no command outstanding"? We'd like to detect the wedge in /health rather than discovering it on the next AF call.
Is there a documented re-sync sequence for the SDK to send when it suspects is_busy() is stuck? Ideally a sequence that does not require the Teensy to actually be responsive — e.g. a UART break + handshake, or a magic-byte sequence the firmware checks even when "busy".
Is the underlying race known? When laser-AF's spot capture overlaps with stage-motion set_target_position calls, can the firmware's busy bookkeeping latch True from one path while the other clears it on the wrong cycle? If yes, what's the recommended thread-safety contract — e.g. must all stage motion be serialized through a single host-side mutex with no overlap with laser-AF?
Recommended firmware version for the 850 nm laser-AF retrofit kit. Our bcdDevice 2.80 is whatever shipped with the units in early 2025; if there's a 2.81+ that addresses the busy-latch race, we'd happily flash.
Defensive code we've shipped today (REEF-side, ready to revert if vendor recommends)
Added timeout_s (default 10 s) to all three SDK wait_till_operation_is_completed methods (LaserAutofocusController, AutofocusWorker, MultiPointWorker) so the wedge stops cascading into a server-wide deadlock. On timeout we raise TimeoutError, which lets the async with sq.busy_lock: block release the lock so the next operation can proceed (and surfaces a clear error message instead of a 30 s gateway timeout).
This is a workaround, not a fix — the underlying is_busy=True latch is still there until a Teensy power-cycle. We'd happily replace it with whatever the vendor recommends.

Affected unit
One of REEF lab's four deployed Squid microscopes (microscope received around December 2023)
Variant: Squid v2 simplified (
is_squid_plus = false)Camera: Daheng MER-1220-32U3M (USB-3 industrial mono)
Microcontroller: Teensyduino USB Serial, USB ID
16c0:0483,bcdDevice 2.80,iSerial 12769040Host: Raspberry Pi 5, Debian 13 trixie, kernel-bundled
cdc_acmdriver,/dev/ttyACM0Software:
squid_control(REEF-vendored fromsquid-controlrepo, git66cf391-dirty)Symptom
microcontroller.is_busy()returnsTrueindefinitely. Every code path that polls it in awhile self.microcontroller.is_busy(): time.sleep(...)hangs (we saw this inLaserAutofocusController.wait_till_operation_is_completed,AutofocusWorker.wait_till_operation_is_completed,MultiPointWorker.wait_till_operation_is_completed).Observable from outside
/healthreturnscontroller_ready=True, microcontroller_ok=True(the busy state is NOT surfaced)Stage motion (
/move/to,/move/handoff,/move/return) works normally → the busy poll lives only inside the AF/multi-point loops; the navigation controller path does not gate on itEvery autofocus call (
/autofocus/contrast,/autofocus/laser,/autofocus/hybrid) hangs indefinitely on the busy-poll loop until our newly-added 10 s timeout firesWhat does NOT recover it
Restarting the
squid-serverPython process (closes & re-opens/dev/ttyACM0)Restarting the host's
cdc_acmUSB binding (we could not test this directly today — needssudoon the host — but on similar incidents in May 2025 the binding cycle alone did not clear it)The
microcontroller.reset()SDK call (sendsCMD_SET.RESET) — untested today because the wedge prevents safely entering the reset pathWhat DOES recover it
Unplugging the Teensy's USB cable for ~30 s and replugging — i.e. a full Teensy power-cycle from the USB bus. The Teensy's onboard 5 V drops, MCU resets cleanly,
is_busy()returns toFalseon next sessionReproduction conditions (not deterministic — hours of normal operation between events)
Has occurred multiple times across our 4 Squid units (squid-1, squid-2, squid+3) over the past 6 weeks
Strongly correlates with rapid back-to-back AF calls — particularly when laser AF runs first and a contrast AF or
/plate/probefollows within ~1 s on the samebusy_lock. We documented this exact race inplate.py:280as "return_stage → get_image sequence wedged the laser AF capture (busy_lock held indefinitely)"Today's instance (2026-06-09 ~15:50) wedged after 3 sequential AF calls on a freshly-loaded plate (deployed our new hybrid AF, ran contrast → laser → hybrid back-to-back)
Specific question(s) for the vendor
Is there a firmware-level health flag the Teensy exposes for "busy state has been True for > N seconds with no command outstanding"? We'd like to detect the wedge in
/healthrather than discovering it on the next AF call.Is there a documented re-sync sequence for the SDK to send when it suspects
is_busy()is stuck? Ideally a sequence that does not require the Teensy to actually be responsive — e.g. a UART break + handshake, or a magic-byte sequence the firmware checks even when "busy".Is the underlying race known? When laser-AF's spot capture overlaps with stage-motion
set_target_positioncalls, can the firmware's busy bookkeeping latch True from one path while the other clears it on the wrong cycle? If yes, what's the recommended thread-safety contract — e.g. must all stage motion be serialized through a single host-side mutex with no overlap with laser-AF?Recommended firmware version for the 850 nm laser-AF retrofit kit. Our
bcdDevice 2.80is whatever shipped with the units in early 2025; if there's a 2.81+ that addresses the busy-latch race, we'd happily flash.Defensive code we've shipped today (REEF-side, ready to revert if vendor recommends)
Added
timeout_s(default 10 s) to all three SDKwait_till_operation_is_completedmethods (LaserAutofocusController,AutofocusWorker,MultiPointWorker) so the wedge stops cascading into a server-wide deadlock. On timeout we raiseTimeoutError, which lets theasync with sq.busy_lock:block release the lock so the next operation can proceed (and surfaces a clear error message instead of a 30 s gateway timeout).This is a workaround, not a fix — the underlying
is_busy=Truelatch is still there until a Teensy power-cycle. We'd happily replace it with whatever the vendor recommends.