Skip to content

Commit 65ecdbd

Browse files
authored
DOCS-1146: Update outdated code on Go custom base example (#1872)
1 parent 261f2c4 commit 65ecdbd

File tree

1 file changed

+103
-86
lines changed
  • docs/extend/modular-resources/create

1 file changed

+103
-86
lines changed

docs/extend/modular-resources/create/_index.md

+103-86
Original file line numberDiff line numberDiff line change
@@ -201,127 +201,147 @@ import (
201201
"go.uber.org/multierr"
202202

203203
"go.viam.com/rdk/components/base"
204-
"go.viam.com/rdk/components/generic"
204+
"go.viam.com/rdk/components/base/kinematicbase"
205205
"go.viam.com/rdk/components/motor"
206-
"go.viam.com/rdk/config"
207-
"go.viam.com/rdk/registry"
208206
"go.viam.com/rdk/resource"
209-
"go.viam.com/rdk/utils"
207+
"go.viam.com/rdk/spatialmath"
210208
)
211209

212-
// Here is where we define our new model's colon-delimited-triplet (acme:demo:mybase)
210+
// Here is where we define your new model's colon-delimited-triplet (acme:demo:mybase)
213211
// acme = namespace, demo = family, mybase = model name.
214212
var (
215213
Model = resource.NewModel("acme", "demo", "mybase")
216214
errUnimplemented = errors.New("unimplemented")
217215
)
218216

219-
// Constructor
220-
func newBase(ctx context.Context, deps registry.Dependencies, config config.Component, logger golog.Logger) (interface{}, error) {
221-
b := &MyBase{logger: logger}
222-
err := b.Reconfigure(config, deps)
223-
return b, err
224-
}
217+
const (
218+
myBaseWidthMm = 500.0 // Base has a wheel tread of 500 millimeters
219+
myBaseTurningRadiusM = 0.3 // Base turns around a circle of radius .3 meters
220+
)
225221

226-
// Defines what the JSON configuration should look like
227-
type MyBaseConfig struct {
228-
LeftMotor string `json:"motorL"`
229-
RightMotor string `json:"motorR"`
222+
func init() {
223+
resource.RegisterComponent(base.API, Model, resource.Registration[base.Base, *Config]{
224+
Constructor: newBase,
225+
})
230226
}
231227

232-
// Validates JSON configuration
233-
func (cfg *MyBaseConfig) Validate(path string) ([]string, error) {
234-
if cfg.LeftMotor == "" {
235-
return nil, fmt.Errorf(`expected "motorL" attribute for mybase %q`, path)
228+
func newBase(ctx context.Context, deps resource.Dependencies, conf resource.Config, logger golog.Logger) (base.Base, error) {
229+
b := &myBase{
230+
Named: conf.ResourceName().AsNamed(),
231+
logger: logger,
236232
}
237-
if cfg.RightMotor == "" {
238-
return nil, fmt.Errorf(`expected "motorR" attribute for mybase %q`, path)
233+
if err := b.Reconfigure(ctx, deps, conf); err != nil {
234+
return nil, err
239235
}
240-
241-
return []string{cfg.LeftMotor, cfg.RightMotor}, nil
236+
return b, nil
242237
}
243238

244-
// Handles attribute reconfiguration
245-
func (base *MyBase) Reconfigure(cfg config.Component, deps registry.Dependencies) error {
246-
base.left = nil
247-
base.right = nil
248-
baseConfig, ok := cfg.ConvertedAttributes.(*MyBaseConfig)
249-
if !ok {
250-
return utils.NewUnexpectedTypeError(baseConfig, cfg.ConvertedAttributes)
239+
240+
// Reconfigure reconfigures with new settings.
241+
func (b *myBase) Reconfigure(ctx context.Context, deps resource.Dependencies, conf resource.Config) error {
242+
b.left = nil
243+
b.right = nil
244+
245+
// This takes the generic resource.Config passed down from the parent and converts it to the
246+
// model-specific (aka "native") Config structure defined, above making it easier to directly access attributes.
247+
baseConfig, err := resource.NativeConfig[*Config](conf)
248+
if err != nil {
249+
return err
251250
}
252-
var err error
253251

254-
base.left, err = motor.FromDependencies(deps, baseConfig.LeftMotor)
252+
b.left, err = motor.FromDependencies(deps, baseConfig.LeftMotor)
255253
if err != nil {
256254
return errors.Wrapf(err, "unable to get motor %v for mybase", baseConfig.LeftMotor)
257255
}
258256

259-
base.right, err = motor.FromDependencies(deps, baseConfig.RightMotor)
257+
b.right, err = motor.FromDependencies(deps, baseConfig.RightMotor)
260258
if err != nil {
261259
return errors.Wrapf(err, "unable to get motor %v for mybase", baseConfig.RightMotor)
262260
}
263261

264-
// Stopping motors at reconfiguration
265-
return multierr.Combine(base.left.Stop(context.Background(), nil), base.right.Stop(context.Background(), nil))
262+
geometries, err := kinematicbase.CollisionGeometry(conf.Frame)
263+
if err != nil {
264+
b.logger.Warnf("base %v %s", b.Name(), err.Error())
265+
}
266+
b.geometries = geometries
267+
268+
// Stop motors when reconfiguring.
269+
return multierr.Combine(b.left.Stop(context.Background(), nil), b.right.Stop(context.Background(), nil))
270+
}
271+
272+
// DoCommand simply echos whatever was sent.
273+
func (b *myBase) DoCommand(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error) {
274+
return cmd, nil
266275
}
267276

268-
// Attributes of the base
269-
type MyBase struct {
270-
generic.Echo
271-
left motor.Motor
272-
right motor.Motor
273-
logger golog.Logger
277+
// Config contains two component (motor) names.
278+
type Config struct {
279+
LeftMotor string `json:"motorL"`
280+
RightMotor string `json:"motorR"`
274281
}
275282

276-
// Implement the methods the Viam RDK defines for the base API (rdk:component:base)
283+
// Validate validates the config and returns implicit dependencies,
284+
// this Validate checks if the left and right motors exist for the module's base model.
285+
func (cfg *Config) Validate(path string) ([]string, error) {
286+
// check if the attribute fields for the right and left motors are non-empty
287+
// this makes them reuqired for the model to successfully build
288+
if cfg.LeftMotor == "" {
289+
return nil, fmt.Errorf(`expected "motorL" attribute for mybase %q`, path)
290+
}
291+
if cfg.RightMotor == "" {
292+
return nil, fmt.Errorf(`expected "motorR" attribute for mybase %q`, path)
293+
}
277294

278-
// MoveStraight: unimplemented
279-
func (base *MyBase) MoveStraight(ctx context.Context, distanceMm int, mmPerSec float64, extra map[string]interface{}) error {
280-
return errUnimplemented
295+
// Return the left and right motor names so that `newBase` can access them as dependencies.
296+
return []string{cfg.LeftMotor, cfg.RightMotor}, nil
297+
}
298+
299+
type myBase struct {
300+
resource.Named
301+
left motor.Motor
302+
right motor.Motor
303+
logger golog.Logger
304+
geometries []spatialmath.Geometry
281305
}
282306

283-
// Spin: unimplemented
284-
func (base *MyBase) Spin(ctx context.Context, angleDeg, degsPerSec float64, extra map[string]interface{}) error {
307+
// MoveStraight does nothing.
308+
func (b *myBase) MoveStraight(ctx context.Context, distanceMm int, mmPerSec float64, extra map[string]interface{}) error {
285309
return errUnimplemented
286310
}
287311

288-
// SetVelocity: unimplemented
289-
func (base *MyBase) SetVelocity(ctx context.Context, linear, angular r3.Vector, extra map[string]interface{}) error {
312+
// Spin does nothing.
313+
func (b *myBase) Spin(ctx context.Context, angleDeg, degsPerSec float64, extra map[string]interface{}) error {
290314
return errUnimplemented
291315
}
292316

293-
// Properties: unimplemented
294-
func (base *MyBase) Spin(ctx context.Context, extra map[string]interface{}) error {
317+
// SetVelocity does nothing.
318+
func (b *myBase) SetVelocity(ctx context.Context, linear, angular r3.Vector, extra map[string]interface{}) error {
295319
return errUnimplemented
296320
}
297321

298-
// SetPower: sets the linear and angular velocity of the left and right motors on the base
299-
func (base *MyBase) SetPower(ctx context.Context, linear, angular r3.Vector, extra map[string]interface{}) error {
300-
// stop the base if absolute value of linear and angular velocity is less than .01
322+
// SetPower computes relative power between the wheels and sets power for both motors.
323+
func (b *myBase) SetPower(ctx context.Context, linear, angular r3.Vector, extra map[string]interface{}) error {
324+
b.logger.Debugf("SetPower Linear: %.2f Angular: %.2f", linear.Y, angular.Z)
301325
if math.Abs(linear.Y) < 0.01 && math.Abs(angular.Z) < 0.01 {
302-
return base.Stop(ctx, extra)
326+
return b.Stop(ctx, extra)
303327
}
304-
305-
// use linear and angular velocity to calculate percentage of max power to pass to SetPower for left & right motors
306328
sum := math.Abs(linear.Y) + math.Abs(angular.Z)
307-
err1 := base.left.SetPower(ctx, (linear.Y-angular.Z)/sum, extra)
308-
err2 := base.right.SetPower(ctx, (linear.Y+angular.Z)/sum, extra)
329+
err1 := b.left.SetPower(ctx, (linear.Y-angular.Z)/sum, extra)
330+
err2 := b.right.SetPower(ctx, (linear.Y+angular.Z)/sum, extra)
309331
return multierr.Combine(err1, err2)
310332
}
311333

312-
// Stop: stops the base from moving by stopping both motors
313-
func (base *MyBase) Stop(ctx context.Context, extra map[string]interface{}) error {
314-
base.logger.Debug("Stop")
315-
316-
err1 := base.left.Stop(ctx, extra)
317-
err2 := base.right.Stop(ctx, extra)
318-
334+
// Stop halts motion.
335+
func (b *myBase) Stop(ctx context.Context, extra map[string]interface{}) error {
336+
b.logger.Debug("Stop")
337+
err1 := b.left.Stop(ctx, extra)
338+
err2 := b.right.Stop(ctx, extra)
319339
return multierr.Combine(err1, err2)
320340
}
321341

322-
// IsMoving: checks if either motor on the base is moving with motors' IsPowered
323-
func (base *MyBase) IsMoving(ctx context.Context) (bool, error) {
324-
for _, m := range []motor.Motor{base.left, base.right} {
342+
// IsMoving returns true if either motor is active.
343+
func (b *myBase) IsMoving(ctx context.Context) (bool, error) {
344+
for _, m := range []motor.Motor{b.left, b.right} {
325345
isMoving, _, err := m.IsPowered(ctx, nil)
326346
if err != nil {
327347
return false, err
@@ -333,25 +353,22 @@ func (base *MyBase) IsMoving(ctx context.Context) (bool, error) {
333353
return false, nil
334354
}
335355

336-
// Stop the base from moving when closing a client's connection to the base
337-
func (base *MyBase) Close(ctx context.Context) error {
338-
return base.Stop(ctx, nil)
356+
// Properties returns details about the physics of the base.
357+
func (b *myBase) Properties(ctx context.Context, extra map[string]interface{}) (base.Properties, error) {
358+
return base.Properties{
359+
TurningRadiusMeters: myBaseTurningRadiusM,
360+
WidthMeters: myBaseWidthMm * 0.001, // converting millimeters to meters
361+
}, nil
339362
}
340363

341-
// Register the component with the Go SDK
342-
func init() {
343-
registry.RegisterComponent(base.Subtype, Model, registry.Component{Constructor: newBase})
344-
345-
// VALIDATION: Uses RegisterComponentAttributeMapConverter to register a custom configuration struct that has a Validate(string) ([]string, error) method.
346-
// The Validate method will automatically be called in RDK's module manager to validate MyBase's configuration and register implicit dependencies.
347-
config.RegisterComponentAttributeMapConverter(
348-
base.Subtype,
349-
Model,
350-
func(attributes config.AttributeMap) (interface{}, error) {
351-
var conf MyBaseConfig
352-
return config.TransformAttributeMapToStruct(&conf, attributes)
353-
},
354-
&MyBaseConfig{})
364+
// Geometries returns physical dimensions.
365+
func (b *myBase) Geometries(ctx context.Context, extra map[string]interface{}) ([]spatialmath.Geometry, error) {
366+
return b.geometries, nil
367+
}
368+
369+
// Close stops motion during shutdown.
370+
func (b *myBase) Close(ctx context.Context) error {
371+
return b.Stop(ctx, nil)
355372
}
356373
```
357374

0 commit comments

Comments
 (0)