Skip to content

Commit f2355d1

Browse files
authored
RSDK-5742 return raw kinematics file from arm server (viamrobotics#3240)
1 parent a528667 commit f2355d1

File tree

10 files changed

+110
-63
lines changed

10 files changed

+110
-63
lines changed

components/arm/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (c *client) updateKinematics(ctx context.Context, extra map[string]interfac
195195
case commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_SVA:
196196
return referenceframe.UnmarshalModelJSON(data, c.name)
197197
case commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_URDF:
198-
modelconf, err := urdf.ConvertURDFToConfig(data, c.name)
198+
modelconf, err := urdf.UnmarshalModelXML(data, c.name)
199199
if err != nil {
200200
return nil, err
201201
}

components/arm/fake/fake.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func modelFromName(model, name string) (referenceframe.Model, error) {
233233
func modelFromPath(modelPath, name string) (referenceframe.Model, error) {
234234
switch {
235235
case strings.HasSuffix(modelPath, ".urdf"):
236-
return urdf.ParseXMLFile(modelPath, name)
236+
return urdf.ParseModelXMLFile(modelPath, name)
237237
case strings.HasSuffix(modelPath, ".json"):
238238
return referenceframe.ParseModelJSONFile(modelPath, name)
239239
default:

components/arm/server.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"go.viam.com/rdk/operation"
1313
"go.viam.com/rdk/protoutils"
14+
"go.viam.com/rdk/referenceframe/urdf"
1415
"go.viam.com/rdk/resource"
1516
"go.viam.com/rdk/spatialmath"
1617
)
@@ -132,13 +133,20 @@ func (s *serviceServer) GetKinematics(ctx context.Context, req *commonpb.GetKine
132133
if model == nil {
133134
return &commonpb.GetKinematicsResponse{Format: commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_UNSPECIFIED}, nil
134135
}
135-
filedata, err := model.MarshalJSON()
136-
if err != nil {
137-
return nil, err
136+
cfg := model.ModelConfig()
137+
if cfg == nil || cfg.OriginalFile == nil {
138+
return &commonpb.GetKinematicsResponse{Format: commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_UNSPECIFIED}, nil
139+
}
140+
resp := &commonpb.GetKinematicsResponse{KinematicsData: cfg.OriginalFile.Bytes}
141+
switch cfg.OriginalFile.Extension {
142+
case "json":
143+
resp.Format = commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_SVA
144+
case urdf.Extension:
145+
resp.Format = commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_URDF
146+
default:
147+
resp.Format = commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_URDF
138148
}
139-
// Marshalled models always marshal to SVA
140-
format := commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_SVA
141-
return &commonpb.GetKinematicsResponse{Format: format, KinematicsData: filedata}, nil
149+
return resp, nil
142150
}
143151

144152
// DoCommand receives arbitrary commands.

components/arm/server_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ import (
1212
"go.viam.com/utils/protoutils"
1313

1414
"go.viam.com/rdk/components/arm"
15+
"go.viam.com/rdk/referenceframe"
16+
"go.viam.com/rdk/referenceframe/urdf"
1517
"go.viam.com/rdk/resource"
1618
"go.viam.com/rdk/spatialmath"
1719
"go.viam.com/rdk/testutils/inject"
20+
"go.viam.com/rdk/utils"
1821
)
1922

2023
var (
@@ -71,6 +74,13 @@ func TestServer(t *testing.T) {
7174
extraOptions = extra
7275
return nil
7376
}
77+
injectArm.ModelFrameFunc = func() referenceframe.Model {
78+
model, err := urdf.ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_viam.urdf"), "foo")
79+
if err != nil {
80+
return nil
81+
}
82+
return model
83+
}
7484
injectArm.StopFunc = func(ctx context.Context, extra map[string]interface{}) error {
7585
extraOptions = extra
7686
return nil
@@ -93,6 +103,9 @@ func TestServer(t *testing.T) {
93103
capArmJointPos = jp
94104
return errMoveToJointPositionFailed
95105
}
106+
injectArm2.ModelFrameFunc = func() referenceframe.Model {
107+
return nil
108+
}
96109
injectArm2.StopFunc = func(ctx context.Context, extra map[string]interface{}) error {
97110
return errStopUnimplemented
98111
}
@@ -180,6 +193,20 @@ func TestServer(t *testing.T) {
180193
test.That(t, capArmJointPos.String(), test.ShouldResemble, positionDegs1.String())
181194
})
182195

196+
t.Run("get kinematics", func(t *testing.T) {
197+
_, err = armServer.GetKinematics(context.Background(), &commonpb.GetKinematicsRequest{Name: missingArmName})
198+
test.That(t, err, test.ShouldNotBeNil)
199+
test.That(t, err.Error(), test.ShouldContainSubstring, errArmUnimplemented.Error())
200+
201+
kinematics, err := armServer.GetKinematics(context.Background(), &commonpb.GetKinematicsRequest{Name: testArmName})
202+
test.That(t, err, test.ShouldBeNil)
203+
test.That(t, kinematics.Format, test.ShouldResemble, commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_URDF)
204+
205+
kinematics, err = armServer.GetKinematics(context.Background(), &commonpb.GetKinematicsRequest{Name: failArmName})
206+
test.That(t, err, test.ShouldBeNil)
207+
test.That(t, kinematics.Format, test.ShouldResemble, commonpb.KinematicsFileFormat_KINEMATICS_FILE_FORMAT_UNSPECIFIED)
208+
})
209+
183210
t.Run("stop", func(t *testing.T) {
184211
_, err = armServer.Stop(context.Background(), &pb.StopRequest{Name: missingArmName})
185212
test.That(t, err, test.ShouldNotBeNil)

components/arm/wrapper/wrapper.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (wrapper *Arm) Geometries(ctx context.Context, extra map[string]interface{}
204204
func modelFromPath(modelPath, name string) (referenceframe.Model, error) {
205205
switch {
206206
case strings.HasSuffix(modelPath, ".urdf"):
207-
return urdf.ParseXMLFile(modelPath, name)
207+
return urdf.ParseModelXMLFile(modelPath, name)
208208
case strings.HasSuffix(modelPath, ".json"):
209209
return referenceframe.ParseModelJSONFile(modelPath, name)
210210
default:

module/modmanager/manager_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -803,8 +803,9 @@ func TestModuleMisc(t *testing.T) {
803803
test.That(t, resp, test.ShouldNotBeNil)
804804
modWorkingDirectory, ok := resp["path"].(string)
805805
test.That(t, ok, test.ShouldBeTrue)
806-
test.That(t, modWorkingDirectory, test.ShouldEqual, filepath.Dir(modPath))
807-
806+
// MacOS prepends "/private/" to the filepath so we check the end of the path to account for this
807+
// i.e. '/private/var/folders/p1/nl3sq7jn5nx8tfkdwpz2_g7r0000gn/T/TestModuleMisc1764175663/002'
808+
test.That(t, modWorkingDirectory, test.ShouldEndWith, filepath.Dir(modPath))
808809
err = mgr.Close(ctx)
809810
test.That(t, err, test.ShouldBeNil)
810811
})

motionplan/kinematic_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func TestKinematicsJSONvsURDF(t *testing.T) {
303303

304304
mJSON, err := frame.ParseModelJSONFile(utils.ResolveFile("components/arm/universalrobots/ur5e.json"), "")
305305
test.That(t, err, test.ShouldBeNil)
306-
mURDF, err := urdf.ParseXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_viam.urdf"), "")
306+
mURDF, err := urdf.ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_viam.urdf"), "")
307307
test.That(t, err, test.ShouldBeNil)
308308

309309
seed := rand.New(rand.NewSource(50))

referenceframe/model_json.go

+29-21
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,42 @@ import (
77
"github.com/pkg/errors"
88
)
99

10+
// ErrNoModelInformation is used when there is no model information.
11+
var ErrNoModelInformation = errors.New("no model information")
12+
1013
// ModelConfig represents all supported fields in a kinematics JSON file.
1114
type ModelConfig struct {
1215
Name string `json:"name"`
1316
KinParamType string `json:"kinematic_param_type,omitempty"`
1417
Links []LinkConfig `json:"links,omitempty"`
1518
Joints []JointConfig `json:"joints,omitempty"`
1619
DHParams []DHParamConfig `json:"dhParams,omitempty"`
20+
OriginalFile *ModelFile
21+
}
22+
23+
// ModelFile is a struct that stores the raw bytes of the file used to create the model as well as its extension,
24+
// which is useful for knowing how to unmarhsal it.
25+
type ModelFile struct {
26+
Bytes []byte
27+
Extension string
28+
}
29+
30+
// UnmarshalModelJSON will parse the given JSON data into a kinematics model. modelName sets the name of the model,
31+
// will use the name from the JSON if string is empty.
32+
func UnmarshalModelJSON(jsonData []byte, modelName string) (Model, error) {
33+
m := &ModelConfig{OriginalFile: &ModelFile{Bytes: jsonData, Extension: "json"}}
34+
35+
// empty data probably means that the robot component has no model information
36+
if len(jsonData) == 0 {
37+
return nil, ErrNoModelInformation
38+
}
39+
40+
err := json.Unmarshal(jsonData, m)
41+
if err != nil {
42+
return nil, errors.Wrap(err, "failed to unmarshal json file")
43+
}
44+
45+
return m.ParseConfig(modelName)
1746
}
1847

1948
// ParseConfig converts the ModelConfig struct into a full Model with the name modelName.
@@ -127,24 +156,3 @@ func ParseModelJSONFile(filename, modelName string) (Model, error) {
127156
}
128157
return UnmarshalModelJSON(jsonData, modelName)
129158
}
130-
131-
// ErrNoModelInformation is used when there is no model information.
132-
var ErrNoModelInformation = errors.New("no model information")
133-
134-
// UnmarshalModelJSON will parse the given JSON data into a kinematics model. modelName sets the name of the model,
135-
// will use the name from the JSON if string is empty.
136-
func UnmarshalModelJSON(jsonData []byte, modelName string) (Model, error) {
137-
m := &ModelConfig{}
138-
139-
// empty data probably means that the robot component has no model information
140-
if len(jsonData) == 0 {
141-
return nil, ErrNoModelInformation
142-
}
143-
144-
err := json.Unmarshal(jsonData, m)
145-
if err != nil {
146-
return nil, errors.Wrap(err, "failed to unmarshal json file")
147-
}
148-
149-
return m.ParseConfig(modelName)
150-
}

referenceframe/urdf/config.go referenceframe/urdf/model.go

+28-25
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import (
1414
"go.viam.com/rdk/utils"
1515
)
1616

17-
// Config represents all supported fields in a Universal Robot Description Format (URDF) file.
18-
type Config struct {
17+
// Extension is the file extension associated with URDF files.
18+
const Extension string = "urdf"
19+
20+
// ModelConfig represents all supported fields in a Universal Robot Description Format (URDF) file.
21+
type ModelConfig struct {
1922
XMLName xml.Name `xml:"robot"`
2023
Name string `xml:"name,attr"`
2124
Links []link `xml:"link"`
@@ -41,9 +44,9 @@ type joint struct {
4144
Limit *limit `xml:"limit,omitempty"`
4245
}
4346

44-
// NewConfigFromWorldState creates a urdf.Config struct which can be marshalled into xml and will be a
47+
// NewModelFromWorldState creates a urdf.Config struct which can be marshalled into xml and will be a
4548
// valid .urdf file representing the geometries in the given worldstate.
46-
func NewConfigFromWorldState(ws *referenceframe.WorldState, name string) (*Config, error) {
49+
func NewModelFromWorldState(ws *referenceframe.WorldState, name string) (*ModelConfig, error) {
4750
// the link we initialize this list with represents the world frame
4851
links := []link{{Name: referenceframe.World}}
4952
joints := make([]joint, 0)
@@ -68,40 +71,24 @@ func NewConfigFromWorldState(ws *referenceframe.WorldState, name string) (*Confi
6871
Child: frame{g.Label()},
6972
})
7073
}
71-
return &Config{
74+
return &ModelConfig{
7275
Name: name,
7376
Links: links,
7477
Joints: joints,
7578
}, nil
7679
}
7780

78-
// ParseXMLFile will read a given file and parse the contained URDF XML data into an equivalent Model.
79-
func ParseXMLFile(filename, modelName string) (referenceframe.Model, error) {
80-
//nolint:gosec
81-
xmlData, err := os.ReadFile(filename)
82-
if err != nil {
83-
return nil, errors.Wrap(err, "Failed to read URDF file")
84-
}
85-
86-
mc, err := ConvertURDFToConfig(xmlData, modelName)
87-
if err != nil {
88-
return nil, err
89-
}
90-
91-
return mc.ParseConfig(modelName)
92-
}
93-
94-
// ConvertURDFToConfig will transfer the given URDF XML data into an equivalent ModelConfig. Direct unmarshaling in the
81+
// UnmarshalModelXML will transfer the given URDF XML data into an equivalent ModelConfig. Direct unmarshaling in the
9582
// same fashion as ModelJSON is not possible, as URDF data will need to be evaluated to accommodate differences
9683
// between the two kinematics encoding schemes.
97-
func ConvertURDFToConfig(xmlData []byte, modelName string) (*referenceframe.ModelConfig, error) {
84+
func UnmarshalModelXML(xmlData []byte, modelName string) (*referenceframe.ModelConfig, error) {
9885
// empty data probably means that the read URDF has no actionable information
9986
if len(xmlData) == 0 {
10087
return nil, referenceframe.ErrNoModelInformation
10188
}
10289

103-
mc := &referenceframe.ModelConfig{}
104-
urdf := &Config{}
90+
mc := &referenceframe.ModelConfig{OriginalFile: &referenceframe.ModelFile{Bytes: xmlData, Extension: Extension}}
91+
urdf := &ModelConfig{}
10592
err := xml.Unmarshal(xmlData, urdf)
10693
if err != nil {
10794
return nil, errors.Wrap(err, "Failed to convert URDF data to equivalent URDFConfig struct")
@@ -242,3 +229,19 @@ func ConvertURDFToConfig(xmlData []byte, modelName string) (*referenceframe.Mode
242229
}
243230
return mc, nil
244231
}
232+
233+
// ParseModelXMLFile will read a given file and parse the contained URDF XML data into an equivalent Model.
234+
func ParseModelXMLFile(filename, modelName string) (referenceframe.Model, error) {
235+
//nolint:gosec
236+
xmlData, err := os.ReadFile(filename)
237+
if err != nil {
238+
return nil, errors.Wrap(err, "failed to read URDF file")
239+
}
240+
241+
mc, err := UnmarshalModelXML(xmlData, modelName)
242+
if err != nil {
243+
return nil, err
244+
}
245+
246+
return mc.ParseConfig(modelName)
247+
}

referenceframe/urdf/config_test.go referenceframe/urdf/model_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414

1515
func TestParseURDFFile(t *testing.T) {
1616
// Test a URDF which has prismatic joints
17-
u, err := ParseXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/example_gantry.urdf"), "")
17+
u, err := ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/example_gantry.urdf"), "")
1818
test.That(t, err, test.ShouldBeNil)
1919
test.That(t, len(u.DoF()), test.ShouldEqual, 2)
2020

2121
// Test a URDF will has collision geometries we can evaluate and a DoF of 6
22-
u, err = ParseXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_viam.urdf"), "")
22+
u, err = ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_viam.urdf"), "")
2323
test.That(t, err, test.ShouldBeNil)
2424
model, ok := u.(*referenceframe.SimpleModel)
2525
test.That(t, ok, test.ShouldBeTrue)
@@ -30,13 +30,13 @@ func TestParseURDFFile(t *testing.T) {
3030
test.That(t, len(modelGeo.Geometries()), test.ShouldEqual, 5)
3131

3232
// Test naming of a URDF to something other than the robot's name element
33-
u, err = ParseXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_minimal.urdf"), "foo")
33+
u, err = ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_minimal.urdf"), "foo")
3434
test.That(t, err, test.ShouldBeNil)
3535
test.That(t, u.Name(), test.ShouldEqual, "foo")
3636
}
3737

3838
func TestURDFTransforms(t *testing.T) {
39-
u, err := ParseXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_minimal.urdf"), "")
39+
u, err := ParseModelXMLFile(utils.ResolveFile("referenceframe/urdf/testfiles/ur5_minimal.urdf"), "")
4040
test.That(t, err, test.ShouldBeNil)
4141
simple, ok := u.(*referenceframe.SimpleModel)
4242
test.That(t, ok, test.ShouldBeTrue)
@@ -75,7 +75,7 @@ func TestWorlStateConversion(t *testing.T) {
7575
)
7676
test.That(t, err, test.ShouldBeNil)
7777

78-
cfg, err := NewConfigFromWorldState(ws, "test")
78+
cfg, err := NewModelFromWorldState(ws, "test")
7979
test.That(t, err, test.ShouldBeNil)
8080
bytes, err := xml.MarshalIndent(cfg, "", " ")
8181
test.That(t, err, test.ShouldBeNil)

0 commit comments

Comments
 (0)