diff --git a/docs/cli.md b/docs/cli.md
index baec5784bb..933d3b6448 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -628,7 +628,7 @@ This includes:
- Building your module for different architectures using cloud runners
- Building a module locally and running it on a target device. Rebuilding & restarting if already running.
-See [Upload a custom module](/how-tos/upload-module/) and [Update an existing module](/how-tos/manage-modules/#update-an-existing-module) for more information.
+See [Upload a module](/how-tos/upload-module/) and [Update an existing module](/how-tos/manage-modules/#update-an-existing-module) for more information.
If you update and release your module as part of a continuous integration (CI) workflow, you can also
[automatically upload new versions of your module on release](/how-tos/manage-modules/#update-an-existing-module-using-a-github-action) using a GitHub Action.
@@ -718,7 +718,7 @@ viam module upload --version=1.0.0 --platform=darwin/arm64 packaged-module.tar.g
| `--org-id` | The organization ID to associate the module to. See [Using the `--org-id` argument](#using-the---org-id-and---public-namespace-arguments) | `create`, `upload` | **Required** |
| `--public-namespace` | The [namespace](/cloud/organizations/#create-a-namespace-for-your-organization) to associate the module to. See [Using the `--public-namespace` argument](#using-the---org-id-and---public-namespace-arguments) | `create`, `upload` | **Required** |
| `--platform` | The architecture of your module binary. See [Using the `--platform` argument](#using-the---platform-argument) | `upload`, `build logs` | **Required** |
-| `--tags` | Comma-separated list of platform tags that determine to which this binary can be deployed. Examples: `distro:debian,distro:ubuntu, os_version:22.04,os_codename:jammy`. For a machine to use an uploaded binary, all tags must be satisfied as well as the `--platform` field.
- `distro`: Distribution. You can find this in `/etc/os-release`. `"debian"` or `"ubuntu"`.
- `os_version`: Operating System version. On Linux, you can find this in `/etc/os-release`. Example for linux: `22.04`. On Mac, run `sw_vers --productVersion` and use the major version only. Example for mac: `14`.
- `codename`: The operating system codename. Find this in `/etc/os-release`. For example: `"bullseye"`, `"bookworm"`, or `"jammy"`.
- `cuda`: Whether using CUDA compiler. Run `nvcc --version`. For example: `"true"`.
- `cuda_version`: The CUDA compiler version. Run `nvcc --version`. For example: `"11"` or `"12"`.
- `jetpack`: Version of the NVIDIA JetPack SDK. Run `apt-cache show nvidia-jetpack`. For example: `"5"`.
- `pi`: Version of the raspberry pi: `"4"` or `"5"`.
- `pifull`: Compute module or model number, for example `cm5p` or `5B`.
| `upload` | Optional |
+| `--tags` | Comma-separated list of platform tags that determine to which platforms this binary can be deployed. Examples: `distro:debian,distro:ubuntu, os_version:22.04,os_codename:jammy`. For a machine to use an uploaded binary, all tags must be satisfied as well as the `--platform` field. - `distro`: Distribution. You can find this in `/etc/os-release`. `"debian"` or `"ubuntu"`.
- `os_version`: Operating System version. On Linux, you can find this in `/etc/os-release`. Example for linux: `22.04`. On Mac, run `sw_vers --productVersion` and use the major version only. Example for mac: `14`.
- `codename`: The operating system codename. Find this in `/etc/os-release`. For example: `"bullseye"`, `"bookworm"`, or `"jammy"`.
- `cuda`: Whether using CUDA compiler. Run `nvcc --version`. For example: `"true"`.
- `cuda_version`: The CUDA compiler version. Run `nvcc --version`. For example: `"11"` or `"12"`.
- `jetpack`: Version of the NVIDIA JetPack SDK. Run `apt-cache show nvidia-jetpack`. For example: `"5"`.
- `pi`: Version of the raspberry pi: `"4"` or `"5"`.
- `pifull`: Compute module or model number, for example `cm5p` or `5B`.
| `upload` | Optional |
| `--version` | The version of your module to set for this upload. See [Using the `--version` argument](#using-the---version-argument) | `upload` | **Required** |
| `--wait` | Wait for the build to finish before outputting any logs | `build logs` | Optional |
@@ -863,7 +863,7 @@ In the example above, the model namespace is set to `acme` to match the owning o
If the two namespaces do not match, the command will return an error.
{{% /alert %}}
-See [Upload a custom module](/how-tos/upload-module/) and [Update an existing module](/how-tos/manage-modules/#update-an-existing-module) for a detailed walkthrough of the `viam module` commands.
+See [Upload a module](/how-tos/upload-module/) and [Update an existing module](/how-tos/manage-modules/#update-an-existing-module) for a detailed walkthrough of the `viam module` commands.
See [Modular resources](/registry/) for a conceptual overview of modules and the modular resource system at Viam.
diff --git a/docs/how-tos/hello-world-module.md b/docs/how-tos/hello-world-module.md
index 7ebcc44c08..8bc6242c54 100644
--- a/docs/how-tos/hello-world-module.md
+++ b/docs/how-tos/hello-world-module.md
@@ -79,27 +79,43 @@ print("Hello, World! The latest random number is ", number, ".")
```
{{% /tab %}}
-
-
-
+{{% /tab %}}
{{< /tabs >}}
1. Replace the path in the script above with the path to where you saved your photo.
@@ -120,16 +136,25 @@ pip install Pillow
python3 test.py
```
+The image you saved should open on your screen, and a random number should print to your terminal.
+
+In later steps, the module generator will create a new virtual environment with required dependencies, so you can deactivate the one you just ran the test script in:
+
+```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
+deactivate
+```
+
{{% /tab %}}
-{{< /tabs >}}
+{{% tab name="Go" %}}
- The image you saved should open on your screen, and a random number should print to your terminal.
+```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
+go run test.go
+```
-1. In later steps, the module generator will create a new virtual environment with required dependencies, so you can deactivate the one you just ran the test script in:
+The image you saved should open on your screen, and a random number should print to your terminal.
- ```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
- deactivate
- ```
+{{% /tab %}}
+{{< /tabs >}}
## Choose an API to implement
@@ -180,7 +205,7 @@ First let's generate the camera component files, and we'll add the sensor code l
We will add the sensor later.
- Model name: `hello-camera`
- Enable cloud build: `No`
- - Register module: `No`
+ - Register module: `Yes`
1. Hit your Enter key and the generator will generate a folder called hello-world containing stub files for your modular camera component.
@@ -210,10 +235,13 @@ You need to add some sensor-specific code to support the sensor component.
- Enable cloud build: `No`
- Register module: `No`
-1. Open temporary/src/main.py.
- Copy the sensor class definition, from `class HelloSensor(Sensor, EasyResource)` through the `get_readings()` function definition (lines 15-65).
+{{< tabs >}}
+{{% tab name="Python" %}}
- Open the hello-world/src/main.py file you generated earlier, and paste the sensor class definition in after the camera class definition, above `if __name__ == "__main__":`.
+3. Open temporary/src/main.py.
+ Copy the sensor class definition, from `class HelloSensor(Sensor, EasyResource)` through the `get_readings()` function definition (lines 15-65).
+
+ Open the hello-world/src/main.py file you generated earlier, and paste the sensor class definition in after the camera class definition, above `if __name__ == "__main__":`.
1. Change `temporary` to `hello-world` in the ModelFamily line, so you have, for example:
@@ -230,8 +258,82 @@ You need to add some sensor-specific code to support the sensor component.
Save the hello-world/src/main.py file.
-1. Open temporary/meta.json and copy the model information.
- For example:
+{{% /tab %}}
+{{% tab name="Go" %}}
+
+3. Edit the file structure:
+
+ 1. Change the name of hello-world/models/module.go to hello-camera.go.
+
+ 1. Change the name of temporary/models/module.go to hello-sensor.go.
+ Move the hello-sensor.go folder from temporary/models/ to /hello-world/models/.
+
+1. Open hello-world/main.go.
+ You need to add the necessary imports and define how it adds the sensor model from the registry.
+ Delete all the contents and replace them with the following:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="29"}
+ package main
+
+ import (
+ "context"
+ "hello-world/models"
+
+ "go.viam.com/rdk/components/camera"
+ "go.viam.com/rdk/components/sensor"
+ "go.viam.com/rdk/logging"
+ "go.viam.com/rdk/module"
+ "go.viam.com/utils"
+ )
+
+ func main() {
+ utils.ContextualMain(mainWithArgs, module.NewLoggerFromArgs("hello-world"))
+ }
+
+ func mainWithArgs(ctx context.Context, args []string, logger logging.Logger) error {
+ helloWorld, err := module.NewModuleFromArgs(ctx)
+ if err != nil {
+ return err
+ }
+
+ if err = helloWorld.AddModelFromRegistry(ctx, camera.API, models.HelloCamera); err != nil {
+ return err
+ }
+
+ if err = helloWorld.AddModelFromRegistry(ctx, sensor.API, models.HelloSensor); err != nil {
+ return err
+ }
+
+ err = helloWorld.Start(ctx)
+ defer helloWorld.Close(ctx)
+ if err != nil {
+ return err
+ }
+
+ <-ctx.Done()
+ return nil
+ }
+ ```
+
+ Save the file.
+
+1. Change all instances of `temporary` in hello-world/models/hello-sensor.go:
+
+ 1. Edit `temporary` to `hello-world` on line 14, so it looks like this (but with your org ID in place of `jessamy`):
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="14"}
+ HelloSensor = resource.NewModel("jessamy", "hello-world", "hello-sensor")
+ ```
+
+ 1. Change both instances of `newTemporaryHelloSensor` to `newHelloWorldHelloSensor`.
+
+ 1. Search for any other instances of `temporary` in the rest of the hello-world/models/hello-sensor.go and replace each with `helloWorld`.
+
+{{% /tab %}}
+{{< /tabs >}}
+
+6. Open temporary/meta.json and copy the model information.
+ For example:
```json {class="line-numbers linkable-line-numbers" data-start="8"}
{
@@ -240,11 +342,11 @@ You need to add some sensor-specific code to support the sensor component.
}
```
-1. Open hello-world/meta.json and paste the sensor model into the model list.
+1. Open hello-world/meta.json and paste the sensor model into the model list.
- Edit the `description` to accurately include both models.
+ Edit the `description` to include both models.
- Change `temporary` to `hello-world`.
+ Change `temporary` to `hello-world`.
The file should now resemble the following:
@@ -390,6 +492,169 @@ You don't need to edit any of the validate or configuration methods because you'
{{< /expand >}}
+{{% /tab %}}
+{{% tab name="Go" %}}
+
+### Implement the camera API
+
+First, implement the camera API methods by editing the camera class definition:
+
+1. Add the following to the list of imports at the top of hello-world/models/hello-camera.go:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="6"}
+ "os"
+ "reflect"
+ "io/ioutil"
+ ```
+
+1. Add `imagePath = ""` to the global variables so you have the following:
+
+ ```go {class="line-numbers linkable-line-numbers" data-line="22" data-start="19" data-line-offset="19"}
+ var (
+ HelloCamera = resource.NewModel("jessamy", "hello-world", "hello-camera")
+ errUnimplemented = errors.New("unimplemented")
+ imagePath = ""
+ )
+ ```
+
+1. In the test script you hard-coded the path to the image.
+ For the module, let's make the path a configurable attribute so you or other users of the module can set the path from which to get the image.
+
+ Edit the `type Config struct` definition, replacing the comments with the following:
+
+ ```go {class="line-numbers" data-start="33"}
+ 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.
+
+1. We are not providing a default image but rely on the end user to supply a valid path to an image when configuring the resource.
+ This means `image_path` is a required attribute.
+ Replace the `Validate` function with the following code to throw an error if `image_path` isn't configured or isn't a string:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="38"}
+ func (cfg *Config) Validate(path string) ([]string, error) {
+ var deps []string
+ if cfg.ImagePath == "" {
+ return nil, resource.NewConfigValidationFieldRequiredError(path, "image_path")
+ }
+ if reflect.TypeOf(cfg.ImagePath).Kind() != reflect.String {
+ return nil, errors.New("image_path must be a string.")
+ }
+ imagePath = cfg.ImagePath
+ return deps, nil
+ }
+ ```
+
+1. The module generator created a stub for the `Image` function we want to implement:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="103" }
+ func (s *helloWorldHelloCamera) Image(ctx context.Context, mimeType string, extra map[string]interface{}) ([]byte, camera.ImageMetadata, error) {
+ panic("not implemented")
+ }
+ ```
+
+ You need to replace `panic("not implemented")` with code to actually implement the method:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="104" }
+ imgFile, err := os.Open(imagePath)
+ if err != nil {
+ return nil, camera.ImageMetadata{}, errors.New("Error opening image.")
+ }
+ defer imgFile.Close()
+ imgByte, err := ioutil.ReadFile(imagePath)
+ return imgByte, camera.ImageMetadata{}, nil
+ ```
+
+1. Delete the `SubscribeRTP` and `Unsubscribe` methods, since they are not applicable to this camera.
+
+1. You can leave the rest of the functions not implemented, because this module is not meant to return a point cloud (`NextPointCloud`), and does not need to return multiple images simultaneously (`Images`).
+ If this camera returned a camera stream instead of a single static file, we would have implemented `Stream` instead of `Read`.
+
+ However, you do need to edit the return statements to return empty structs that match the API.
+ Edit these methods so they look like this:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="110" }
+ func (s *helloWorldHelloCamera) NewClientFromConn(ctx context.Context, conn rpc.ClientConn, remoteName string, name resource.Name, logger logging.Logger) (camera.Camera, error) {
+ return nil, errors.New("not implemented")
+ }
+
+ func (s *helloWorldHelloCamera) Stream(ctx context.Context, errHandlers ...gostream.ErrorHandler) (gostream.VideoStream, error) {
+ return nil, errors.New("not implemented")
+ }
+
+ func (s *helloWorldHelloCamera) Images(ctx context.Context) ([]camera.NamedImage, resource.ResponseMetadata, error) {
+ return []camera.NamedImage{}, resource.ResponseMetadata{}, errors.New("not implemented")
+ }
+
+ func (s *helloWorldHelloCamera) NextPointCloud(ctx context.Context) (pointcloud.PointCloud, error) {
+ return nil, errors.New("not implemented")
+ }
+
+ func (s *helloWorldHelloCamera) Properties(ctx context.Context) (camera.Properties, error) {
+ return camera.Properties{}, errors.New("not implemented")
+ }
+
+ func (s *helloWorldHelloCamera) DoCommand(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error) {
+ return map[string]interface{}{}, errors.New("not implemented")
+ }
+ ```
+
+1. Save the file.
+
+### Implement the sensor API
+
+{{< expand "Click if you are also creating a sensor component" >}}
+
+Now edit the sensor class definition to implement the sensor API.
+You don't need to edit any of the validate or configuration methods because you're not adding any configurable attributes for the sensor model.
+
+1. Add `"math/rand"` to the list of imports in hello-sensor.go for the random number generation.
+
+1. Since `errUnimplemented` and `Config` are defined in hello-camera.go, you need to change hello-sensor.go to avoid redeclaring them:
+
+ - Delete line 16, `errUnimplemented = errors.New("unimplemented")` from hello-sensor.go.
+
+ - On line 27, change `type Config struct {` to `type sensorConfig struct {`.
+
+ - Search for all instances of `*Config` in hello-sensor.go and change them to `*sensorConfig`.
+
+1. The sensor API only has one resource-specific method, `Readings`:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="93" }
+ func (s *helloWorldHelloSensor) Readings(ctx context.Context, extra map[string]interface{}) (map[string]interface{}, error) {
+ panic("not implemented")
+ }
+ ```
+
+ Replace `panic("not implemented")` with the following code:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="94" }
+ number := rand.Float64()
+ return map[string]interface{}{
+ "random_number": number,
+ }, nil
+ ```
+
+1. In the `NewClientFromConn` definition, replace `panic("not implemented")` with the following:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="90"}
+ return nil, errUnimplemented
+ ```
+
+1. In the `DoCommand` definition, replace `panic("not implemented")` with the following:
+
+ ```go {class="line-numbers linkable-line-numbers" data-start="101"}
+ return map[string]interface{}{}, errors.New("not implemented")
+ ```
+
+1. Save the file.
+
+{{< /expand >}}
+
{{% /tab %}}
{{< /tabs >}}
@@ -397,6 +662,9 @@ You don't need to edit any of the validate or configuration methods because you'
With the implementation written, it's time to test your module locally:
+{{< tabs >}}
+{{% tab name="Python" %}}
+
1. Create a virtual Python environment with the necessary packages by running the setup file from within the hello-world directory:
```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
@@ -406,7 +674,20 @@ With the implementation written, it's time to test your module locally:
This environment is where the local module will run.
`viam-server` does not need to run inside this environment.
-1. Make sure your machine's instance of `viam-server` is live and connected to the [Viam app](https://app.viam.com).
+{{% /tab %}}
+{{% tab name="Go" %}}
+
+1. From within the hello-world directory, compile your module into a single executable:
+
+ ```sh {class="command-line" data-prompt="$" data-output="5-10"}
+ make setup
+ make build
+ ```
+
+{{% /tab %}}
+{{< /tabs >}}
+
+2. Make sure your machine's instance of `viam-server` is live and connected to the [Viam app](https://app.viam.com).
1. In the Viam app, navigate to your machine's **CONFIGURE** page.
@@ -478,7 +759,10 @@ The hello world module you created is for learning purposes, not to provide any
{{< /expand >}}
-To package and upload your module and make it available to configure on machines in your organization:
+To package (for Python) and upload your module and make it available to configure on machines in your organization:
+
+{{< tabs >}}
+{{% tab name="Python" %}}
1. Package the module as an archive, run the following command from inside the hello-world directory:
@@ -494,10 +778,23 @@ To package and upload your module and make it available to configure on machines
viam module upload --version 1.0.0 --platform any module.tar.gz
```
-1. Now, if you look at the [Viam Registry page](https://app.viam.com/registry) while logged into your account, you'll be able to find your private module listed.
- You can configure the hello-sensor and hello-camera on your machines just as you would configure other components and services; there's no more need for local module configuration.
+{{% /tab %}}
+{{% tab name="Go" %}}
+
+From within your hello-world directory, run the `viam module upload` CLI command to upload the module to the registry:
+
+```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
+viam module upload --version 1.0.0 --platform any .
+```
+
+{{% /tab %}}
+{{< /tabs >}}
+
+Now, if you look at the [Viam Registry page](https://app.viam.com/registry) while logged into your account, you'll be able to find your private module listed.
+Because the module is now in the registry, you can configure the hello-sensor and hello-camera on your machines just as you would configure other components and services; there's no more need for local module configuration.
+The local module configuration is primarily for testing purposes.
- ![The create a component menu open, searching for hello. The hello-camera and hello-sensor components are shown in the search results.](/how-tos/hello-config.png)
+![The create a component menu open, searching for hello. The hello-camera and hello-sensor components are shown in the search results.](/how-tos/hello-config.png)
For more information about uploading modules, see [Upload a module](/how-tos/upload-module/).
diff --git a/docs/how-tos/upload-module.md b/docs/how-tos/upload-module.md
index 5f580f555c..5894f3b9e4 100644
--- a/docs/how-tos/upload-module.md
+++ b/docs/how-tos/upload-module.md
@@ -111,7 +111,7 @@ Edit the newly-created `meta.json` file, and provide the required configuration
module_id |
string |
Required |
- The module ID, which includes either the module namespace or organization-id, followed by its name (pre-populated using the --name you provided in the viam module create command).
+ | The module ID, which includes either the module namespace or organization ID, followed by its name (pre-populated using the --name you provided in the viam module create command).
Caution
@@ -264,7 +264,7 @@ where:
You can only provide one `platform` argument at a time to the `viam module upload` command.
See [Using the `--platform` argument](/cli/#using-the---platform-argument) for the full list of supported architectures.
- `module-path`: provide the path to the file, directory, or compressed archive (with `.tar.gz` or `.tgz` extension) that contains your custom module code.
-- `tags`: provide a comma-separated list of platform tags that determine to which this binary can be deployed. See [`viam module upload`](/cli/#module).
+- `tags`: provide a comma-separated list of platform tags that determine to which platforms this binary can be deployed. See [`viam module upload`](/cli/#module).
{{% alert title="Important" color="note" %}}
The `viam module upload` command only supports one `platform` argument at a time.
@@ -275,19 +275,19 @@ The Viam Registry page for your module displays the platforms your module suppor
For example:
-- To upload a custom module that is defined in a single file named `my-module-file` in a local `bin` directory:
+- To upload a module that is defined in a single file named `my-module-file` in a local `bin` directory:
```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
viam module upload --version 1.0.0 --platform linux/amd64 ./bin/my-module-file
```
-- To upload a custom module that includes multiple files, as well as a separate entry point file, all contained with a local `bin` directory:
+- To upload a module that includes multiple files, as well as a separate entry point file, all contained with a local `bin` directory:
```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
viam module upload --version 1.0.0 --platform linux/amd64 ./bin
```
-- To upload a custom module that has been compressed as an archive named `packaged-module.tar.gz`:
+- To upload a module that has been compressed as an archive named `packaged-module.tar.gz`:
```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
viam module upload --version 1.0.0 --platform linux/amd64 packaged-module.tar.gz
|