@@ -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.
214212var (
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