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. The component configuration for the sensor could look like this, with the name of the camera as an attribute:
{
"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.
To access resources from within a module, use dependencies:
Use required dependencies when your module should fail to build or reconfigure if a dependency does not successfully start.
viam-server
will not build or reconfigure a module if the module has required dependencies that are not available.
In your modular resource’s validate_config
method, check the configuration attributes, then add the dependency name to the first list of dependencies in the returned tuple:
For example:
@classmethod
def validate_config(
cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
req_deps = []
fields = config.attributes.fields
if "camera_name" not 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
req_deps.append(camera_name)
return req_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) (requiredDeps []string, optionalDeps []string, err error) {
var reqDeps []string
if cfg.CameraName == "" {
return nil, nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
}
reqDeps = append(reqDeps, cfg.CameraName)
return reqDeps, nil, 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 an optional dependency does not start, the modular resource will continue to build and reconfigure without it.
viam-server
reattempts to construct the optional dependency every 5 seconds.
When an optional dependency constructs successfully, your modular resource reconfigures so it can access the optional dependency.
Example use case for optional dependencies: If your module depends on multiple cameras, but can function even when some are unavailable, you can code the cameras as optional dependencies so that your module can construct and reconfigure without them.
If your module has optional dependencies, the steps are the same as for required dependencies, except that your validate_config
function should add the dependency to the second element of the returned tuple:
@classmethod
def validate_config(
cls, config: ComponentConfig
) -> Tuple[Sequence[str], Sequence[str]]:
opt_deps = []
fields = config.attributes.fields
if "camera_name" not 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
opt_deps.append(camera_name)
return [], opt_deps
Be sure to handle the case where the dependency is not available 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
function should add the dependency to the second returned element:
func (cfg *Config) Validate(path string) (requiredDeps []string, optionalDeps []string, err error) {
var optDeps []string
if cfg.CameraName == "" {
return nil, nil, resource.NewConfigValidationFieldRequiredError(path, "camera_name")
}
optDeps = append(optDeps, cfg.CameraName)
return nil, optDeps, nil
}
Be sure to handle the case where the dependency is not available in your API implementation as well.
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!