Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOCS-3172: Change references to old module generator to CLI #3745

Merged
merged 10 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/how-tos/create-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ In this step, you will code the logic that is unique to your model.

{{% alert title="Tip (optional)" color="tip" %}}

If you are using Golang, use the [Golang Module templates](https://github.com/viam-labs/module-templates-golang) which contain detailed instructions for creating your module.
If you are using Python or Golang, use the [`viam module generate` CLI command](/cli/#module) to generate the scaffolding for a module with one resource model.

If you are using Python, you can use the [Viam module generator](https://github.com/viam-labs/generator-viam-module/) to generate the scaffolding for a module with one resource model.
For a step-by-step guide that uses the CLI module generator, see [Create a Hello World module](/how-tos/hello-world-module/).

{{% /alert %}}

Expand Down
2 changes: 1 addition & 1 deletion docs/how-tos/hello-world-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ To package (for Python) and upload your module and make it available to configur
{{< tabs >}}
{{% tab name="Python" %}}

1. Package the module as an archive, run the following command from inside the <file>hello-world</file> directory:
1. To package the module as an archive, run the following command from inside the <file>hello-world</file> directory:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
tar -czf module.tar.gz run.sh setup.sh requirements.txt src
Expand Down
215 changes: 97 additions & 118 deletions docs/how-tos/sensor-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,29 @@ Making a module to support your sensor will allow you to use it with Viam's data
1. [Start with a test script](#start-with-a-test-script)
1. [Generate template module code](#generate-template-module-code)
1. [Implement the sensor API](#implement-the-sensor-api)
1. [Make the module executable](#make-the-module-executable)
1. [Test your module locally](#test-your-module-locally)
1. [Upload your module](#upload-your-module-to-the-registry)

{{% /alert %}}

## Prerequisites

{{< expand "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.

{{< readfile "/static/include/how-to/install-cli.md" >}}

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

{{< readfile "/static/include/how-to/auth-cli.md" >}}
{{< /expand >}}

{{% expand "Install viam-server on your computer and connect to the Viam app" %}}

{{% snippet "setup.md" %}}

{{% /expand%}}

## Start with a test script

Start by getting a test script working so you can check that your sensor code itself works before packaging it into a module.
Expand Down Expand Up @@ -143,71 +160,28 @@ Run your test script from your terminal and make sure you are able to get readin
There are a few standardized files that must be part of any module.
You can create these automatically using the Viam module generator:

{{< table >}}
{{% tablestep %}}
**1. Install the module generator**

Install the [module generator](https://github.com/viam-labs/generator-viam-module/):

1. Install node (version 16 or later) and npm if you don't already have them:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install 16
nvm use 16
```

1. Install Yeoman:
1. Run the `module generate` command in your terminal:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
npm install -g yo
viam module generate
```

1. Now install the module generator:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
npm install -g generator-viam-module
```

{{% /tablestep %}}
{{% tablestep %}}
**2. Run the generator**

Go to the directory where you want to create your module and run the generator:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
yo viam-module
```

{{% /tablestep %}}
{{% tablestep %}}
**3. Name your model**

When prompted for a model triplet, use `<your organization public namespace>:<repo name>:<what you want to call your sensor model>`.
For example, `jessamy:weather:meteo_PM`.

- You can find your organization namespace by going to your organization settings in the [Viam app](https://app.viam.com).
- The repo name (also called family name) is generally the name of the GitHub repo where you will put your module code.
Name it something related to what your module does.
- Name your sensor 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.

For more information, see [Name your new resource model](/how-tos/create-module/#name-your-new-resource-model).

{{% /tablestep %}}
{{% tablestep %}}
**4. Specify the API**

For the API triplet, enter `rdk:component:sensor`.
This means that you are implementing the standard Viam sensor API.

When asked whether this is a Viam SDK built-in API, enter `yes`.
2. Follow the prompts, naming your module and selecting from the options.

{{% /tablestep %}}
{{< /table >}}
<br>
<!--prettier-ignore-->
| Prompt | Description |
| -------| ----------- |
| Module name | The module name describes the module or the family of devices it supports. It is generally the same as the name of the GitHub repo where you will put your module code. For example, `weather`. |
| Language | The language for the module. To follow this guide, choose Python. |
| Visibility | Choose `Private` to share only with your organization, or `Public` to share publicly with all organizations. If you are testing, choose `Private`. |
| Namespace/Organization ID | In the [Viam app](https://app.viam.com), navigate to your organization settings through the menu in upper right corner of the page. Find the **Public namespace** and copy that string. In the example snippets below, the namespace is `jessamy`. |
| Resource to add to the module (API) | The [component API](/appendix/apis/#component-apis) or [service API](/appendix/apis/#service-apis) the resource you're creating implements. Choose `Sensor Component` for this guide. |
| Model name | Name your sensor 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. |
| Enable cloud build | You can select `No` for this guide because you'll build the module yourself before uploading it. If you select `Yes` and push the generated files (including the <file>.github</file> folder) and create a release of the format `vX.X.X`, the module will build and upload to the Viam registry. |
| Register module | Select `Yes` unless you are creating a local-only module for testing purposes and do not intend to upload it. |

The generator creates a `run.sh` file, a `requirements.txt` file, a readme, and source code files.
In the next section, you'll customize some of these files to support your sensor.
The generator will create a folder containing stub files for your modular sensor component.
In the next section, you'll customize some of the generated files to support your sensor.

## Implement the sensor API

Expand All @@ -218,18 +192,19 @@ You need to implement this method so your sensor supports the sensor API:
{{% tablestep %}}
**1. Edit configuration code**

In the generated <file>/YOUR_MODULE_NAME/src/</file> directory, open the </file>MODEL_NAME.py</file> file.
In the generated <file>/YOUR_MODULE_NAME/src/</file> directory, open the </file>main.py</file> file.

Edit the config attributes to fit your sensor.
For example, if your sensor requires two pins, copy the `some_pin` lines and add another pin with a different name.
If you want to be able to configure something else, for example the location to get online data from, you can add attributes for that.
If your sensor doesn't require any configuration, delete the `some_pin` lines but don't delete the `validate` and `reconfigure` functions entirely; they're needed for the module to function even if they don't actually validate the input or reconfigure the resource.
For example, if your sensor requires two pins, edit the validate function to check that they are configured.
Edit the reconfigure function to get the configured values of each parameter from the configuration.
If you want to be able to configure something else, for example the location to get online data from, you can add attributes for that (see example code in the expander below).
If your sensor doesn't require any configuration, leave the `validate` and `reconfigure` functions as they are; they're needed for the module to function even if they don't actually validate the input or reconfigure the resource.

{{% /tablestep %}}
{{% tablestep %}}
**2. Define `get_readings`**

In the `get_readings` function definition, paste your test script.
In the `get_readings` function definition, replace `raise NotImplementedError()` by pasting your test script.
Edit the script to return a dictionary of readings instead of printing them.
Be sure to add any required imports to the top of the file.

Expand All @@ -248,52 +223,45 @@ Be sure to add any required imports to the top of the file.
The following code puts the functionality of the [example test script](#start-with-a-test-script) into the `get_readings` function definition.

```python {class="line-numbers linkable-line-numbers"}
# meteo_PM.py
from typing import ClassVar, Mapping, Any, Optional
from typing_extensions import Self
import asyncio
from typing import Any, ClassVar, Final, Mapping, Optional, Sequence

from viam.utils import SensorReading, struct_to_dict
from viam.module.types import Reconfigurable
from typing_extensions import Self
from viam.components.sensor import Sensor
from viam.module.module import Module
from viam.proto.app.robot import ComponentConfig
from viam.proto.common import ResourceName
from viam.resource.base import ResourceBase
from viam.resource.easy_resource import EasyResource
from viam.resource.types import Model, ModelFamily
from viam.components.sensor import Sensor
from viam.logging import getLogger
from viam.utils import SensorReading, struct_to_dict

import openmeteo_requests
import requests_cache
from retry_requests import retry

LOGGER = getLogger(__name__)


class meteo_PM(Sensor, Reconfigurable):

"""
Sensor represents a sensing device that can provide measurement readings.
"""

class Meteopm(Sensor, EasyResource):
MODEL: ClassVar[Model] = Model(
ModelFamily("jessamy", "weather"), "meteo_PM")

# Class parameters
latitude: float # Latitude at which to get data
longitude: float # Longitude at which to get data

# Constructor
@classmethod
def new(
cls, config: ComponentConfig,
dependencies: Mapping[ResourceName, ResourceBase]
) -> Self:
my_class = cls(config.name)
my_class.reconfigure(config, dependencies)
return my_class

# Validates JSON Configuration
cls, config: ComponentConfig, dependencies: Mapping[
ResourceName, ResourceBase]
) -> Self:
"""This method creates a new instance of this Sensor component.
The default implementation sets the name from the `config` parameter
and then calls `reconfigure`.
"""
return super().new(config, dependencies)

@classmethod
def validate(cls, config: ComponentConfig):
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
"""This method allows you to validate the configuration object
received from the machine, as well as to return any implicit
dependencies based on that `config`.
"""
fields = config.attributes.fields
# Check that configured fields are floats
if "latitude" in fields:
Expand All @@ -303,28 +271,31 @@ class meteo_PM(Sensor, Reconfigurable):
if "longitude" in fields:
if not fields["longitude"].HasField("number_value"):
raise Exception("Longitude must be a float.")
return
return []

# Handles attribute reconfiguration
def reconfigure(
self, config: ComponentConfig,
dependencies: Mapping[ResourceName, ResourceBase]
):
self, config: ComponentConfig, dependencies: Mapping[
ResourceName, ResourceBase]
):
"""This method allows you to dynamically update your service
when it receives a new `config` object.
"""
attrs = struct_to_dict(config.attributes)

self.latitude = float(attrs.get("latitude", 45))
LOGGER.debug("Using latitude: " + str(self.latitude))
self.logger.debug("Using latitude: " + str(self.latitude))

self.longitude = float(attrs.get("longitude", -121))
LOGGER.debug("Using longitude: " + str(self.longitude))

return
self.logger.debug("Using longitude: " + str(self.longitude))
return super().reconfigure(config, dependencies)

async def get_readings(
self, *, extra: Optional[Mapping[str, Any]] = None,
timeout: Optional[float] = None, **kwargs
self,
*,
extra: Optional[Mapping[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs
) -> Mapping[str, SensorReading]:

# Set up the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession(
'.cache', expire_after=3600)
Expand All @@ -351,13 +322,17 @@ class meteo_PM(Sensor, Reconfigurable):
current_pm10 = current.Variables(0).Value()
current_pm2_5 = current.Variables(1).Value()

LOGGER.info(current_pm2_5)
self.logger.info(current_pm2_5)

# Return a dictionary of the readings
return {
"pm2_5": current_pm2_5,
"pm10": current_pm10
}


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

{{< /expand >}}
Expand All @@ -370,15 +345,6 @@ Most modules have their implementation code linked on their module page, so you
Update the generated <file>requirements.txt</file> file to include any packages that must be installed for the module to run.
Depending on your use case, you may not need to add anything here beyond `viam-sdk` which is auto-populated.

## Make the module executable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call


You need an executable file so that `viam-server` can run your module.
The module generator already created the <file>run.sh</file> "entrypoint" file for you, so all you need to do is make this file executable by running the following command with your correct file path:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
sudo chmod +x <your-file-path-to>/run.sh
```

## Test your module locally

{{% expand "Prerequisite: A running machine connected to the Viam app." %}}
Expand All @@ -394,7 +360,20 @@ It's a good idea to test your module locally before uploading it to the [Viam Re

{{< table >}}
{{% tablestep link="/how-tos/create-module/#test-your-module-locally" %}}
**1. Configure your local module on a machine**
**1. Set up a virtual environment**

Create a virtual Python environment with the necessary packages by running the setup file from within the <file>hello-world</file> directory:

```sh {id="terminal-prompt" class="command-line" data-prompt="$"}
sh setup.sh
```

This environment is where the local module will run.
`viam-server` does not need to run inside this environment.

{{% /tablestep %}}
{{% tablestep %}}
**2. Configure your local module on a machine**

On your machine's **CONFIGURE** tab in the [Viam app](https://app.viam.com), click the **+** (create) icon in the left-hand menu.
Select **Local module**, then **Local module**.
Expand All @@ -403,7 +382,7 @@ Type in the _absolute_ path on your machine's filesystem to your module's execut
Click **Create**.

{{% /tablestep %}}
{{% tablestep link="/how-tos/create-module/#test-your-module-locally" %}}
{{% tablestep %}}
**2. Configure the model provided by your module**

Click the **+** button again, this time selecting **Local module** and then **Local component**.
Expand Down
Loading