diff --git a/.github/workflows/sdk_protos_map.csv b/.github/workflows/sdk_protos_map.csv index 138081ad7b..115db7144a 100644 --- a/.github/workflows/sdk_protos_map.csv +++ b/.github/workflows/sdk_protos_map.csv @@ -5,6 +5,7 @@ arm,GetEndPosition,,get_end_position,EndPosition,endPosition,getEndPosition arm,MoveToPosition,,move_to_position,MoveToPosition,moveToPosition,moveToPosition arm,MoveToJointPositions,,move_to_joint_positions,MoveToJointPositions,moveToJointPositions,moveToJointPositions arm,MoveThroughJointPositions,,,MoveThroughJointPositions,, +arm,MoveThroughJointPositionsStreamed,,move_through_joint_positions_streamed,MoveThroughJointPositionsStreamed,, arm,GetJointPositions,,get_joint_positions,JointPositions,jointPositions,getJointPositions arm,Get3DModels,,,Get3DModels,get3DModels,get3DModels ## Flutter-only client-side helper, sums link lengths from getKinematics() locally; no proto/RPC and no analog in other SDKs: diff --git a/.github/workflows/update_sdk_methods.py b/.github/workflows/update_sdk_methods.py index a5c77ed099..c341a0a41b 100755 --- a/.github/workflows/update_sdk_methods.py +++ b/.github/workflows/update_sdk_methods.py @@ -538,11 +538,29 @@ def parse_method_usage(usage_string): param_type_link = "https://pkg.go.dev/builtin#error" else: param_raw = regex.sub(r'<.*?>', '', param).removesuffix(')').split() - ## Handle channel data types (only used for Board > StreamTicks): - if len(param_raw) == 3 and param_raw[0] == 'ch': - type_name = 'ch chan' - param_type = 'Tick' - type_link = '#Tick' + + ## pkg.go.dev HTML-escapes the arrows in channel types, so put them back + ## before we match on the tokens: + param_raw = [token.replace('<', '<').replace('>', '>') for token in param_raw] + + ## Clear the per-parameter state. Python scopes these to the whole function, + ## so a parameter shape matching none of the cases below would otherwise + ## inherit the previous parameter's values and document itself as a copy of + ## its neighbor: + type_name = None + param_type = None + type_link = None + + ## Handle channel parameters, whose type spans two tokens: a direction + ## marker and the element type. All three directions occur in the SDK, + ## and the element type can itself be a slice: + if len(param_raw) == 3 and param_raw[1] in ('chan', '<-chan', 'chan<-'): + type_name = param_raw[0] + param_type = param_raw[1] + ' ' + param_raw[2] + try: + type_link = regex.findall(r'href="([^"]+)">', param)[-1] + except: + print("DEBUG: No type link found: {}, {}".format(usage_string, param)) ## Handle named parameters: elif len(param_raw) == 2: type_name = param_raw[0] @@ -589,6 +607,14 @@ def parse_method_usage(usage_string): except: print("DEBUG: No type link found: {}, {}, {}".format(usage_string, param, param_raw)) + ## Nothing above claimed this parameter. Fall back to the stripped source + ## text so the shape that got missed is visible in the output and in the + ## log, rather than quietly taking on its neighbor's identity: + if type_name is None and param_type is None: + print("DEBUG: Unhandled parameter shape: {}, {}".format(param, param_raw)) + type_name = '' + param_type = ' '.join(param_raw) + if type_link: param_type_link = type_link else: diff --git a/docs/motion-planning/move-an-arm/move-by-joint-positions.md b/docs/motion-planning/move-an-arm/move-by-joint-positions.md index 250a97d57a..e830ac584e 100644 --- a/docs/motion-planning/move-an-arm/move-by-joint-positions.md +++ b/docs/motion-planning/move-an-arm/move-by-joint-positions.md @@ -21,7 +21,10 @@ are different tools. You reach for joint-space when: causes a wrist flip or elbow reconfiguration. - You want predictable motion between two configurations you both control. -- You are building a control loop that computes its own joint targets. + +Both methods on this page need every waypoint before the arm starts moving. If +you are computing the trajectory as the arm runs, see +[Stream joint positions to an arm](/motion-planning/move-an-arm/stream-joint-positions/). **A caveat before you dive in.** Joint-space moves bypass the motion planner. No obstacle avoidance, no constraint satisfaction, no path smoothing. If the @@ -226,12 +229,13 @@ programmatically. ## Joint-space moves compared to motion.Move -| Motion path | Use when | -| ------------------------------------ | --------------------------------------------------------------------------------------------------- | -| `arm.MoveToJointPositions` | You know the joint angles you want. | -| `arm.MoveThroughJointPositions` (Go) | You have a sequence of joint targets and want per-call velocity or acceleration caps. | -| `arm.MoveToPosition` | You have a Cartesian target pose but don't need obstacle avoidance. | -| `motion.Move` | You have a Cartesian target and want obstacle avoidance, constraints, and IK picked by the planner. | +| Motion path | Use when | +| ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `arm.MoveToJointPositions` | You know the joint angles you want. | +| `arm.MoveThroughJointPositions` (Go) | You have a sequence of joint targets and want per-call velocity or acceleration caps. | +| [`arm.MoveThroughJointPositionsStreamed`](/motion-planning/move-an-arm/stream-joint-positions/) (Python, Go, C++) | You are producing the trajectory as the arm moves and cannot supply it all up front. | +| `arm.MoveToPosition` | You have a Cartesian target pose but don't need obstacle avoidance. | +| `motion.Move` | You have a Cartesian target and want obstacle avoidance, constraints, and IK picked by the planner. | Joint-space moves are the right call when you need to control the posture of the arm precisely. They do not protect against collisions @@ -271,6 +275,8 @@ module's documentation or the kinematics file. ## What's next +- [Stream joint positions to an arm](/motion-planning/move-an-arm/stream-joint-positions/): + push waypoints while the arm is already moving. - [Move an arm to a pose](/motion-planning/move-an-arm/move-to-pose/): Cartesian motion with obstacle avoidance through `motion.Move`. - [Move with constraints](/motion-planning/move-an-arm/move-with-constraints/): diff --git a/docs/motion-planning/move-an-arm/overview.md b/docs/motion-planning/move-an-arm/overview.md index 0731618723..99315ad292 100644 --- a/docs/motion-planning/move-an-arm/overview.md +++ b/docs/motion-planning/move-an-arm/overview.md @@ -10,24 +10,28 @@ aliases: - /motion-planning/pick-and-place/ --- -Viam exposes three ways to command an arm. Three questions sort them: +Viam exposes multiple ways to command an arm. Three questions sort them: 1. **What do you know about the destination?** A Cartesian target (a pose - in space) calls for the motion service. A specific joint configuration - calls for direct joint commands. + in space), a region of acceptable poses, an ordered list of goals, or a + specific joint configuration each point to a different call. 2. **Does the path matter, or only the endpoint?** If you need a straight line, a fixed orientation, or any other rule about the path itself, you need constraints. -3. **Do you want obstacle avoidance and IK picked for you, or fine +3. **Do you want obstacle avoidance and inverse kinematics (IK) picked for you, or fine manual control?** The motion service picks the IK solution and plans around obstacles for you; direct joint commands execute exactly the angles you send. -| Pattern | Input | Obstacle avoidance | Path-shape control | When to pick | -| -------------------------------------------------------------------------------- | ------------------------ | ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| [Move to a pose](/motion-planning/move-an-arm/move-to-pose/) | Cartesian target | Yes | No | You know where the end effector needs to go and want the planner to choose the path. | -| [Move with constraints](/motion-planning/move-an-arm/move-with-constraints/) | Cartesian target + rules | Yes | Yes | The shape of the motion matters (straight-line tool path, level end effector). | -| [Move by joint positions](/motion-planning/move-an-arm/move-by-joint-positions/) | Joint angles | No | Direct | You know the joint angles, need predictable motion between known configurations, or want to avoid the planner picking an unexpected IK solution. | +| Pattern | Input | Obstacle avoidance | Path-shape control | When to pick | +| -------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| [Move to a pose](/motion-planning/move-an-arm/move-to-pose/) | Cartesian target | Yes | No | You know where the end effector needs to go and want the planner to choose the path. | +| [Move with constraints](/motion-planning/move-an-arm/move-with-constraints/) | Cartesian target + rules | Yes | Yes | The shape of the motion matters (straight-line tool path, level end effector). | +| [Relax a goal with a pose cloud](/motion-planning/move-an-arm/pose-clouds/) | Cartesian target + tolerances | Yes | No | Any pose in a region will do, or an exact goal plans slowly or keeps failing. | +| [Move through waypoints](/motion-planning/move-an-arm/multiple-waypoints/) | Ordered list of Cartesian and/or joint goals | Yes | Partial | The arm must pass through specific intermediate goals in one continuous trajectory. | +| [Move by joint positions](/motion-planning/move-an-arm/move-by-joint-positions/) | Joint angles | No | Direct | You know the joint angles, need predictable motion between known configurations, or want to avoid the planner picking an unexpected IK solution. | +| [Stream joint positions](/motion-planning/move-an-arm/stream-joint-positions/) | Joint waypoints with times, sent while the arm moves | No | Direct | You are computing the trajectory as the motion runs, from a teleoperation feed or a control loop. | +| [Arm-level Cartesian move](/reference/apis/components/arm/#movetoposition) | Cartesian target | No | No | You have a pose and deliberately want the arm's own IK, with no planner, frame system, or obstacle checking. | For the four constraint types the planner enforces, see [Configure motion constraints](/motion-planning/move-an-arm/constraints/). @@ -59,6 +63,7 @@ from the table above. {{% card link="/motion-planning/move-an-arm/move-with-constraints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/constraints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/move-by-joint-positions/" noimage="true" %}} +{{% card link="/motion-planning/move-an-arm/stream-joint-positions/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/multiple-waypoints/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/pose-clouds/" noimage="true" %}} {{% card link="/motion-planning/move-an-arm/pick-an-object/" noimage="true" %}} diff --git a/docs/motion-planning/move-an-arm/stream-joint-positions.md b/docs/motion-planning/move-an-arm/stream-joint-positions.md new file mode 100644 index 0000000000..ea57dd7e5d --- /dev/null +++ b/docs/motion-planning/move-an-arm/stream-joint-positions.md @@ -0,0 +1,244 @@ +--- +linkTitle: "Stream joint positions" +title: "Stream joint positions to an arm" +weight: 35 +layout: "docs" +type: "docs" +description: "Push joint trajectory points to an arm while it is already moving, using MoveThroughJointPositionsStreamed." +capabilities: ["motion-planning", "hw-arm"] +--- + +`MoveThroughJointPositionsStreamed` opens a stream onto which you push joint +trajectory points while the arm is already moving: the arm executes the points +it has while you keep appending. The +[other joint-space methods](/motion-planning/move-an-arm/move-by-joint-positions/) +take the whole motion up front, the configurations to hit and optional +ceilings on how fast to get there. The arm module picks the motion profile from +there. + +Streaming hands the arm a time-parameterized trajectory: be at this +configuration at this time, moving at this velocity. You produce that schedule. + +Reach for streaming when the trajectory is produced as the motion runs: a +teleoperation feed, a visual-servoing loop, a force-feedback correction on a +surface-finishing path, or a trajectory long enough that you want to keep only +part of it in memory. + +{{< alert title="Joint-space moves bypass the motion planner" color="caution" >}} +You are responsible for obstacle avoidance, constraint satisfaction, and path +smoothing. A trajectory point that puts the arm through the table or your +workspace fixture executes as sent. Everything you stream must already be safe. +{{< /alert >}} + +`MoveThroughJointPositionsStreamed` is safety-heartbeat monitored: if the +session that last called it stops sending heartbeats, `viam-server` stops the +arm. A client that dies mid-trajectory leaves the arm stopped. + +## Prerequisites + +- A configured arm component and an SDK client. +- An arm module that implements streaming. Streaming is currently supported for the [`viam:ufactory`](https://app.viam.com/module/viam/ufactory) and [`viam:universal-robots`](https://app.viam.com/module/viam/universal-robots) modules. +- A source of trajectory points that stays within the arm's joint limits. + +## What a trajectory point contains + +A trajectory point carries the following: + +- **Time**, measured from the start of the motion. The first point must be + zero, and every point after it must be strictly later than the one before. +- **Positions**, one value per joint, matching the arm's degrees of freedom. +- **Constraints**, optional and set per point. A point either carries no constraints at all or carries a velocity for every joint, with accelerations an optional addition on top. The arm starts from rest, so the first point is the arm standing still: either leave constraints off that point, or give every joint a velocity of zero. Only velocities have to be zero there; an acceleration on the first point is allowed. + +Each SDK uses different units for positions and constraints. For reference, see [Units](/motion-planning/move-an-arm/move-by-joint-positions/#units-python-uses-degrees-go-uses-radians). + +## How batching works + +A batch is a request of one or more trajectory points. Point times are offsets +from the start of the motion, so the same trajectory sent whole and sent in +three batches produces the same motion. Batching controls delivery; the point +times control the arm. + +Batch size controls how far ahead of the arm you commit. A point you +have sent is final: points cannot be replaced or revoked. Large batches cost +fewer round trips and leave more of the trajectory queued if your producer +falls behind, at the price of a longer committed stretch. Small batches keep +the last committed point close to where the arm is now, so a fresh sensor +reading can still change the next move. + +## Stream a trajectory + +Each SDK exposes the same stream through a different control flow. Python takes +an async iterator and gives you one back. Go hands you two channels that you +own. + +{{< alert title="SDK availability" color="caution" >}} +`MoveThroughJointPositionsStreamed` is available in the **Python, Go, and C++ +SDKs**. The TypeScript SDK does not expose it yet. +{{< /alert >}} + +{{< tabs >}} +{{% tab name="Python" %}} + +`move_through_joint_positions_streamed` takes an async iterator of batches and +returns an async iterator of `Arm.TrajectoryUpdate` values. Iterate the result +to read updates as the arm works through the trajectory. Each list you yield +becomes one `TrajectoryBatch` request. + +```python +from datetime import timedelta + +from viam.components.arm import Arm + +my_arm = Arm.from_robot(machine, "my-arm") + +# Times are offsets from the start of the motion; positions are degrees. +first_batch = [ + Arm.TrajectoryPoint( + time=timedelta(0), + positions=[0, -45, 90, 0, 45, 0], + # Velocities on the t=0 point must be zero. + constraints=Arm.KinematicConstraints(velocities=[0, 0, 0, 0, 0, 0]), + ), + Arm.TrajectoryPoint( + time=timedelta(milliseconds=500), + positions=[0, -22.5, 90, 0, 22.5, 0], + ), + Arm.TrajectoryPoint( + time=timedelta(seconds=1), + positions=[0, 0, 90, 0, 0, 0], + ), +] + + +async def batches(): + # Yield a list per batch. Returning ends the trajectory. + yield first_batch + + +async for update in my_arm.move_through_joint_positions_streamed(batches()): + # Updates arrive as the arm executes. Stopping this loop early closes + # the stream. + pass +``` + +{{% /tab %}} +{{% tab name="Go" %}} + +You create both channels. Write batches to `batches` and close it to end the +motion. Drain `responses` for the life of the call, because the client blocks +while it waits to hand one over, and close it after the call returns. + +```go +import ( + "math" + "time" + + "go.viam.com/rdk/components/arm" + "go.viam.com/rdk/referenceframe" +) + +batches := make(chan []arm.TrajectoryPoint) +responses := make(chan arm.Response) + +// The arm is free to acknowledge nothing at all, so this goroutine drains the +// channel rather than tracking progress. +go func() { + for range responses { + } +}() + +go func() { + defer close(batches) + + // Give up if the call returns early, so this goroutine never blocks on a + // channel nobody is reading. + send := func(b []arm.TrajectoryPoint) bool { + select { + case batches <- b: + return true + case <-ctx.Done(): + return false + } + } + + // Ten waypoints, 100ms apart, sent five at a time. + batch := make([]arm.TrajectoryPoint, 0, 5) + for i := 0; i < 10; i++ { + batch = append(batch, arm.TrajectoryPoint{ + Time: time.Duration(i*100) * time.Millisecond, + // Revolute joint values are radians, matching referenceframe.Input. + Positions: []referenceframe.Input{ + 0, -math.Pi/4 + float64(i)*math.Pi/40, math.Pi / 2, 0, math.Pi / 4, 0, + }, + }) + if len(batch) == 5 { + if !send(batch) { + return + } + batch = make([]arm.TrajectoryPoint, 0, 5) + } + } + if len(batch) > 0 { + send(batch) + } +}() + +// Blocks until the arm finishes the trajectory, the stream fails, or another +// operation cancels it. +err := myArm.MoveThroughJointPositionsStreamed(ctx, batches, responses, nil) +close(responses) +if err != nil { + logger.Fatal(err) +} +``` + +{{% /tab %}} +{{< /tabs >}} + +## Troubleshooting + +{{< expand "Streamed trajectory behaves oddly across a batch boundary" >}} + +Times are offsets from the start of the whole motion, not from the start of the +batch they arrive in. The first point of the stream must be at time zero and +every later point must be strictly greater than the one before it, across batch +boundaries as well as within a batch. Restarting the clock at each batch sends +the arm a trajectory that goes backwards in time. + +`viam-server` leaves enforcement to the arm module, so what a violation looks +like depends on the module: an error, a refused batch, or unintended motion. + +{{< /expand >}} + +{{< expand "Streamed trajectory fails with a joint range error" >}} + +The error names the joint index and the range it violated, not which trajectory +point carried it: `joint 1 needs to be within range [-360, 360] and cannot be +moved to 400`. Check the whole trajectory against the joint limits before you +start streaming to find the point at fault. + +The Go client raises this one as it encodes each point, the same check the +unary path makes, advancing through the trajectory point by point. Earlier +batches are already in flight by the time a bad point appears, so a rejected +point tears the whole stream down instead of returning an error for that +point alone. If the arm's kinematics are unregistered, the client logs a warning +and skips the check. + +{{< /expand >}} + +{{< expand "Wrong number of values error" >}} + +The `positions` array must match the arm's degrees of freedom. A 6-DOF +arm expects six values, a 7-DOF arm expects seven. Check the arm +module's documentation or the kinematics file. + +{{< /expand >}} + +## What's next + +- [Move by joint positions](/motion-planning/move-an-arm/move-by-joint-positions/): + the unary methods, for trajectories you have in hand before the arm moves. +- [Move an arm to a pose](/motion-planning/move-an-arm/move-to-pose/): + Cartesian motion with obstacle avoidance through `motion.Move`. +- [Arm kinematics](/motion-planning/reference/kinematics/): the + kinematic file that declares joint limits. diff --git a/static/include/components/apis/generated/arm-table.md b/static/include/components/apis/generated/arm-table.md index 8975707249..ee74129d4d 100644 --- a/static/include/components/apis/generated/arm-table.md +++ b/static/include/components/apis/generated/arm-table.md @@ -5,6 +5,7 @@ | [`MoveToPosition`](/reference/apis/components/arm/#movetoposition) | Move the end of the arm in a straight line to the desired pose, relative to the base of the arm. | | [`MoveToJointPositions`](/reference/apis/components/arm/#movetojointpositions) | Move each joint on the arm to the position specified in `positions`. | | [`MoveThroughJointPositions`](/reference/apis/components/arm/#movethroughjointpositions) | Move the arm's joints through the given positions in the order they are specified. | +| [`MoveThroughJointPositionsStreamed`](/reference/apis/components/arm/#movethroughjointpositionsstreamed) | Stream batches of timed trajectory points to the arm and execute them in order as they arrive. | | [`GetJointPositions`](/reference/apis/components/arm/#getjointpositions) | Get the current position of each joint on the arm. | | [`Get3DModels`](/reference/apis/components/arm/#get3dmodels) | Get the 3D models of the arm. | | [`CalculateMaxReach`](/reference/apis/components/arm/#calculatemaxreach) | Calculate the maximum reach of the arm by summing all link lengths from its kinematics data. | diff --git a/static/include/components/apis/generated/arm.md b/static/include/components/apis/generated/arm.md index 8f3d2eb425..22a1c16cfb 100644 --- a/static/include/components/apis/generated/arm.md +++ b/static/include/components/apis/generated/arm.md @@ -376,6 +376,61 @@ For more information, see the [Go SDK Docs](https://pkg.go.dev/go.viam.com/rdk/c {{% /tab %}} {{< /tabs >}} +### MoveThroughJointPositionsStreamed + +Stream batches of timed trajectory points to the arm and execute them in order as they arrive. +Unlike `MoveThroughJointPositions`, the full trajectory does not have to be known before the motion starts: the caller keeps appending points while the arm executes the ones it already has. + +{{< tabs >}} +{{% tab name="Python" %}} + +**Parameters:** + +- `batches` ([AsyncIterator[List[viam.components.arm.Arm.TrajectoryPoint]]](https://python.viam.dev/autoapi/viam/components/arm/index.html#viam.components.arm.Arm.TrajectoryPoint)) (required): an asynchronous iterator of lists of TrajectoryPoint. Each list becomes one wire TrajectoryBatch. +- `extra` (Mapping[[str](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str), Any]) (optional): Extra options to pass to the underlying RPC call. +- `timeout` ([float](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex)) (optional): An option to set how long to wait (in seconds) before calling a time-out and closing the underlying RPC call. + +**Returns:** + +- ([AsyncIterator[viam.components.arm.Arm.TrajectoryUpdate]](https://python.viam.dev/autoapi/viam/components/arm/index.html#viam.components.arm.Arm.TrajectoryUpdate)): : the arm’s updates, yielded as they arrive. + +**Example:** + +```python {class="line-numbers linkable-line-numbers"} +my_arm = Arm.from_robot(robot=machine, name="my_arm") + +async def batches(): + yield [ + Arm.TrajectoryPoint(time=timedelta(seconds=0.0), positions=[0.0, 0.0, 0.0, 0.0, 0.0]), + Arm.TrajectoryPoint(time=timedelta(seconds=1.0), positions=[10.0, 0.0, 0.0, 0.0, 0.0]), + ] + +async for update in my_arm.move_through_joint_positions_streamed(batches()): + # Observe the arm's updates; a fault raises out of this iteration. + pass +``` + +For more information, see the [Python SDK Docs](https://python.viam.dev/autoapi/viam/components/arm/client/index.html#viam.components.arm.client.ArmClient.move_through_joint_positions_streamed). + +{{% /tab %}} +{{% tab name="Go" %}} + +**Parameters:** + +- `ctx` [(Context)](https://pkg.go.dev/context#Context): A Context carries a deadline, a cancellation signal, and other values across API boundaries. +- `batches` [(<-chan []TrajectoryPoint)](https://pkg.go.dev/go.viam.com/rdk/components/arm#TrajectoryPoint): The channel the caller sends trajectory batches on. Each send is one batch of `TrajectoryPoint` values, appended to the motion in order. Close the channel to signal that no more points are coming. +- `responses` [(chan<- Response)](https://pkg.go.dev/go.viam.com/rdk/components/arm#Response): The channel acknowledgments arrive on while the arm executes. `Response` carries no fields today. The caller must drain this channel for the duration of the call, and closes it only after the call returns. +- `extra` [(map[string]interface{})](https://go.dev/blog/maps): Extra options to pass to the underlying RPC call. + +**Returns:** + +- [(error)](https://pkg.go.dev/builtin#error): An error, if one occurred. + +For more information, see the [Go SDK Docs](https://pkg.go.dev/go.viam.com/rdk/components/arm#Arm). + +{{% /tab %}} +{{< /tabs >}} + ### GetJointPositions Get the current position of each joint on the arm. diff --git a/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.batches.md b/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.batches.md new file mode 100644 index 0000000000..9ff312bfbc --- /dev/null +++ b/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.batches.md @@ -0,0 +1 @@ +The channel the caller sends trajectory batches on. Each send is one batch of `TrajectoryPoint` values, appended to the motion in order. Close the channel to signal that no more points are coming. diff --git a/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.responses.md b/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.responses.md new file mode 100644 index 0000000000..afb4746122 --- /dev/null +++ b/static/include/components/apis/overrides/methods/go.arm.MoveThroughJointPositionsStreamed.responses.md @@ -0,0 +1 @@ +The channel acknowledgments arrive on while the arm executes. `Response` carries no fields today. The caller must drain this channel for the duration of the call, and closes it only after the call returns. diff --git a/static/include/components/apis/overrides/protos/arm.MoveThroughJointPositionsStreamed.md b/static/include/components/apis/overrides/protos/arm.MoveThroughJointPositionsStreamed.md new file mode 100644 index 0000000000..6d4f502959 --- /dev/null +++ b/static/include/components/apis/overrides/protos/arm.MoveThroughJointPositionsStreamed.md @@ -0,0 +1,2 @@ +Stream batches of timed trajectory points to the arm and execute them in order as they arrive. +Unlike `MoveThroughJointPositions`, the full trajectory does not have to be known before the motion starts: the caller keeps appending points while the arm executes the ones it already has.