Previous
Hello World module
Dependencies are other resources that your modular resource needs to access in order to function.
For example, you could write a sensor component that requires a camera component, meaning that the camera is a dependency of that sensor.
{
"name": "mime-type-sensor",
"api": "rdk:component:sensor",
"model": "jessamy:my-module:my-sensor",
"attributes": {
"camera_name": "camera-1"
}
}
Dependencies are configured just like any other resource attribute. The difference is that dependencies represent other resources that must be built before the resource that depends on them.
When viam-server
builds all the resources on a machine, it builds the dependencies first.
From within a module, you cannot access resources in the same way that you would in a client application.
For example, you cannot call Camera.from_robot()
to get a camera resource.
Instead, you must access dependencies by writing your module code as follows:
In your modular resource’s validate_config
method, check the configuration attributes, then add the dependency name to the list of dependencies.
For example:
@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
deps = []
fields = config.attributes.fields
if not "camera_name" in fields:
raise Exception("missing required camera_name attribute")
elif not fields["camera_name"].HasField("string_value"):
raise Exception("camera_name must be a string")
camera_name = fields["camera_name"].string_value
deps.append(camera_name)
return deps
In your reconfigure
method:
dependencies
mapping.
def reconfigure(
self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]
):
camera_name = config.attributes.fields["camera_name"].string_value
camera_resource = dependencies[Camera.get_resource_name(camera_name)]
self.the_camera = cast(Camera, camera_resource)
# If you need to use the camera name in your module,
# for example to pass it to a vision service method,
# you can store it in an instance variable.
self.camera_name = camera_name
return super().reconfigure(config, dependencies)
You can now call API methods on the dependency resource within your module, for example:
img = await self.the_camera.get_image()
For full examples, see
In your modular resource’s Config
struct, add the dependency attribute name like any other attribute.
For example:
type Config struct {
CameraName string `json:"camera_name"`
}
Add the dependency to the <module-name><resource-name>
struct:
type myModuleMySensor struct {
resource.AlwaysRebuild
name resource.Name
logger logging.Logger
cfg *Config
camera camera.Camera
cancelCtx context.Context
cancelFunc func()
}
In your modular resource’s Validate
method, check the configuration attributes, then add the dependency name to the list of dependencies:
func (cfg *Config) Validate(path string) ([]string, error) {
var deps []string
if cfg.CameraName == "" {
return nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
}
deps = append(deps, cfg.CameraName)
return deps, nil
}
In your resource’s constructor, initialize the dependency:
func NewMySensor(ctx context.Context,deps resource.Dependencies,
name resource.Name, conf *Config, logger logging.Logger) (sensor.Sensor, error) {
cancelCtx, cancelFunc := context.WithCancel(context.Background())
s := &myModuleMySensor{
name: name,
logger: logger,
cfg: conf,
cancelCtx: cancelCtx,
cancelFunc: cancelFunc,
}
camera, err := camera.FromDependencies(deps, conf.CameraName)
if err != nil {
return nil, errors.New("failed to get camera dependency")
}
s.camera = camera
return s, nil
}
You can now call API methods on the dependency resource within your module, for example:
img, imgMetadata, err := s.camera.Image(ctx, utils.MimeTypeJPEG, nil)
Most Go modules use resource.AlwaysRebuild
within the <module-name><resource-name>
struct, which means that the resource rebuilds every time the module is reconfigured.
The steps above use resource.AlwaysRebuild
.
If you need to maintain the state of your resource, see (Optional) Create and edit a Reconfigure
function.
If your module has optional dependencies, the steps are the same as for required dependencies, except that your validate_config
method can treat the dependency as optional by returning an empty list if the dependency is not configured:
@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
deps = []
fields = config.attributes.fields
if "camera_name" not in fields:
self.logger.info(
"camera_name not configured, using no camera")
else:
if not fields["camera_name"].HasField("string_value"):
raise Exception("camera_name must be a string")
deps.append(fields["camera_name"].string_value)
return deps
Be sure to handle the case where the dependency is not configured in your API implementation as well.
If your module has optional dependencies, the steps are the same as for required dependencies, except that your Validate
method can treat the dependency as optional by returning an empty list if the dependency is not configured:
func (cfg *Config) Validate(path string) ([]string, error) {
var deps []string
if cfg.CameraName == "" {
logger.Info("camera_name not configured, using no camera")
return nil, nil
}
deps = append(deps, cfg.CameraName)
return deps, nil
}
Be sure to handle the case where the dependency is not configured in your API implementation as well.
Some older modules use explicit dependencies, which require users to list the names of dependencies in the depends_on
field of the resource’s configuration, for example:
{
"name": "mime-type-sensor",
"api": "rdk:component:sensor",
"model": "jessamy:my-module:my-sensor",
"attributes": {
"camera_name": "camera-1"
},
"depends_on": ["camera-1"]
}
This is deprecated and not recommended when writing new modules.
Instead, we recommend using implicit dependencies (as shown in the examples above), which do not require users to list the names of dependencies in the depends_on
field.
If your module requires dependencies, you can make it easier for users to configure them by writing a discovery service as one model within your module.
Was this page helpful?
Glad to hear it! If you have any other feedback please let us know:
We're sorry about that. To help us improve, please tell us what we can do better:
Thank you!