Access machine resources from within a module

From within a modular resource, you can access other machine resources using dependencies. For background on required and optional dependencies, see the overview.

The dependency pattern

Every dependency follows two steps: declare it in validation, then resolve it in your constructor. Once resolved, you call methods on it like any other resource.

The examples below show a base that depends on two motors – a required left motor and an optional right motor (for a base that can operate in single-motor mode).

1. Declare dependencies in validation

Dependency names come from your resource’s configuration attributes, keeping the module flexible:

{
  "name": "my-base",
  "api": "rdk:component:base",
  "model": "myorg:mymodule:mybase",
  "attributes": {
    "left_motor": "motor-1",
    "right_motor": "motor-2"
  }
}

Your validation method parses these names and returns them as required or optional dependencies:

@classmethod
def validate_config(
    cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
    req_deps = []
    opt_deps = []
    fields = config.attributes.fields

    # Required dependency
    if "left_motor" not in fields:
        raise Exception("missing required left_motor attribute")
    req_deps.append(fields["left_motor"].string_value)

    # Optional dependency
    if "right_motor" in fields:
        opt_deps.append(fields["right_motor"].string_value)

    return req_deps, opt_deps

Define your config struct with fields for each dependency name:

type Config struct {
    LeftMotor  string `json:"left_motor"`
    RightMotor string `json:"right_motor"`
}

func (cfg *Config) Validate(path string) ([]string, []string, error) {
    // Required dependency
    if cfg.LeftMotor == "" {
        return nil, nil,
            resource.NewConfigValidationFieldRequiredError(
                path, "left_motor")
    }
    reqDeps := []string{cfg.LeftMotor}

    // Optional dependency
    var optDeps []string
    if cfg.RightMotor != "" {
        optDeps = append(optDeps, cfg.RightMotor)
    }

    return reqDeps, optDeps, nil
}

2. Resolve dependencies

Resolve dependencies in your constructor. Use the dependency name to look up the resource, then cast it to the correct type.

from typing import cast
from viam.components.motor import Motor


@classmethod
def new(
    cls, config: ComponentConfig,
    dependencies: Mapping[ResourceName, ResourceBase]
) -> Self:
    instance = cls(config.name)
    fields = config.attributes.fields

    # Required dependency -- direct lookup
    left_name = fields["left_motor"].string_value
    instance.left = cast(
        Motor,
        dependencies[Motor.get_resource_name(left_name)])

    # Optional dependency -- use .get() and handle None
    instance.right = None
    if "right_motor" in fields:
        right_name = fields["right_motor"].string_value
        right_resource = dependencies.get(
            Motor.get_resource_name(right_name))
        if right_resource is not None:
            instance.right = cast(Motor, right_resource)

    return instance
import (
    "go.viam.com/rdk/components/motor"
)

func newMyBase(ctx context.Context, deps resource.Dependencies,
    conf resource.Config, logger logging.Logger,
) (base.Base, error) {
    baseConfig, err := resource.NativeConfig[*Config](conf)
    if err != nil {
        return nil, err
    }

    b := &myBase{
        Named:  conf.ResourceName().AsNamed(),
        logger: logger,
    }

    // Required dependency
    b.left, err = motor.FromProvider(deps, baseConfig.LeftMotor)
    if err != nil {
        return nil, err
    }

    // Optional dependency -- check config, ignore error
    if baseConfig.RightMotor != "" {
        b.right, err = motor.FromProvider(
            deps, baseConfig.RightMotor)
        if err != nil {
            logger.Infow("right motor not available, "+
                "running in single-motor mode",
                "error", err)
        }
    }

    return b, nil
}

What’s next