Conversation
Three defects that all come from the same place: a DM motor in MIT mode keeps executing the last target frame it was given, and a disabled motor sends nothing at all. The enable window. The 0xFC enable carries no target of its own and does not clear the target registers, so the instant it takes effect the motor resumes whatever target a previous session left behind -- if you Ctrl+C'd mid-close(), that register still says q=closed, kp=100. Enable now streams zero-gain frames over that window, waits for a fresh status frame, and then leaves the motor holding the position it actually measured. initialize(), enable() and clear_fault() all go through that one path, and the bare enable() in initialize()'s retry loop is gone, since an uncovered enable is exactly the window being closed. The silent wait. initialize() used to wait quietly for feedback while the motor was already enabled. An enabled motor that hears nothing for ~900 ms latches the 0xD comm-loss fault, so that wait manufactured the failure it was looking for. It now keeps feeding the motor while it waits. Stale state. A disabled motor does not stream status frames, so get_state() silently returned the last decoded values -- or constructor defaults, i.e. position 0.0 with temperatures 0/0 -- while the jaw might be anywhere. MotorState and GripperState now carry data_age_s, and get_state() warns when the snapshot is older than STALE_AFTER_S. A new refresh_status() sends the 0xCC refresh command, which the motor answers regardless of enable state, so the position can be read before the first enable instead of guessed. Also adds the three error codes the SDK was rendering as "unknown error": 0x8 (over-voltage), 0xE (overload) and 0xD (comm loss) -- the last one being the fault a disabled motor sits in, which made a normal state look like a hardware failure. The old `err in (0, 1)` reading of `initialize()` is deliberately NOT adopted: this repository requires `err == 1` and tests/test_enable.py pins that. Only the freshness half of the change is taken here. Verified: 52 unittest cases, 0 failures -- the 40 that shipped plus 12 in tests/test_stale_state.py covering the freshness contract, the new error codes, and refresh_status on both paths.
Contributor
|
提交重复 |
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
Three defects that all come from the same place: a DM motor in MIT mode keeps executing
the last target frame it was given, and a disabled motor sends nothing at all.
The enable window. The
0xFCenable carries no target of its own and does not clearthe target registers, so the instant it takes effect the motor resumes whatever target a
previous session left behind — if you Ctrl+C'd mid-
close(), that register still saysq=closed, kp=100. Enable now streams zero-gain frames over that window, waits for afresh status frame, and then leaves the motor holding the position it actually measured.
The silent wait.
initialize()used to wait quietly for feedback while the motor wasalready enabled. An enabled motor that hears nothing for ~900 ms latches the
0xDcomm-loss fault, so that wait manufactured the failure it was looking for. It now keeps
feeding the motor while it waits.
Stale state. A disabled motor does not stream status frames, so
get_state()silently returned the last decoded values — or constructor defaults, i.e. position
0.0with temperatures
0/0— while the jaw might be anywhere.MotorStateandGripperStatenow carry
data_age_s, andget_state()warns when the snapshot is older thanSTALE_AFTER_S.Consumers that read
get_state()now have a way to tell a measurement from a leftover,and
refresh_status()lets them read the position before enabling instead of guessing.No signature becomes incompatible: the added parameters are optional and the return types
are unchanged.
Changes
_hold_at_current()and_enable_and_hold()and routeinitialize(),enable()andclear_fault()through them, so every re-enable is covered by zero-gain frames and ends holding the measured position.enable()frominitialize()'s retry loop: an uncovered enable is exactly the window being closed.MotorState.has_data/data_age_sandGripperState.has_data/is_stale/STALE_AFTER_S.LiteGrip.refresh_status()andLiteGripCAN.refresh_status()(the0xCCrefresh command), which the motor answers regardless of enable state. It sends no motion command.0x8(over-voltage),0xD(comm loss) and0xE(overload) error codes.0xDin particular is the fault a disabled motor sits in, and it was being reported as "unknown error" — a normal state that looks like a hardware failure.The old
err in (0, 1)reading ofinitialize()is deliberately not adopted: thisrepository requires
err == 1andtests/test_enable.pypins that. Only the freshnesshalf of the change is taken.
Testing
Result:
Ran 52 tests—OK. That is the 40 that shipped with the repository, plus 12in a new
tests/test_stale_state.pycovering the freshness contract (never-received vsmeasured-but-old), the threshold boundary, the new error codes, and
refresh_status()onboth the answering and the silent path.
Two existing test doubles needed the fields the code now reads, and
tests/fake_can.pyand
tests/test_enable.pywere updated for that. No assertion was removed or relaxed:test_enable.pystill requireserr == 1for success, still requireserr == 0to raiserather than lie, and still requires retries to happen. Its fake controller now advances
its scripted error on the
enablecall rather than on everypoll, because the enablewindow polls repeatedly during its zero-gain cover — which is also a more faithful model
of what the test's own comment describes ("the first two enable frames were lost, the
third really enabled").
Issues
No tracking issue. Nothing is closed by this pull request.