-
Notifications
You must be signed in to change notification settings - Fork 110
/
framesystem.go
300 lines (276 loc) · 10 KB
/
framesystem.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package framesystem
import (
"context"
"fmt"
"sync"
"github.com/edaniels/golog"
"github.com/pkg/errors"
"go.opencensus.io/trace"
commonpb "go.viam.com/api/common/v1"
"go.viam.com/rdk/config"
"go.viam.com/rdk/referenceframe"
"go.viam.com/rdk/resource"
"go.viam.com/rdk/robot"
framesystemparts "go.viam.com/rdk/robot/framesystem/parts"
)
// SubtypeName is the name of the type of service.
const SubtypeName = resource.SubtypeName("frame_system")
// LocalFrameSystemName is the default name of the frame system created by the service.
const LocalFrameSystemName = "robot"
// A Service that returns the frame system for a robot.
type Service interface {
Config(ctx context.Context, additionalTransforms []*commonpb.Transform) (framesystemparts.Parts, error)
TransformPose(
ctx context.Context, pose *referenceframe.PoseInFrame, dst string,
additionalTransforms []*commonpb.Transform,
) (*referenceframe.PoseInFrame, error)
}
// RobotFsCurrentInputs will get present inputs for a framesystem from a robot and return a map of those inputs, as well as a map of the
// InputEnabled resources that those inputs came from.
func RobotFsCurrentInputs(
ctx context.Context,
r robot.Robot,
fs referenceframe.FrameSystem,
) (map[string][]referenceframe.Input, map[string]referenceframe.InputEnabled, error) {
input := referenceframe.StartPositions(fs)
// build maps of relevant components and inputs from initial inputs
allOriginals := map[string][]referenceframe.Input{}
resources := map[string]referenceframe.InputEnabled{}
for name, original := range input {
// skip frames with no input
if len(original) == 0 {
continue
}
// add component to map
allOriginals[name] = original
components := robot.AllResourcesByName(r, name)
if len(components) != 1 {
return nil, nil, fmt.Errorf("got %d resources instead of 1 for (%s)", len(components), name)
}
component, ok := components[0].(referenceframe.InputEnabled)
if !ok {
return nil, nil, fmt.Errorf("%v(%T) is not InputEnabled", name, components[0])
}
resources[name] = component
// add input to map
pos, err := component.CurrentInputs(ctx)
if err != nil {
return nil, nil, err
}
input[name] = pos
}
return input, resources, nil
}
// New returns a new frame system service for the given robot.
func New(ctx context.Context, r robot.Robot, logger golog.Logger) Service {
return &frameSystemService{
r: r,
logger: logger,
}
}
// the frame system service collects all the relevant parts that make up the frame system from the robot
// configs, and the remote robot configs.
type frameSystemService struct {
mu sync.RWMutex
r robot.Robot
localParts framesystemparts.Parts // gotten from the local robot's config.Config
offsetParts map[string]*config.FrameSystemPart // gotten from local robot's config.Remote
logger golog.Logger
}
// Update will rebuild the frame system from the newly updated robot.
// NOTE(RDK-258): If remotes can trigger a local robot to reconfigure, you can cache the remoteParts in svc as well.
func (svc *frameSystemService) Update(ctx context.Context, resources map[resource.Name]interface{}) error {
ctx, span := trace.StartSpan(ctx, "services::framesystem::Update")
defer span.End()
err := svc.updateLocalParts(ctx)
if err != nil {
return err
}
err = svc.updateOffsetParts(ctx)
if err != nil {
return err
}
remoteParts, err := svc.updateRemoteParts(ctx)
if err != nil {
return err
}
// combine the parts, sort, and print the result
allParts := combineParts(svc.localParts, svc.offsetParts, remoteParts)
sortedParts, err := framesystemparts.TopologicallySort(allParts)
if err != nil {
return err
}
svc.logger.Debugf("updated robot frame system:\n%v", sortedParts.String())
return nil
}
// Config returns the info of each individual part that makes up the frame system
// The output of this function is to be sent over GRPC to the client, so the client
// can build its frame system. requests the remote components from the remote's frame system service.
// NOTE(RDK-258): If remotes can trigger a local robot to reconfigure, you don't need to update remotes in every call.
func (svc *frameSystemService) Config(ctx context.Context, additionalTransforms []*commonpb.Transform) (framesystemparts.Parts, error) {
ctx, span := trace.StartSpan(ctx, "services::framesystem::Config")
defer span.End()
// update parts from remotes
remoteParts, err := svc.updateRemoteParts(ctx)
if err != nil {
return nil, err
}
// build the config
allParts := combineParts(svc.localParts, svc.offsetParts, remoteParts)
for _, transformMsg := range additionalTransforms {
newPart, err := config.ConvertTransformProtobufToFrameSystemPart(transformMsg)
if err != nil {
return nil, err
}
allParts = append(allParts, newPart)
}
sortedParts, err := framesystemparts.TopologicallySort(allParts)
if err != nil {
return nil, err
}
return sortedParts, nil
}
// TransformPose will transform the pose of the requested poseInFrame to the desired frame in the robot's frame system.
func (svc *frameSystemService) TransformPose(
ctx context.Context,
pose *referenceframe.PoseInFrame,
dst string,
additionalTransforms []*commonpb.Transform,
) (*referenceframe.PoseInFrame, error) {
ctx, span := trace.StartSpan(ctx, "services::framesystem::TransformPose")
defer span.End()
svc.mu.RLock()
defer svc.mu.RUnlock()
// get the frame system and initial inputs
allParts, err := svc.Config(ctx, additionalTransforms)
if err != nil {
return nil, err
}
fs, err := NewFrameSystemFromParts(LocalFrameSystemName, "", allParts, svc.logger)
if err != nil {
return nil, err
}
input := referenceframe.StartPositions(fs)
// build maps of relevant components and inputs from initial inputs
for name, original := range input {
// determine frames to skip
if len(original) == 0 {
continue
}
// add component to map
components := robot.AllResourcesByName(svc.r, name)
if len(components) != 1 {
return nil, fmt.Errorf("got %d resources instead of 1 for (%s)", len(components), name)
}
component, ok := components[0].(referenceframe.InputEnabled)
if !ok {
return nil, fmt.Errorf("%v(%T) is not InputEnabled", name, components[0])
}
// add input to map
pos, err := component.CurrentInputs(ctx)
if err != nil {
return nil, err
}
input[name] = pos
}
tf, err := fs.Transform(input, pose, dst)
if err != nil {
return nil, err
}
pose, _ = tf.(*referenceframe.PoseInFrame)
return pose, nil
}
// updateLocalParts collects the physical parts of the robot that may have frame info,
// excluding remote robots and services, etc from the robot's config.Config.
func (svc *frameSystemService) updateLocalParts(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "services::framesystem::updateLocalParts")
defer span.End()
parts := make(map[string]*config.FrameSystemPart)
seen := make(map[string]bool)
local, ok := svc.r.(robot.LocalRobot)
if !ok {
return robot.NewUnimplementedLocalInterfaceError(svc.r)
}
cfg, err := local.Config(ctx) // Eventually there will be another function that gathers the frame system config
if err != nil {
return err
}
for _, c := range cfg.Components {
if c.Frame == nil { // no Frame means dont include in frame system.
continue
}
if c.Name == referenceframe.World {
return errors.Errorf("cannot give frame system part the name %s", referenceframe.World)
}
if c.Frame.Parent == "" {
return errors.Errorf("parent field in frame config for part %q is empty", c.Name)
}
seen[c.Name] = true
model, err := extractModelFrameJSON(svc.r, c.ResourceName())
if err != nil && !errors.Is(err, referenceframe.ErrNoModelInformation) {
return err
}
parts[c.Name] = &config.FrameSystemPart{Name: c.Name, FrameConfig: c.Frame, ModelFrame: model}
}
svc.localParts = framesystemparts.PartMapToPartSlice(parts)
return nil
}
// updateOffsetParts collects the frame offset information from the config.Remote of the local robot.
func (svc *frameSystemService) updateOffsetParts(ctx context.Context) error {
ctx, span := trace.StartSpan(ctx, "services::framesystem::updateOffsetParts")
defer span.End()
local, ok := svc.r.(robot.LocalRobot)
if !ok {
return robot.NewUnimplementedLocalInterfaceError(svc.r)
}
conf, err := local.Config(ctx)
if err != nil {
return err
}
remoteNames := local.RemoteNames()
offsetParts := make(map[string]*config.FrameSystemPart)
for _, remoteName := range remoteNames {
rConf, err := getRemoteRobotConfig(remoteName, conf)
if err != nil {
return errors.Wrapf(err, "remote %s", remoteName)
}
if rConf.Frame == nil { // skip over remote if it has no frame info
svc.logger.Debugf("remote %s has no frame config info, skipping", remoteName)
continue
}
connectionName := rConf.Name + "_" + referenceframe.World
// build the frame system part that connects remote world to base world
connection := &config.FrameSystemPart{
Name: connectionName,
FrameConfig: rConf.Frame,
}
offsetParts[remoteName] = connection
}
svc.offsetParts = offsetParts
return nil
}
// updateRemoteParts is a helper function to get parts from the connected remote robots, and renames them.
func (svc *frameSystemService) updateRemoteParts(ctx context.Context) (map[string]framesystemparts.Parts, error) {
ctx, span := trace.StartSpan(ctx, "services::framesystem::updateRemoteParts")
defer span.End()
// get frame parts for each remote robot, skip if not in remote offset map
remoteParts := make(map[string]framesystemparts.Parts)
remoteNames := svc.r.RemoteNames()
for _, remoteName := range remoteNames {
if _, ok := svc.offsetParts[remoteName]; !ok {
continue // no remote frame info, skipping
}
remote, ok := svc.r.RemoteByName(remoteName)
if !ok {
return nil, errors.Errorf("cannot find remote robot %s", remoteName)
}
rParts, err := robotFrameSystemConfig(ctx, remote)
if err != nil {
return nil, errors.Wrapf(err, "remote %s", remoteName)
}
connectionName := remoteName + "_" + referenceframe.World
rParts = framesystemparts.RenameRemoteParts(rParts, remoteName, connectionName)
remoteParts[remoteName] = rParts
}
return remoteParts, nil
}