Repository navigation
feat: report a stale status read, and let the caller refresh it - #68
Merged
Merged
Conversation
A status read could not tell a measurement from a memory. The readings get_state() returns are a cache, and only a status frame updates it. A DM motor answers the control frames it receives, so a gripper that is being driven reports on its own — but one holding a pose, or sitting disabled, sends nothing, and get_state(wait=True) then waited out its 50 ms and handed back the previous frame with no sign that nothing had answered. Say which happened, and give the caller a way out: - GripperState gains fresh (a frame answered this read), data_age_s (seconds since the frame behind the snapshot arrived, None when there never was one), and is_stale(stale_after_s=STALE_AFTER_S), plus the module-level STALE_AFTER_S = 0.5 s — the threshold the C++ SDK uses, so a guard reads the same on either side. - LiteGrip.refresh_status() and LiteGripCAN.refresh_status() send the 0xCC status request, which makes an idle motor speak without moving the jaws. - MotorState records last_frame_time (wall clock) beside last_update (monotonic). The receive path stamps frames with time.monotonic(), so an age has to be measured on that clock; a stamp for a human to read needs one of its own. Note for callers: GripperState.timestamp used to be the time of the call that returned the snapshot, and is now the arrival time of the frame behind it — 0.0 when no frame has ever arrived. Code that timed a loop with it reads a stale constant instead of the current time, and code that read it as a heartbeat should read fresh instead. That is a public field changing meaning; under the release rules this publishes as a minor. Closes #66
cao-xiao-hao
force-pushed
the
feat/report-a-stale-status-read
branch
from
October 14, 2026 06:42
7f7499f to
4f49b93
Compare
|
🎉 This PR is included in version 1.1.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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.
Summary
A status read could not tell a measurement from a memory. The readings
get_state()returns are a cache, and only a status frame updates it. A DM motor answers the control frames it receives, so a gripper that is being driven reports on its own — but one holding a pose, or sitting disabled, sends nothing, andget_state(wait=True)then waited out its 50 ms and handed back the previous frame with no sign that nothing had answered. This makes the snapshot say which happened, and adds the request that gives an idle motor something to answer.Breaks a public field's meaning for consumers; publishes as a minor under this policy.
GripperState.timestampused to be the time of the call that returned the snapshot and is now the arrival time of the frame behind it,0.0when no frame has ever arrived. Code that timed a loop with it now reads a stale constant, and code that read it as a heartbeat should readfreshinstead. A major was not approved for this, so the marker is deliberately absent — if you want it, the newest tag isv1.0.2, and the transition would bev1.0.2 -> v2.0.0.Changes
GripperStategainsfresh(a frame answered this read),data_age_s(seconds since the frame behind the snapshot arrived,Nonewhen there never was one), andis_stale(stale_after_s=STALE_AFTER_S);STALE_AFTER_S = 0.5 sis exported fromlitegrip, the threshold the C++ SDK uses.LiteGrip.refresh_status()andLiteGripCAN.refresh_status()send the 0xCC status request, which makes an idle motor speak without moving the jaws. It reports likeenable():Falsewith a warning when the request did not go out.LiteGripCANgainslast_update(monotonic) andlast_frame_time(wall clock);MotorStaterecords the second beside the first, because the receive path stamps frames withtime.monotonic()and an age has to be measured on that clock.get_state()passes the frame's stamps through instead of stamping the call, and returnsfresh=bool(answered).README.mdandreadme_zn.md: the correctedget_state()row, arefresh_status()row, and a section on whatfresh,data_age_s,timestampandis_stale()mean.tests/test_state_staleness.py: 12 tests over an idle bus, a never-speaking motor, the request path, and the failed write.tests/fake_can.py:FakeLiteGripCANcan go idle (delivers), serve a request (refresh_ok,refresh_pending,refresh_calls), and carries the two stamps.Testing
The new tests are not vacuous: forcing
timestamp=time.time(), fresh=True, data_age_s=Noneinget_state()fails 5 of the 12.Issues
Closes #66