Repository navigation
Driver error handling: real exceptions, no hangs - #98
Merged
Merged
Conversation
_receive raised CommunicationError, which was never imported, so a bad checksum or failed read became a NameError (#88), and a timeout returned the in-band value (0xFF, 0xFF, 0xFF, 0xFF) (#58). Now a timeout raises TimeoutError and a failed read or bad checksum raises OSError, chained to the serial error (#60). They are built-in exceptions because Pyro can only rebuild those on a client, and a Pyro CommunicationError (the issue's suggestion) would make the client's proxy drop its connection and lock. _communicate's lock was created fresh on every call, so it serialized nothing, and a failed exchange never left the ticket queue, so every later call spun forever. A single per-laser lock replaces both. The wait for a reply now sleeps instead of busy-polling. on() and off() send ten no-ops as a delay; one going unanswered is still tolerated, as it was when timeouts returned a value. Fixes #88, fixes #58 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They also caught KeyboardInterrupt and SystemExit, and threw away the original error. TSL550.wavelength_logging's loop never repeated; it now raises TimeoutError saying how far the data got, or OSError chained to the serial error, instead of an opaque RuntimeError. The commented-out TSL550 test scaffold is replaced with real tests. Fixes #60 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
analog_read spun at full CPU until the pin reported, forever if it never did, hanging the whole daemon. It now takes a timeout (default 1 s), sleeps between polls, and raises TimeoutError. Fixes #57 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
connect() dropped its ResourceManager as soon as it returned; it is now kept and closed by close(). The timeout docstring said the default was 1 ms; it is 1000 ms. Fixes #79 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They passed serial numbers to the constructor; instruments are constructed without arguments and connected with connect(). The examples now close the stage in a finally block, drop the Kinesis DLL path workaround (the thorlabs_kinesis package adds that directory itself), and correct comments that disagreed with the drivers. Fixes #41 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 5, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #88, #58, #60, #57, #79, #41. There is one commit per issue (#88 and #58 share one).
ppcl55x (#88, #58, part of #60)
CommunicationError, which was never imported, so it became aNameError. A timeout returned the in-band value(0xFF, 0xFF, 0xFF, 0xFF). Now:TimeoutError;OSError, chained to the serial error;OSError.CommunicationError(whichppcl55xraisesCommunicationErrorwithout importing it, so read errors becomeNameError#88 suggested):CommunicationError, the client's proxy drops its connection (Pyro5/client.py,_pyroInvoke). That releases any lock the client holds.SerializeError._readreturns a magic(0xFF, 0xFF, 0xFF, 0xFF)tuple to signal a timeout #58 fix needs it._communicatecreated itsthreading.Lock()fresh on every call, so the lock serialized nothing._readreturns a magic(0xFF, 0xFF, 0xFF, 0xFF)tuple to signal a timeout #58 it would happen on every timeout too. A single per-laser lock replaces both.on()andoff()still tolerate unanswered no-ops. They send ten no-ops purely as a delay. With the old timeout value, an unanswered one passed silently, so it still does. The first command (Resena) isn't tolerated, so a laser that doesn't respond still makeson()fail.Other bare
except:clauses (#60)wavelength_logging: thewhile Truenever repeated, and every failure became the sameRuntimeError.TimeoutError, saying how far it got ("after 1 of 3 points").OSError, chained to the original.Exception), and kdc101 logs the traceback.Arduino (#57)
analog_readspun at full CPU until a pin reported, forever if it never did. It now takestimeout: float = 1.0, sleeps between polls, and raisesTimeoutError.RTO (#79)
connect()no longer discards its VISAResourceManager. It is kept, andclose()closes it even if closing the device fails.Motion examples (#41)
connect(serialno=..., ...), using the drivers' real keyword names. They close the stage infinally.os.add_dll_directoryfor the Kinesis DLLs, becausethorlabs_kinesisadds that directory itself (and readsTHORLABS_DLL_PATHS).Behavior changes for the changelog
TimeoutError/OSError(previously a magic value or aNameError).wavelength_logging: raisesTimeoutError/OSErrorinstead ofRuntimeError.analog_readraisesTimeoutErrorafter 1 s by default, instead of hanging.Testing
on()with unanswered no-ops;analog_read;ResourceManagerlifetime.on()/off()and normal reads on a real PPCL550/551. PPCL550 autoconnect() cycles between success and failure #28 (the PPCL550 autoconnect flapping) may be related to the hang fixed here.Found but not fixed (KDC101)
I found these while checking the examples against the drivers:
stop_modegetter: compares an int to ac_short, so it is always False, and itselifchecksjog_modeby mistake. It raises when jog mode is continuous, andkcube_jogging.pyprints it.jog_velocitysetter: overwrites the jog acceleration withmove_acceleration.jog_accelerationsetter: sends the move velocity, unconverted, instead of the jog velocity.move_continuous: readse.errcodeon aRuntimeError, which has no such attribute.close(): raisesAttributeErroron an instance that never connected.🤖 Generated with Claude Code