Skip to content
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
516 changes: 268 additions & 248 deletions docs/examples/example.ipynb

Large diffs are not rendered by default.

20 changes: 14 additions & 6 deletions docs/examples/my_cool_arm.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# my-python-robot/my_cool_arm.py

import asyncio
import os
import json
from typing import Any, Dict, Optional, Tuple

from viam.components.arm import Arm, JointPositions, KinematicsFileFormat, Pose
from viam.operations import run_with_operation

Expand All @@ -25,6 +26,17 @@ def __init__(self, name: str):
# Starting joint positions
self.joint_positions = JointPositions(values=[0, 0, 0, 0, 0, 0])
self.is_stopped = True

# Minimal working kinematics model
self.kinematics = json.dumps(
{
"name": "MyArm",
"links": [{"id": "base", "parent": "world", "translation": {"x": 0, "y": 0, "z": 0}}],
"joints": [
{"id": "waist", "type": "revolute", "parent": "base", "axis": {"x": 0, "y": 0, "z": 1}, "max": 359, "min": -359}
],
}
).encode("utf-8")
super().__init__(name)

async def get_end_position(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Pose:
Expand Down Expand Up @@ -81,8 +93,4 @@ async def is_moving(self) -> bool:
return not self.is_stopped

async def get_kinematics(self, **kwargs) -> Tuple[KinematicsFileFormat.ValueType, bytes]:
dirname = os.path.dirname(__file__)
filepath = os.path.join(dirname, "./xarm6_kinematics.json")
with open(filepath, mode="rb") as f:
file_data = f.read()
return (KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA, file_data)
return KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA, self.kinematics
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# VIAM Module Example
# VIAM Complex Module Example
This example goes through how to create custom modular resources using Viam's python SDK, and how to connect it to a Robot.

This is a limited document. For a more in-depth understanding of modules, see the [documentation](https://docs.viam.com/program/extend/modular-resources/).

## Purpose
Modular resources allows you to define custom components and services, and add them to your robot. Viam ships with many component types, but you're not limited to only using those types -- you can create your own using modules.
Modular resources allow you to define custom components and services, and add them to your robot. Viam ships with many component types, but you're not limited to only using those types -- you can create your own using modules.

For more information, see the [documentation](https://docs.viam.com/program/extend/modular-resources/).
For more information, see the [documentation](https://docs.viam.com/program/extend/modular-resources/). For a simpler example, take a look at the [simple module example](https://github.com/viamrobotics/viam-python-sdk/tree/main/examples/simple_module), which only contains one custom resource model in one file.
Copy link
Contributor

Choose a reason for hiding this comment

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

it may be good to start with simple and then offer the grander example with custom proto

Copy link
Member Author

Choose a reason for hiding this comment

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

the readme here is part of the complex_module example folder, so I feel like unless we want to restructure the examples even more, it's the user's choice as to which example they start with (the line here is just to remind them there's something simpler)

Copy link
Contributor

Choose a reason for hiding this comment

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

ahh. sorry. i'd misunderstood. thanks


## Project structure
The definition of the new resources are in the `src` directory. Within this directory are the `proto`, `gizmo`, `arm`, and `summation` subdirectories.
Expand All @@ -21,14 +21,14 @@ The `arm` directory contains all the necessary definitions for creating a custom

There is also a `main.py` file, which creates a module, adds the desired resources, and starts the module. This file is called by the `run.sh` script, which is the entrypoint for this module. Read further to learn how to connect this module to your robot.

Outside the `src` directory, there is the `client.py` file. This can be used to test the module once it's connected to the robot. You will have to update the credentials and robot address in that file.
Outside the `src` directory, there is a `client.py` file. You can use this file to test the module once you have connected to your robot and configured the module. You will have to update the credentials and robot address in that file.

## How to use
## Configuring and using the module
These steps assume that you have a robot available at [app.viam.com](app.viam.com).

The `run.sh` script is the entrypoint for this module. To connect this module with your robot, you must add this module's entrypoint to the robot's config. For example, this could be `/home/viam-python-sdk/examples/module/run.sh`. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#use-a-modular-resource-with-your-robot) for more details.
The `run.sh` script is the entrypoint for this module. To connect this module with your robot, you must add this module's entrypoint to the robot's config. For example, the entrypoint file may be at `/home/viam-python-sdk/examples/complex_module/run.sh` and you must add this file path to your configuration. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#use-a-modular-resource-with-your-robot) for more details.

Once the module has been added to your robot, you can then add a component that uses the `MyGizmo` model. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#configure-a-component-instance-for-a-modular-resource) for more details. You can add a service that uses the `MySum` model in a similar manner.
Once the module has been added to your robot, add a `Gizmo` component that uses the `MyGizmo` model. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#configure-a-component-instance-for-a-modular-resource) for more details. You can also add an `Arm` component that uses the `MyArm` model and a `Summation` service that uses the `MySum` model in a similar manner.

An example configuration for an Arm component, a Gizmo component, and a Summation service could look like this:
```json
Expand Down Expand Up @@ -80,7 +80,7 @@ An example configuration for an Arm component, a Gizmo component, and a Summatio
"modules": [
{
"name": "my-module",
"executable_path": "/home/viam-python-sdk/examples/module/run.sh"
"executable_path": "/home/viam-python-sdk/examples/complex_module/run.sh"
}
]
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions examples/complex_module/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# add a version if viam should be pinned to a specific version
viam-sdk
13 changes: 13 additions & 0 deletions examples/complex_module/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
cd `dirname $0`

# Create a virtual environment to run our code
VENV_NAME="venv"
Copy link
Member

Choose a reason for hiding this comment

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

Does this not assume you have venv installed? and a virtual env named venv?

Copy link
Member Author

Choose a reason for hiding this comment

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

venv is baked into python 3.4 and up, so should be good for the versions of python we support.
I've used venv for myself for the longest time haha, is there a different name you like?

Copy link
Member

Choose a reason for hiding this comment

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

AH sorry this is creating a virtual env. I guess then it assumes that there isn't already a virtual env named venv? What would happen if a user already have a venv?

Copy link
Member

Choose a reason for hiding this comment

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

NVM, it creates it in this directory, so there should be no collisions.

PYTHON="$VENV_NAME/bin/python"

python3 -m venv $VENV_NAME
$PYTHON -m pip install -r requirements.txt -U # remove -U if viam-sdk should not be upgraded whenever possible

# Be sure to use `exec` so that termination signals reach the python process,
# or handle forwarding termination signals manually
exec $PYTHON -m src.main $@
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def is_moving(self) -> bool:

async def get_kinematics(self, **kwargs) -> Tuple[KinematicsFileFormat.ValueType, bytes]:
dirname = os.path.dirname(__file__)
filepath = os.path.join(dirname, "./xarm6_kinematics.json")
filepath = os.path.join(dirname, "./my_arm_kinematics.json")
with open(filepath, mode="rb") as f:
file_data = f.read()
return (KinematicsFileFormat.KINEMATICS_FILE_FORMAT_SVA, file_data)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "xArm6",
"name": "MyArm",
"links": [
{
"id": "base",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from viam.module.module import Module
from viam.components.arm import Arm

from .arm import MyArm
from .arm.my_arm import MyArm
from .gizmo import Gizmo, MyGizmo
from .summation import MySummationService, SummationService


async def main():
"""This function creates and starts a new module, after adding all desired resources.
Resources must be pre-registered. For an example, see the `gizmo.__init__.py` file.
"""This function creates and starts a new module, after adding all desired resource models.
Resource models must be pre-registered. For an example, see the `gizmo.__init__.py` file.
"""

module = Module.from_args()
Expand Down
6 changes: 0 additions & 6 deletions examples/module/run.sh

This file was deleted.

46 changes: 46 additions & 0 deletions examples/simple_module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# VIAM Simple Module Example
This example goes through how to create custom modular resources using Viam's python SDK, and how to connect it to a Robot.

This is a limited document. For a more in-depth understanding of modules, see the [documentation](https://docs.viam.com/program/extend/modular-resources/).

## Purpose
Modular resources allow you to define custom components and services, and add them to your robot. Viam ships with many component types, but you're not limited to only using those types -- you can create your own using modules.

For more information, see the [documentation](https://docs.viam.com/program/extend/modular-resources/). For a more complex example, take a look at the [complex module example](https://github.com/viamrobotics/viam-python-sdk/tree/main/examples/complex_module), which contains multiple new APIs and custom resource models.

## Project structure
The definition of the new resources are in the `main` file of the `src` directory.

The `run.sh` script is the entrypoint for a module and calls the `main.py` file. The `main.py` file contains the definition of a new sensor model and code to register it. When called, the program creates and starts the module. Read further to learn how to connect this module to your robot.

Outside the `src` directory, there is a `client.py` file. You can use this file to test the module once you have connected to your robot and configured the module. You will have to update the credentials and robot address in that file.

## Configuring and using the module
These steps assume that you have a robot available at [app.viam.com](app.viam.com).

The `run.sh` script is the entrypoint for this module. To connect this module with your robot, you must add this module's entrypoint to the robot's config. For example, the entrypoint file may be at `/home/viam-python-sdk/examples/simple_module/run.sh` and you must add this file path to your configuration. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#use-a-modular-resource-with-your-robot) for more details.

Once the module has been added to your robot, add a new component that uses the `MySensor` model. See the [documentation](https://docs.viam.com/program/extend/modular-resources/#configure-a-component-instance-for-a-modular-resource) for more details.

An example configuration for a Sensor component could look like this:
```json
{
"components": [
{
"name": "sensor1",
"type": "sensor",
"model": "acme:demo:mysensor",
"attributes": {},
"depends_on": []
}
],
"modules": [
{
"name": "my-module",
"executable_path": "/home/viam-python-sdk/examples/simple_module/run.sh"
}
]
}
```

After the robot has started and connected to the module, you can use the provided `client.py` to connect to your robot and make calls to your custom, modular resources.
29 changes: 29 additions & 0 deletions examples/simple_module/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import asyncio

from viam import logging
from viam.robot.client import RobotClient
from viam.rpc.dial import Credentials, DialOptions
from viam.components.sensor import Sensor


async def connect():
creds = Credentials(type="<your authentication type here>", payload="<your authentication payload here>")
opts = RobotClient.Options(refresh_interval=0, dial_options=DialOptions(credentials=creds), log_level=logging.DEBUG)
return await RobotClient.at_address("<your robot uri here>", opts)


async def main():
robot = await connect()

print("Resources:")
print(robot.resource_names)

sensor = Sensor.from_robot(robot, name="sensor1")
reading = await sensor.get_readings()
print(f"The reading is {reading}")

await robot.close()


if __name__ == "__main__":
asyncio.run(main())
2 changes: 2 additions & 0 deletions examples/simple_module/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# add a version if viam should be pinned to a specific version
viam-sdk
13 changes: 13 additions & 0 deletions examples/simple_module/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
cd `dirname $0`

# Create a virtual environment to run our code
VENV_NAME="venv"
PYTHON="$VENV_NAME/bin/python"

python3 -m venv $VENV_NAME
$PYTHON -m pip install -r requirements.txt -U # remove -U if viam-sdk should not be upgraded whenever possible

# Be sure to use `exec` so that termination signals reach the python process,
# or handle forwarding termination signals manually
exec $PYTHON src/main.py $@
47 changes: 47 additions & 0 deletions examples/simple_module/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import asyncio
from typing import Any, ClassVar, Dict, Mapping, Optional

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.registry import Registry, ResourceCreatorRegistration
from viam.resource.types import Model, ModelFamily
from viam.utils import ValueTypes


class MySensor(Sensor):
# Subclass the Viam Sensor component and implement the required functions
MODEL: ClassVar[Model] = Model(ModelFamily("acme", "demo"), "mysensor")

def __init__(self, name: str):
super().__init__(name)

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

async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Mapping[str, Any]:
return {"signal": 1}

async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]:
return command


async def main():
"""This function creates and starts a new module, after adding all desired resource models.
Resource creators must be registered to the resource registry before the module adds the resource model.
"""
Registry.register_resource_creator(Sensor.SUBTYPE, MySensor.MODEL, ResourceCreatorRegistration(MySensor.new))

module = Module.from_args()
module.add_model_from_registry(Sensor.SUBTYPE, MySensor.MODEL)
await module.start()


if __name__ == "__main__":
asyncio.run(main())