-
Notifications
You must be signed in to change notification settings - Fork 109
/
robot.go
319 lines (270 loc) · 10.1 KB
/
robot.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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Package robot defines the robot which is the root of all robotic parts.
package robot
import (
"context"
"fmt"
"strings"
"time"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/dynamic"
"github.com/pkg/errors"
"go.viam.com/utils/pexec"
"go.viam.com/rdk/cloud"
"go.viam.com/rdk/config"
"go.viam.com/rdk/grpc"
"go.viam.com/rdk/logging"
"go.viam.com/rdk/operation"
"go.viam.com/rdk/pointcloud"
"go.viam.com/rdk/referenceframe"
"go.viam.com/rdk/resource"
"go.viam.com/rdk/robot/framesystem"
"go.viam.com/rdk/robot/packages"
weboptions "go.viam.com/rdk/robot/web/options"
"go.viam.com/rdk/session"
)
// A Robot encompasses all functionality of some robot comprised
// of parts, local and remote.
//
// DiscoverComponents example:
//
// // Define a new discovery query.
// q := resource.NewDiscoveryQuery(acme.API, resource.Model{Name: "some model"})
//
// // Define a list of discovery queries.
// qs := []resource.DiscoverQuery{q}
//
// // Get component configurations with these queries.
// component_configs, err := machine.DiscoverComponents(ctx.Background(), qs)
//
// ResourceNames example:
//
// resource_names := machine.ResourceNames()
//
// FrameSystemConfig example:
//
// // Print the frame system configuration
// frameSystem, err := machine.FrameSystemConfig(context.Background(), nil)
// fmt.Println(frameSystem)
//
// TransformPose example:
//
// import (
// "go.viam.com/rdk/referenceframe"
// "go.viam.com/rdk/spatialmath"
// )
//
// baseOrigin := referenceframe.NewPoseInFrame("test-base", spatialmath.NewZeroPose())
// movementSensorToBase, err := machine.TransformPose(ctx, baseOrigin, "my-movement-sensor", nil)
//
// Status example:
//
// status, err := machine.Status(ctx)
//
// GetCloudMetadata example:
//
// metadata, err := machine.CloudMetadata()
// machine_id = metadata.MachineID
// machine_part_id = metadata.MachinePartID
// primary_org_id = metadata.PrimaryOrgID
// location_id = metadata.LocationID
//
// Close example:
//
// // Cleanly close the underlying connections and stop any periodic tasks,
// err := machine.Close(ctx)
//
// StopAll example:
//
// // Cancel all current and outstanding operations for the machine and stop all actuators and movement.
// err := machine.StopAll(ctx)
//
// Shutdown example:
//
// // Shut down the robot.
// err := machine.Shutdown()
type Robot interface {
// DiscoverComponents returns discovered component configurations.
DiscoverComponents(ctx context.Context, qs []resource.DiscoveryQuery) ([]resource.Discovery, error)
// RemoteByName returns a remote robot by name.
RemoteByName(name string) (Robot, bool)
// ResourceByName returns a resource by name
ResourceByName(name resource.Name) (resource.Resource, error)
// RemoteNames returns the names of all known remote robots.
RemoteNames() []string
// ResourceNames returns a list of all known resource names.
ResourceNames() []resource.Name
// ResourceRPCAPIs returns a list of all known resource RPC APIs.
ResourceRPCAPIs() []resource.RPCAPI
// ProcessManager returns the process manager for the robot.
ProcessManager() pexec.ProcessManager
// OperationManager returns the operation manager the robot is using.
OperationManager() *operation.Manager
// SessionManager returns the session manager the robot is using.
SessionManager() session.Manager
// PackageManager returns the package manager the robot is using.
PackageManager() packages.Manager
// Logger returns the logger the robot is using.
Logger() logging.Logger
// FrameSystemConfig returns the individual parts that make up a robot's frame system
FrameSystemConfig(ctx context.Context) (*framesystem.Config, error)
// TransformPose will transform the pose of the requested poseInFrame to the desired frame in the robot's frame system.
TransformPose(
ctx context.Context,
pose *referenceframe.PoseInFrame,
dst string,
additionalTransforms []*referenceframe.LinkInFrame,
) (*referenceframe.PoseInFrame, error)
// TransformPointCloud will transform the pointcloud to the desired frame in the robot's frame system.
// Do not move the robot between the generation of the initial pointcloud and the receipt
// of the transformed pointcloud because that will make the transformations inaccurate.
TransformPointCloud(ctx context.Context, srcpc pointcloud.PointCloud, srcName, dstName string) (pointcloud.PointCloud, error)
// Status takes a list of resource names and returns their corresponding statuses. If no names are passed in, return all statuses.
Status(ctx context.Context, resourceNames []resource.Name) ([]Status, error)
// CloudMetadata returns app-related information about the robot.
CloudMetadata(ctx context.Context) (cloud.Metadata, error)
// Close attempts to cleanly close down all constituent parts of the robot.
Close(ctx context.Context) error
// StopAll cancels all current and outstanding operations for the robot and stops all actuators and movement
StopAll(ctx context.Context, extra map[resource.Name]map[string]interface{}) error
// RestartModule reloads a module as if its config changed
RestartModule(ctx context.Context, req RestartModuleRequest) error
// Shutdown shuts down the robot.
Shutdown(ctx context.Context) error
}
// A LocalRobot is a Robot that can have its parts modified.
type LocalRobot interface {
Robot
// Config returns a config representing the current state of the robot.
Config() *config.Config
// Reconfigure instructs the robot to safely reconfigure itself based
// on the given new config.
Reconfigure(ctx context.Context, newConfig *config.Config)
// StartWeb starts the web server, will return an error if server is already up.
StartWeb(ctx context.Context, o weboptions.Options) error
// StopWeb stops the web server, will be a noop if server is not up.
StopWeb()
// WebAddress returns the address of the web service.
WebAddress() (string, error)
// ModuleAddress returns the address (path) of the unix socket modules use to contact the parent.
ModuleAddress() (string, error)
// ExportResourcesAsDot exports the resource graph as a DOT representation for
// visualization.
// DOT reference: https://graphviz.org/doc/info/lang.html
ExportResourcesAsDot(index int) (resource.GetSnapshotInfo, error)
}
// A RemoteRobot is a Robot that was created through a connection.
type RemoteRobot interface {
Robot
// Connected returns whether the remote is connected or not.
Connected() bool
}
// Status holds a resource name, the time that resource was last reconfigured
// (or built), and its corresponding status. Status.Status is expected to be
// comprised of string keys and values comprised of primitives, list of
// primitives, maps with string keys (or at least can be decomposed into one),
// or lists of the forementioned type of maps. Results with other types of data
// are not guaranteed.
type Status struct {
Name resource.Name
LastReconfigured time.Time
Status interface{}
}
// RestartModuleRequest is a go mirror of a proto message.
type RestartModuleRequest struct {
ModuleID string
ModuleName string
}
// AllResourcesByName returns an array of all resources that have this short name.
// NOTE: this function queries by the shortname rather than the fully qualified resource name which is not recommended practice
// and may become deprecated in the future.
func AllResourcesByName(r Robot, name string) []resource.Resource {
all := []resource.Resource{}
for _, n := range r.ResourceNames() {
if n.ShortName() == name {
r, err := r.ResourceByName(n)
if err != nil {
panic("this should be impossible")
}
all = append(all, r)
}
}
return all
}
// NamesByAPI is a helper for getting all names from the given Robot given the API.
func NamesByAPI(r Robot, api resource.API) []string {
names := []string{}
for _, n := range r.ResourceNames() {
if n.API == api {
names = append(names, n.ShortName())
}
}
return names
}
// TypeAndMethodDescFromMethod attempts to determine the resource API and its respective gRPC method information
// from the given robot and method path. If nothing can be found, grpc.UnimplementedError is returned.
func TypeAndMethodDescFromMethod(r Robot, method string) (*resource.RPCAPI, *desc.MethodDescriptor, error) {
methodParts := strings.Split(method, "/")
if len(methodParts) != 3 {
return nil, nil, grpc.UnimplementedError
}
protoSvc := methodParts[1]
protoMethod := methodParts[2]
var foundType *resource.RPCAPI
for _, resAPI := range r.ResourceRPCAPIs() {
if resAPI.Desc.GetFullyQualifiedName() == protoSvc {
apiCopy := resAPI
foundType = &apiCopy
break
}
}
if foundType == nil {
return nil, nil, grpc.UnimplementedError
}
methodDesc := foundType.Desc.FindMethodByName(protoMethod)
if methodDesc == nil {
return nil, nil, grpc.UnimplementedError
}
return foundType, methodDesc, nil
}
// ResourceFromProtoMessage attempts to find out the name/resource associated with a gRPC message.
func ResourceFromProtoMessage(
robot Robot,
msg *dynamic.Message,
api resource.API,
) (interface{}, resource.Name, error) {
// we assume a convention that there will be a field called name that will be the resource
// name and a string.
if !msg.HasFieldName("name") {
return nil, resource.Name{}, errors.New("unable to determine resource name due to missing 'name' field")
}
name, ok := msg.GetFieldByName("name").(string)
if !ok || name == "" {
return nil, resource.Name{}, fmt.Errorf("unable to determine resource name due to invalid name field %v", name)
}
fqName := resource.NewName(api, name)
res, err := robot.ResourceByName(fqName)
if err != nil {
return nil, resource.Name{}, err
}
return res, fqName, nil
}
// ResourceFromRobot returns a resource from a robot.
func ResourceFromRobot[T resource.Resource](robot Robot, name resource.Name) (T, error) {
var zero T
res, err := robot.ResourceByName(name)
if err != nil {
return zero, err
}
part, ok := res.(T)
if !ok {
return zero, resource.TypeError[T](res)
}
return part, nil
}
// MatchesModule returns true if the passed-in module matches its name / ID.
func (rmr *RestartModuleRequest) MatchesModule(mod config.Module) bool {
if len(rmr.ModuleID) > 0 {
return mod.ModuleID == rmr.ModuleID
}
return mod.Name == rmr.ModuleName
}