Support additional hardware and software

If your physical or virtual hardware is not supported by an existing registry module, you can create a new module to add support for it.

If you want to create a "custom module", this page provides instructions for creating one in Python and Go.

This page provides instructions for creating a module in Python or Go. For C++ module examples, see the C++ examples directory on GitHub. If you want to create a module for use with a microcontroller, see Modules for ESP32.

Example module: With each step of this guide, you have instructions for creating a module which does two things:

  1. Gets an image from a configured path on your machine
  2. Returns a random number

Prerequisites

Install the Viam CLI and authenticate

Install the Viam CLI and authenticate to Viam, from the same machine that you intend to upload your module from.

To download the Viam CLI on a macOS computer, install brew and run the following commands:

brew tap viamrobotics/brews
brew install viam

To download the Viam CLI on a Linux computer with the aarch64 architecture, run the following commands:

sudo curl -o /usr/local/bin/viam https://storage.googleapis.com/packages.viam.com/apps/viam-cli/viam-cli-stable-linux-arm64
sudo chmod a+rx /usr/local/bin/viam

To download the Viam CLI on a Linux computer with the amd64 (Intel x86_64) architecture, run the following commands:

sudo curl -o /usr/local/bin/viam https://storage.googleapis.com/packages.viam.com/apps/viam-cli/viam-cli-stable-linux-amd64
sudo chmod a+rx /usr/local/bin/viam

You can also install the Viam CLI using brew on Linux amd64 (Intel x86_64):

brew tap viamrobotics/brews
brew install viam

Download the binary and run it directly to use the Viam CLI on a Windows computer.

If you have Go installed, you can build the Viam CLI directly from source using the go install command:

go install go.viam.com/rdk/cli/viam@latest

To confirm viam is installed and ready to use, issue the viam command from your terminal. If you see help instructions, everything is correctly installed. If you do not see help instructions, add your local go/bin/* directory to your PATH variable. If you use bash as your shell, you can use the following command:

echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc

For more information see install the Viam CLI.

Authenticate your CLI session with Viam using one of the following options:

viam login

This will open a new browser window with a prompt to start the authentication process. If a browser window does not open, the CLI will present a URL for you to manually open in your browser. Follow the instructions to complete the authentication process.

Use your organization, location, or machine part API key and corresponding API key ID in the following command:

viam login api-key --key-id <api-key-id> --key <organization-api-key-secret>
A running machine connected to Viam

You can write a module without a machine, but to test your module you’ll need a machine.

Add a new machine on Viam. On the machine’s page, follow the setup instructions to install viam-server on the computer you’re using for your project. Wait until your machine has successfully connected to Viam.

For Python developers: Use Python 3.11+

If you plan to write your module using Python, you need Python 3.11 or newer installed on your computer to use the code generation tool in this guide.

You can check by running python3 --version or python --version in your terminal.

Preparation

While not required, we recommend starting by writing a test script to check that you can connect to and control your hardware from your computer, perhaps using the manufacturer’s API or other low-level code.

Example module: For the example module, the test script will open an image in the same directory and print a random number.

import random
from PIL import Image

# Open an image
img = Image.open("example.png")
img.show()

# Return a random number
random_number = random.random()
print(random_number)
package main

import (
  "fmt"
  "math/rand"
  "os"
)

func main() {
  // Open an image
  imgFile, err := os.Open("example.png")
  if err != nil {
    fmt.Printf("Error opening image file: %v\n", err)
    return
  }
  defer imgFile.Close()
  imgByte, err := os.ReadFile("example.png")
  fmt.Printf("Image file type: %T\n", imgByte)
  if err != nil {
    fmt.Printf("Error reading image file: %v\n", err)
    return
  }

  // Return a random number
  number := rand.Float64()
  fmt.Printf("Random number: %f\n", number)
}

Choose an API

You can think of a module as a packaged wrapper around a script. The module takes the functionality of the script and maps it to a standardized API for use within the Viam ecosystem.

Review the available component APIs and choose the one whose methods map most closely to the functionality you need.

If you need a method that is not in your chosen API, you can use the flexible DoCommand (which is built into all component APIs) to create custom commands. See Run control logic for more information.

Example module: To choose the Viam APIs that make sense for your module, think about the functionality you want to implement. You need a way to return an image and you need a way to return a number.

If you look at the camera API, you can see the GetImage method, which returns an image. That will work for the image.

The camera API also has a few other methods. You do not need to fully implement all the methods of an API. For example, this camera does not use point cloud data, so for methods like GetPointCloud it will return an “unimplemented” error.

The sensor API includes the GetReadings method. You can return the random number with that.

Note that the camera API can’t return a number and the sensor API can’t return an image. Each model can implement only one API, but your module can contain multiple modular resources. Therefore it is best to make two modular resources: a camera to return the image and a sensor to return a random number.

Write your module

Generate stub files

Use the Viam CLI to generate template files for your module:

1

Run the module generate command in your terminal:

viam module generate
Click for more details about each prompt
PromptDescription
Module nameChoose a name that describes the set of resources it supports.
LanguageChoose the programming language for the module. The CLI supports Python and Golang.
VisibilityChoose Private to share only with your organization, or Public to share publicly with all organizations. If you are testing, choose Private.
Namespace/Organization IDNavigate to your organization settings through the menu in the upper-right corner of the page. Find the Public namespace (or create one if you haven’t already) and copy that string. If you use the organization ID, you must still create a public namespace first if you wish to share the module publicly.
Resource to add to the module (API)The component API your module will implement. See Choose an API for more information.
Model nameName your component model based on what it supports, for example, if it supports a model of ultrasonic sensor called “XYZ Sensor 1234” you could call your model xyz_1234 or similar. Must be all-lowercase and use only alphanumeric characters (a-z and 0-9), hyphens (-), and underscores (_).
Enable cloud buildIf you select Yes (recommended) and push the generated files (including the .github folder) and create a release of the format X.X.X, the module will build for all architectures specified in the meta.json build file. You can select No if you want to always build the module yourself before uploading it. For more information see Update and manage modules.
Register moduleSelect Yes unless you are creating a local-only module for testing purposes and do not intend to upload it. Registering a module makes its name and metadata appear in the registry; uploading the actual code that powers the module is a separate step. If you decline to register the module at this point, you can run viam module create to register it later.

Example module: To build an example module that contains a camera model, use the following command:

viam module generate --language python --model-name hello-camera \
  --name hello-world --resource-subtype=camera --public false \
  --enable-cloud true
viam module generate --language go --model-name hello-camera \
  --name hello-world --resource-subtype=camera --public false \
  --enable-cloud true

The CLI only supports generating code for one model at a time. You can add the model for the sensor in a later step in Creating multiple models within one module.

2

The generator creates a directory containing stub files for your modular component. In the next section, you’ll customize some of the generated files to support your camera.

Example module: For the example module, the file structure is:

hello-world/
└── src/
|   ├── models/
|   |   └── hello_camera.py
|   └── main.py
└── README.md
└── build.sh
└── meta.json
└── requirements.txt
└── run.sh
└── setup.sh

If you want to understand the module structure, here’s what each file does:

  • README.md: Documentation template that gets uploaded to the registry when you upload the module.
  • meta.json: Module metadata that gets uploaded to the registry when you upload the module.
  • main.py and hello_camera.py: Core code that registers the module and resource and provides the model implementation.
  • setup.sh and requirements.txt: Setup script that creates a virtual environment and installs the dependencies listed in requirements.txt.
  • build.sh: Build script that packages the code for upload.
  • run.sh: Script that runs setup.sh and then executes the module from main.py.
hello-world/
└── cmd/
|   ├── cli/
|   |   └── main.go
|   └── module/
|       └── main.go
└── Makefile
└── README.md
└── go.mod
└── module.go
└── meta.json

If you want to understand the module structure, here’s what each file does:

  • README.md: Documentation template that gets uploaded to the registry when you upload the module.
  • meta.json: Module metadata that gets uploaded to the registry when you upload the module.
  • module/main.go and module.go: Core code that registers the module and resource and provides the model implementation.
  • cli/main.go: You can run this file to test the model you are creating (go run ./cmd/cli).
  • Makefile: Build and setup commands.

Creating multiple models within one module

Some of the code you generated for your first modular resource is shared across the module no matter how many modular resource models it supports. Some of the code you generated is resource-specific.

If you have multiple modular resources that are related, you can put them all into the same module.

For convenience, we recommend running the module generator again from within the first module’s directory, generating an unregistered module, and copying the resource-specific code from it.

Example module: Change directory into the first module’s directory:

cd hello-world

Run the following command from within the first module’s directory to generate temporary code you can copy from. Do not register this module.

viam module generate --language python --model-name hello-sensor \
  --name hello-world --resource-subtype=sensor --public false \
  --enable-cloud true

Click on each tab to see how the file should change to add the sensor-specific code:

Move the generated hello-world/hello-world/src/models/hello_sensor.py file to hello-world/src/models/.

Open the hello-world/src/main.py file and add HelloSensor to the list of imports so you have:

import asyncio

from viam.module.module import Module
try:
    from models.hello_camera import HelloCamera
    from models.hello_sensor import HelloSensor
except ModuleNotFoundError:  # when running as local module with run.sh
    from .models.hello_camera import HelloCamera
    from .models.hello_sensor import HelloSensor

if __name__ == '__main__':
    asyncio.run(Module.run_from_registry())

Save the file.

Open hello-world/meta.json and add the sensor model into the model list. Edit the description to include both models.

{
  "$schema": "https://dl.viam.dev/module.schema.json",
  "module_id": "exampleorg:hello-world",
  "visibility": "private",
  "url": "",
  "description": "Example camera and sensor components: hello-camera and hello-sensor",
  "models": [
    {
      "api": "rdk:component:camera",
      "model": "exampleorg:hello-world:hello-camera",
      "short_description": "A camera that returns an image.",
      "markdown_link": "README.md#model-exampleorghello-worldhello-camera"
    },
    {
      "api": "rdk:component:sensor",
      "model": "exampleorg:hello-world:hello-sensor",
      "short_description": "A sensor that returns a random number.",
      "markdown_link": "README.md#model-exampleorghello-worldhello-sensor"
    }
  ],
  "applications": null,
  "markdown_link": "README.md",
  "entrypoint": "./run.sh",
  "first_run": "",
  "build": {
    "build": "./build.sh",
    "setup": "./setup.sh",
    "path": "dist/archive.tar.gz",
    "arch": ["linux/amd64", "linux/arm64", "darwin/arm64", "windows/amd64"]
  }
}

Save the file.

viam module generate --language go --model-name hello-sensor \
  --name hello-world --resource-subtype=sensor --public false \
  --enable-cloud true

Click on each tab to see how the file should change to add the sensor-specific code:

In the initial module, change the name of hello-world/module.go to hello-camera.go.

Move and rename hello-world/hello-world/module.go to hello-world/hello-sensor.go.

Open hello-world/cmd/module/main.go. This file must add resource imports and register the module’s models:

package main

import (
    "helloworld"
    "go.viam.com/rdk/module"
    "go.viam.com/rdk/resource"
    camera "go.viam.com/rdk/components/camera"
    sensor "go.viam.com/rdk/components/sensor"
)

func main() {
    // ModularMain can take multiple APIModel arguments, if your module implements multiple models.
    module.ModularMain(
      resource.APIModel{ camera.API, helloworld.HelloCamera},
      resource.APIModel{ sensor.API, helloworld.HelloSensor},
    )
}

Save the file.

Open hello-world/meta.json and add the sensor model into the model list. Edit the description to include both models.

{
  "$schema": "https://dl.viam.dev/module.schema.json",
  "module_id": "exampleorg:hello-world",
  "visibility": "private",
  "url": "",
  "description": "Example camera and sensor components: hello-camera and hello-sensor",
  "models": [
    {
      "api": "rdk:component:camera",
      "model": "exampleorg:hello-world:hello-camera",
      "short_description": "A camera that returns an image.",
      "markdown_link": "README.md#model-exampleorghello-worldhello-camera"
    },
    {
      "api": "rdk:component:sensor",
      "model": "exampleorg:hello-world:hello-sensor",
      "short_description": "A sensor that returns a random number.",
      "markdown_link": "README.md#model-exampleorghello-worldhello-sensor"
    }
  ],
  "applications": null,
  "markdown_link": "README.md",
  "entrypoint": "bin/hello-world",
  "first_run": "",
  "build": {
    "build": "make module.tar.gz",
    "setup": "make setup",
    "path": "module.tar.gz",
    "arch": ["linux/amd64", "linux/arm64", "darwin/arm64", "windows/amd64"]
  }
}

Save the file.

You can now delete the temporary hello-world/hello-world directory and all its contents.

Implement the components

At this point you have a template for your module.

If you want to see example modules, check out the Viam Registry. Many modules have a linked GitHub repo, where you can see the module’s code. When logged in, you can also download the module’s source code to inspect it.

Generally you will add your custom logic in these files:

FileDescription
/src/models/<model-name>.pySet up the configuration options for the model and implement the API methods for the model.
setup.sh and run.shAdd any logic for installing or running other software for your module.
requirements.txtAdd any Python packages that are required for your module. They will be installed by setup.sh.

Generally you will add your custom logic in these files:

FileDescription
Model file (for example hello-camera.go)Implement the API methods for the model.

Example module: You can view complete example code in the hello-world-module repository on GitHub.

Set up model configuration options

Many resource models have configuration options that allow you to specify options such as:

  • A file path from which to access data
  • A pin to which a device is wired
  • An optional signal frequency to override a default value
  • The name of another resource you wish to use in the model

Model configuration happens in two steps:

1

Validation

The validation step serves two purposes:

  • Confirm that the model configuration contains all required attributes and that these attributes are of the right type.
  • Identify and return a list of names of required resources and a list of names of optional resources. viam-server will pass these resources to the next step as dependencies. For more information, see Module dependencies.

Example module: Imagine how a user might configure the finished camera model. Since the camera model returns an image at a provided path, the configuration must contain a variable to pass in the file path.

{
  "image_path": "/path/to/file"
}

In /src/models/<model-name>.py, edit the validate_config function to:

    @classmethod
    def validate_config(
        cls, config: ComponentConfig
    ) -> Tuple[Sequence[str], Sequence[str]]:
        # Check that a path to get an image was configured
        fields = config.attributes.fields
        if "image_path" not in fields:
            raise Exception("Missing image_path attribute.")
        elif not fields["image_path"].HasField("string_value"):
            raise Exception("image_path must be a string.")

        return [], []

In hello-world/hello-camera.go edit the Validate function to:

func (cfg *Config) Validate(path string) ([]string, []string, error) {
    var deps []string
    if cfg.ImagePath == "" {
        return nil, nil, resource.NewConfigValidationFieldRequiredError(path, "image_path")
    }
    if reflect.TypeOf(cfg.ImagePath).Kind() != reflect.String {
        return nil, nil, errors.New("image_path must be a string.")
    }
    imagePath = cfg.ImagePath
    return deps, []string{}, nil
}

Add the following import at the top of hello-world/hello-camera.go:

"reflect"

For the sensor model, you do not need to edit any of the validation or configuration methods because the sensor has no configurable attributes.

2

Reconfiguration

viam-server calls the reconfigure method when the user adds the model or changes its configuration.

The reconfiguration step serves two purposes:

  • Use the configuration attributes and dependencies to set attributes on the model for usage within the API methods.
  • Obtain access to dependencies. For information on how to use dependencies, see Module dependencies.

Example module: For the camera model, the reconfigure method serves to set the image path for use in API methods.

  1. Open /src/models/hello_camera.py.

  2. Edit the reconfigure function to:

        def reconfigure(
            self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]
        ):
            attrs = struct_to_dict(config.attributes)
            self.image_path = str(attrs.get("image_path"))
    
            return super().reconfigure(config, dependencies)
    
  3. Add the following import to the top of the file:

    from viam.utils import struct_to_dict
    
  1. Open hello-world/hello-camera.go.

  2. Add imagePath = "" to the global variables so you have the following:

    var (
        HelloCamera      = resource.NewModel("exampleorg", "hello-world", "hello-camera")
        errUnimplemented = errors.New("unimplemented")
        imagePath        = ""
    )
    
  3. Edit the type Config struct definition, replacing the comments with the following:

    type Config struct {
        resource.AlwaysRebuild
        ImagePath string `json:"image_path"`
    }
    

    This adds the image_path attribute and causes the resource to rebuild each time the configuration is changed.

Need to maintain state? Click here.

The resource.AlwaysRebuild parameter in the Config struct causes viam-server to fully rebuild the resource each time the user changes the configuration.

If you need to maintain the state of the resource, for example if you are implementing a board and need to keep the software PWM loops running, you can implement this function so viam-server updates the configuration without rebuilding the resource from scratch. In this case, your Reconfigure function should do the following:

  • If you assigned any configuration attributes to global variables, get the values from the latest config object and update the values of the global variables.
  • Assign default values as necessary to any optional attributes if the user hasn’t configured them.

If you create a Reconfigure function, you must also edit the constructor to explicitly call Reconfigure.

For an example that implements the Reconfigure method, see mybase.go on GitHub.

resource.AlwaysRebuild provides an implementation of Reconfigure that returns a NewMustRebuild error. This error doesn’t exist in the other SDKs, so AlwaysRebuild is not supported in those SDKs.

Implement API methods

Depending on the component API you are implementing, you can implement different API methods.

For each API method you want to implement, replace the body of the method with your relevant logic. Make sure you return the correct type in accordance with the function’s return signature. You can find details about the return types at python.viam.dev.

Example module: Implement the camera API and the sensor API:

1

The module generator created a stub for the get_images() function we want to implement in hello-world/src/models/hello_camera.py.

You need to replace raise NotImplementedError() with code to implement the method:

    async def get_images(
        self,
        *,
        filter_source_names: Optional[Sequence[str]] = None,
        extra: Optional[Dict[str, Any]] = None,
        timeout: Optional[float] = None,
        **kwargs
    ) -> Tuple[Sequence[NamedImage], ResponseMetadata]:
        img = Image.open(self.image_path)
        vi_img = pil_to_viam_image(img, CameraMimeType.JPEG)
        named = NamedImage("default", vi_img.data, vi_img.mime_type)
        metadata = ResponseMetadata()
        return [named], metadata

Add the following import to the top of the file:

from viam.media.utils.pil import pil_to_viam_image
from viam.media.video import CameraMimeType
from PIL import Image

Save the file.

2

Leave the rest of the camera API methods unimplemented. They do not apply to this camera.

3

Open requirements.txt. Add the following line:

Pillow
4

Next, implement the sensor API. The module generator created a stub for the get_readings() function we want to implement in hello-world/src/models/hello_sensor.py.

Replace raise NotImplementedError() with code to implement the method:

    async def get_readings(
        self,
        *,
        extra: Optional[Mapping[str, Any]] = None,
        timeout: Optional[float] = None,
        **kwargs
    ) -> Mapping[str, SensorReading]:
        number = random.random()
        return {
            "random_number": number
        }

Add the following import to the top of the file:

import random

Save the file.

5

Leave the rest of the sensor API methods unimplemented.

You may see examples in registry modules that use a different pattern from what the generator creates. For example, some older example modules define async def main() inside main.py. We recommend using the pattern the generator follows:

import asyncio
from viam.module.module import Module
try:
    from models.hello_camera import HelloCamera
except ModuleNotFoundError:
    # when running as local module with run.sh
    from .models.hello_camera import HelloCamera

if __name__ == '__main__':
    asyncio.run(Module.run_from_registry())

A previous version of the CLI module generator created __init__.py files, but now uses a different module structure. We recommend using what the current generator creates rather than old examples that use __init__.py files.

For each API method you want to implement, replace the body of the method with your relevant logic. Make sure you return the correct type in accordance with the function’s return signature. You can find details about the return types at go.viam.com/rdk/components.

Example module: Implement the camera API and the sensor API:

1

The module generator created a stub for the Images function we want to implement in hello-world/hello-camera.go.

You need to replace panic("not implemented") with code to implement the method:

func (s *helloWorldHelloCamera) Images(ctx context.Context, filterSourceNames []string, extra map[string]interface{}) ([]camera.NamedImage, resource.ResponseMetadata, error) {
    var responseMetadataRetVal resource.ResponseMetadata

    imgFile, err := os.Open(imagePath)
    if err != nil {
        return nil, responseMetadataRetVal, errors.New("Error opening image.")
    }
    defer imgFile.Close()

    imgByte, err := os.ReadFile(imagePath)
    if err != nil {
        return nil, responseMetadataRetVal, err
    }

    named, err := camera.NamedImageFromBytes(imgByte, "default", "image/png")
    if err != nil {
        return nil, responseMetadataRetVal, err
    }

    return []camera.NamedImage{named}, responseMetadataRetVal, nil
}
2

Add the following import at the top of hello-world/hello-camera.go:

"os"

Save the file.

3

Leave the rest of the camera API methods unimplemented. They do not apply to this camera.

4

Next, implement the sensor API. The module generator created a stub for the Readings() function we want to implement in hello-world/hello-sensor.go.

Replace panic("not implemented") with code to implement the method:

func (s *helloWorldHelloSensor) Readings(ctx context.Context, extra map[string]interface{}) (map[string]interface{}, error) {
    number := rand.Float64()
    return map[string]interface{}{
        "random_number": number,
    }, nil
}
5

Add the following import to the list of imports at the top of hello-world/hello-sensor.go:

"math/rand"
6

Since errUnimplemented and Config are defined in hello-camera.go, you need to change hello-sensor.go to avoid redeclaring them:

In hello-sensor.go:

  • Delete the "errors" import.

  • Search for and delete the line errUnimplemented = errors.New("unimplemented").

  • Search for type Config struct { and change it to type sensorConfig struct {.

  • Search for all instances of *Config in hello-sensor.go and change them to *sensorConfig.

7

Leave the rest of the sensor API methods unimplemented.

Save the file.

Test your module locally

You can test your module locally before uploading it to the registry. You can configure it in the web UI using the local files on your machine.

Add module to machine

To get your module onto your machine, hot reloading builds and packages it and then uses the shell service to copy it to the machine for testing. If you are using a Python virtual environment (venv), make sure your module files are on the same device where viam-server is running, and add the module manually instead.

Run the following command to build the module and add it to your machine:

viam module reload-local --cloud-config /path/to/viam.json
viam module reload --part-id 123abc45-1234-432c-aabc-z1y111x23a00

For more information, see the viam module documentation.

Reload troubleshooting
  • Error: Could not connect to machine part: context deadline exceeded; context deadline exceeded; mDNS query failed to find a candidate

    Try specifying the --part-id, which you can find by clicking the Live indicator on your machine’s page and clicking Part ID.

  • Error: Rpc error: code = Unknown desc = stat /root/.viam/packages-local: no such file or directory

    Try specifying the --home directory, for example /Users/yourname/ on macOS.

  • Error: Error while refreshing token, logging out. Please log in again

    Run viam login to reauthenticate the CLI.

Try using a different command

If you are still having problems with the reload command, you can use a different, slower method of rebuilding and then restarting the module. Run the following command to rebuild your module:

viam module build local

Then restart it on your machine’s CONFIGURE tab. In the upper-right corner of the module’s card, click the menu, then click Restart.

Module menu.

Navigate to your machine’s CONFIGURE page.

Click the + button, select Local module, then again select Local module.

Enter the path to the automatically-generated run.sh script. Click Create. For local modules, viam-server uses this path to start the module.

Example module: For the hello-world module, the path should resemble /home/yourname/hello-world/run.sh on Linux, or /Users/yourname/hello-world/run.sh on macOS.

Save the config.

From within the module directory, compile your module with the module build command into a single executable:

viam module build local

Click the + button, select Local module, then again select Local module.

Enter the path to the /bin/<module-name> executable. For local modules, viam-server uses this path to start the module.

Example module: For the hello-world module, the path should resemble /home/yourname/hello-world/bin/hello-world.

Click Create.

Save the config.

Add local model

1

Configure the model provided by your module

On your machine’s CONFIGURE page, click +, click Local module, then click Local component or Local service.

Select or enter the model namespace triplet, for example exampleorg:hello-world:hello-camera. You can find the triplet in the model field of your meta.json file.

Select the Type corresponding to the API you implemented.

Enter a Name such as camera-1. Click Create.

2

Configure attributes

When you add a new component or service, a panel appears for it on the CONFIGURE tab. If your model has required or optional attributes, configure them in the configuration field by adding them inside the {} object.

Example module: For the camera model, add the image_path attribute by replacing {} with:

{
  "image_path": "<replace with the path to your image>"
}
3

Save the config and wait a few seconds for it to apply.

Then click the TEST section of the camera’s configuration card. If there are errors you will see them on the configuration panel and on the LOGS tab.

4

Test the component

Click the TEST bar at the bottom of your modular component configuration, and check whether it works as expected.

Example module: For the camera model, the test panel should show the image:

The configuration interface with the Test section of the camera card open, showing a hello world image.

If you also implemented the sensor model, add and test it the same way.

5

Iterate

If your component works, you’re almost ready to share your module by uploading it to the registry. If not, you have some debugging to do.

Each time you make changes to your local module code, you need to update its instance on your machine:

Run the reload command again to rebuild and restart your module:

viam module reload-local --cloud-config /path/to/viam.json
viam module reload --part-id 123abc45-1234-432c-aabc-z1y111x23a00

As you iterate, save the code changes, then restart the module in your machine’s CONFIGURE tab: In the upper-right corner of the module’s card, click menu, then click Restart.

Module menu.

Run the following command to rebuild your module:

viam module build local

Then restart it in your machine’s CONFIGURE tab. In the upper-right corner of the module’s card, click menu, then click Restart.

Module menu.

Next steps

Once you have thoroughly tested your module, continue to package and deploy it.