Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSDK-4120 add MoveThroughJointPositions to Arm interface #4508

Merged
merged 16 commits into from
Nov 5, 2024
43 changes: 20 additions & 23 deletions components/arm/arm.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,26 @@ func Named(name string) resource.Name {
//
// MoveToJointPositions example:
//
// // Assumes you have imported "go.viam.com/api/component/arm/v1" as `componentpb`
// myArm, err := arm.FromRobot(machine, "my_arm")
//
// // Declare an array of values with your desired rotational value for each joint on the arm.
// degrees := []float64{4.0, 5.0, 6.0}
// // Declare an array of values with your desired rotational value (in radians) for each joint on the arm.
// inputs := referenceframe.FloatsToInputs([]float64{0, math.Pi/2, math.Pi})
//
// // Declare a new JointPositions with these values.
// jointPos := &componentpb.JointPositions{Values: degrees}
// // Move each joint of the arm to the positions specified in the above slice
// err = myArm.MoveToJointPositions(context.Background(), inputs, nil)
//
// // Move each joint of the arm to the position these values specify.
// err = myArm.MoveToJointPositions(context.Background(), jointPos, nil)
// MoveToJointPositions example:
//
// myArm, err := arm.FromRobot(machine, "my_arm")
//
// // Declare a 2D array of values with your desired rotational value (in radians) for each joint on the arm.
// inputs := [][]referenceframe.Input{
// referenceframe.FloatsToInputs([]float64{0, math.Pi/2, math.Pi})
// referenceframe.FloatsToInputs([]float64{0, 0, 0})
// }
//
// // Move each joint of the arm through the positions in the slice defined above
// err = myArm.MoveThroughJointPositions(context.Background(), inputs, nil, nil)
//
// JointPositions example:
//
Expand Down Expand Up @@ -113,6 +122,10 @@ type Arm interface {
// This will block until done or a new operation cancels this one.
MoveToJointPositions(ctx context.Context, positions []referenceframe.Input, extra map[string]interface{}) error

// MoveThroughJointPositions moves the arm's joints through the given positions in the order they are specified.
// This will block until done or a new operation cancels this one.
MoveThroughJointPositions(ctx context.Context, positions [][]referenceframe.Input, options *MoveOptions, extra map[string]any) error

// JointPositions returns the current joint positions of the arm.
JointPositions(ctx context.Context, extra map[string]interface{}) ([]referenceframe.Input, error)
}
Expand Down Expand Up @@ -159,22 +172,6 @@ func CreateStatus(ctx context.Context, a Arm) (*pb.Status, error) {
return &pb.Status{EndPosition: endPosition, JointPositions: jointPositions, IsMoving: isMoving}, nil
}

// GoToWaypoints will visit in turn each of the joint position waypoints generated by a motion planner.
func GoToWaypoints(ctx context.Context, a Arm, waypoints [][]referenceframe.Input) error {
for _, waypoint := range waypoints {
err := ctx.Err() // make sure we haven't been cancelled
if err != nil {
return err
}

err = a.GoToInputs(ctx, waypoint)
if err != nil {
return err
}
}
return nil
}

// CheckDesiredJointPositions validates that the desired joint positions either bring the joint back
// in bounds or do not move the joint more out of bounds.
func CheckDesiredJointPositions(ctx context.Context, a Arm, desiredInputs []referenceframe.Input) error {
Expand Down
44 changes: 34 additions & 10 deletions components/arm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ func (c *client) MoveToJointPositions(ctx context.Context, positions []reference
if err != nil {
return err
}
if positions == nil {
c.logger.Warnf("%s MoveToJointPositions: position parameter is nil", c.name)
}
jp, err := referenceframe.JointPositionsFromInputs(c.model, positions)
if err != nil {
return err
Expand All @@ -109,6 +106,39 @@ func (c *client) MoveToJointPositions(ctx context.Context, positions []reference
return err
}

func (c *client) MoveThroughJointPositions(
ctx context.Context,
positions [][]referenceframe.Input,
options *MoveOptions,
extra map[string]interface{},
) error {
ext, err := protoutils.StructToStructPb(extra)
if err != nil {
return err
}
if positions == nil {
c.logger.Warnf("%s MoveThroughJointPositions: position argument is nil", c.name)
}
allJPs := make([]*pb.JointPositions, 0, len(positions))
for _, position := range positions {
jp, err := referenceframe.JointPositionsFromInputs(c.model, position)
if err != nil {
return err
}
allJPs = append(allJPs, jp)
}
req := &pb.MoveThroughJointPositionsRequest{
Name: c.name,
Positions: allJPs,
Extra: ext,
}
if options != nil {
req.Options = options.toProtobuf()
}
_, err = c.client.MoveThroughJointPositions(ctx, req)
return err
}

func (c *client) JointPositions(ctx context.Context, extra map[string]interface{}) ([]referenceframe.Input, error) {
ext, err := protoutils.StructToStructPb(extra)
if err != nil {
Expand Down Expand Up @@ -145,13 +175,7 @@ func (c *client) CurrentInputs(ctx context.Context) ([]referenceframe.Input, err
}

func (c *client) GoToInputs(ctx context.Context, inputSteps ...[]referenceframe.Input) error {
// TODO: switch this over call MoveThroughJointPositions when that API is ready
for _, goal := range inputSteps {
if err := c.MoveToJointPositions(ctx, goal, nil); err != nil {
return err
}
}
return nil
return c.MoveThroughJointPositions(ctx, inputSteps, nil, nil)
}

func (c *client) DoCommand(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error) {
Expand Down
35 changes: 28 additions & 7 deletions components/arm/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ func TestClient(t *testing.T) {
var (
capArmPos spatialmath.Pose
capArmJointPos []referenceframe.Input
moveOptions arm.MoveOptions
extraOptions map[string]interface{}
)

pos1 := spatialmath.NewPoseFromPoint(r3.Vector{X: 1, Y: 2, Z: 3})
jointPos1 := []referenceframe.Input{{1.}, {2.}, {3.}}
expectedGeometries := []spatialmath.Geometry{spatialmath.NewPoint(r3.Vector{1, 2, 3}, "")}
expectedMoveOptions := arm.MoveOptions{MaxVelRads: 1, MaxAccRads: 2}
injectArm := &inject.Arm{}
injectArm.EndPositionFunc = func(ctx context.Context, extra map[string]interface{}) (spatialmath.Pose, error) {
extraOptions = extra
Expand All @@ -52,12 +54,22 @@ func TestClient(t *testing.T) {
extraOptions = extra
return nil
}

injectArm.MoveToJointPositionsFunc = func(ctx context.Context, jp []referenceframe.Input, extra map[string]interface{}) error {
capArmJointPos = jp
extraOptions = extra
return nil
}
injectArm.MoveThroughJointPositionsFunc = func(
ctx context.Context,
positions [][]referenceframe.Input,
options *arm.MoveOptions,
extra map[string]interface{},
) error {
capArmJointPos = positions[len(positions)-1]
moveOptions = *options
extraOptions = extra
return nil
}
injectArm.StopFunc = func(ctx context.Context, extra map[string]interface{}) error {
extraOptions = extra
return errStopUnimplemented
Expand All @@ -82,7 +94,6 @@ func TestClient(t *testing.T) {
capArmPos = ap
return nil
}

injectArm2.MoveToJointPositionsFunc = func(ctx context.Context, jp []referenceframe.Input, extra map[string]interface{}) error {
capArmJointPos = jp
return nil
Expand All @@ -94,11 +105,10 @@ func TestClient(t *testing.T) {
return nil
}

armSvc, err := resource.NewAPIResourceCollection(
arm.API, map[resource.Name]arm.Arm{
arm.Named(testArmName): injectArm,
arm.Named(testArmName2): injectArm2,
})
armSvc, err := resource.NewAPIResourceCollection(arm.API, map[resource.Name]arm.Arm{
arm.Named(testArmName): injectArm,
arm.Named(testArmName2): injectArm2,
})
test.That(t, err, test.ShouldBeNil)
resourceAPI, ok, err := resource.LookupAPIRegistration[arm.Arm](arm.API)
test.That(t, err, test.ShouldBeNil)
Expand Down Expand Up @@ -164,6 +174,17 @@ func TestClient(t *testing.T) {
test.That(t, capArmJointPos, test.ShouldResemble, jointPos2)
test.That(t, extraOptions, test.ShouldResemble, map[string]interface{}{"foo": "MoveToJointPositions"})

err = arm1Client.MoveThroughJointPositions(
context.Background(),
[][]referenceframe.Input{jointPos2, jointPos1},
&expectedMoveOptions,
map[string]interface{}{"foo": "MoveThroughJointPositions"},
)
test.That(t, err, test.ShouldBeNil)
test.That(t, capArmJointPos, test.ShouldResemble, jointPos1)
test.That(t, moveOptions, test.ShouldResemble, expectedMoveOptions)
test.That(t, extraOptions, test.ShouldResemble, map[string]interface{}{"foo": "MoveThroughJointPositions"})

err = arm1Client.Stop(context.Background(), map[string]interface{}{"foo": "Stop"})
test.That(t, err, test.ShouldNotBeNil)
test.That(t, err.Error(), test.ShouldContainSubstring, errStopUnimplemented.Error())
Expand Down
29 changes: 19 additions & 10 deletions components/arm/eva/eva.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,24 @@ func (e *eva) MoveToJointPositions(ctx context.Context, newPositions []reference
return e.doMoveJoints(ctx, radians)
}

func (e *eva) MoveThroughJointPositions(
ctx context.Context,
positions [][]referenceframe.Input,
_ *arm.MoveOptions,
_ map[string]interface{},
) error {
for _, goal := range positions {
if err := arm.CheckDesiredJointPositions(ctx, e, goal); err != nil {
return err
}
err := e.MoveToJointPositions(ctx, goal, nil)
if err != nil {
return err
}
}
return nil
}

func (e *eva) doMoveJoints(ctx context.Context, joints []float64) error {
if err := e.apiLock(ctx); err != nil {
return err
Expand Down Expand Up @@ -384,16 +402,7 @@ func (e *eva) CurrentInputs(ctx context.Context) ([]referenceframe.Input, error)
}

func (e *eva) GoToInputs(ctx context.Context, inputSteps ...[]referenceframe.Input) error {
for _, goal := range inputSteps {
if err := arm.CheckDesiredJointPositions(ctx, e, goal); err != nil {
return err
}
err := e.MoveToJointPositions(ctx, goal, nil)
if err != nil {
return err
}
}
return nil
return e.MoveThroughJointPositions(ctx, inputSteps, nil, nil)
}

func (e *eva) Close(ctx context.Context) error {
Expand Down
22 changes: 16 additions & 6 deletions components/arm/fake/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ func (a *Arm) MoveToJointPositions(ctx context.Context, joints []referenceframe.
return nil
}

// MoveThroughJointPositions moves the fake arm through the given inputs.
func (a *Arm) MoveThroughJointPositions(
ctx context.Context,
positions [][]referenceframe.Input,
_ *arm.MoveOptions,
_ map[string]interface{},
) error {
for _, goal := range positions {
if err := a.MoveToJointPositions(ctx, goal, nil); err != nil {
return err
}
}
return nil
}

// JointPositions returns joints.
func (a *Arm) JointPositions(ctx context.Context, extra map[string]interface{}) ([]referenceframe.Input, error) {
a.mu.RLock()
Expand All @@ -214,12 +229,7 @@ func (a *Arm) CurrentInputs(ctx context.Context) ([]referenceframe.Input, error)

// GoToInputs moves the fake arm to the given inputs.
func (a *Arm) GoToInputs(ctx context.Context, inputSteps ...[]referenceframe.Input) error {
for _, goal := range inputSteps {
if err := a.MoveToJointPositions(ctx, goal, nil); err != nil {
return err
}
}
return nil
return a.MoveThroughJointPositions(ctx, inputSteps, nil, nil)
}

// Close does nothing.
Expand Down
34 changes: 34 additions & 0 deletions components/arm/pb_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,45 @@ import (
"fmt"

commonpb "go.viam.com/api/common/v1"
pb "go.viam.com/api/component/arm/v1"

"go.viam.com/rdk/referenceframe"
"go.viam.com/rdk/referenceframe/urdf"
"go.viam.com/rdk/utils"
)

// MoveOptions define parameters to be obeyed during arm movement.
type MoveOptions struct {
Copy link
Member

@nfranczak nfranczak Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might make more sense for this struct to live in arm.go? What are your thoughts?
Having it live in pb_helpers.go kind of obscures its presence/makes it harder to find

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its pretty easy to find and the struct is simple enough that it kind of feels like a code artifact that is a helper. Going to leave this as is for now and mentally flag this as something to change down the line as we change arms more

MaxVelRads, MaxAccRads float64
}

func moveOptionsFromProtobuf(protobuf *pb.MoveOptions) *MoveOptions {
if protobuf == nil {
protobuf = &pb.MoveOptions{}
}

var vel, acc float64
if protobuf.MaxVelDegsPerSec != nil {
vel = *protobuf.MaxVelDegsPerSec
}
if protobuf.MaxAccDegsPerSec2 != nil {
acc = *protobuf.MaxAccDegsPerSec2
}
return &MoveOptions{
MaxVelRads: utils.DegToRad(vel),
MaxAccRads: utils.DegToRad(acc),
}
}

func (opts *MoveOptions) toProtobuf() *pb.MoveOptions {
vel := utils.RadToDeg(opts.MaxVelRads)
acc := utils.RadToDeg(opts.MaxAccRads)
return &pb.MoveOptions{
MaxVelDegsPerSec: &vel,
MaxAccDegsPerSec2: &acc,
}
}

func parseKinematicsResponse(name string, resp *commonpb.GetKinematicsResponse) (referenceframe.Model, error) {
format := resp.GetFormat()
data := resp.GetKinematicsData()
Expand Down
22 changes: 22 additions & 0 deletions components/arm/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ func (s *serviceServer) MoveToJointPositions(
return &pb.MoveToJointPositionsResponse{}, arm.MoveToJointPositions(ctx, inputs, req.Extra.AsMap())
}

// MoveThroughJointPositions moves an arm of the underlying robot through the requested joint positions.
func (s *serviceServer) MoveThroughJointPositions(
ctx context.Context,
req *pb.MoveThroughJointPositionsRequest,
) (*pb.MoveThroughJointPositionsResponse, error) {
operation.CancelOtherWithLabel(ctx, req.Name)
arm, err := s.coll.Resource(req.Name)
if err != nil {
return nil, err
}
allInputs := make([][]referenceframe.Input, 0, len(req.Positions))
for _, position := range req.Positions {
inputs, err := referenceframe.InputsFromJointPositions(arm.ModelFrame(), position)
if err != nil {
return nil, err
}
allInputs = append(allInputs, inputs)
}
err = arm.MoveThroughJointPositions(ctx, allInputs, moveOptionsFromProtobuf(req.Options), req.Extra.AsMap())
return &pb.MoveThroughJointPositionsResponse{}, err
}

// Stop stops the arm specified.
func (s *serviceServer) Stop(ctx context.Context, req *pb.StopRequest) (*pb.StopResponse, error) {
operation.CancelOtherWithLabel(ctx, req.Name)
Expand Down
Loading
Loading