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
10 changes: 6 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on:
workflow_dispatch:
push:
paths:
- 'src/viam/**'
- 'docs/**'
- 'README.md'
branches: [ main ]
- "src/viam/**"
- "docs/**"
- "README.md"
branches: [main]

jobs:
generate-docs:
Expand All @@ -27,9 +27,11 @@ jobs:

- name: Generate docs
run: |
poetry run python3 -m docs.examples._data_server &
poetry run python3 -m examples.server.v1.server 0.0.0.0 9091 &
sleep 2
make documentation
kill -9 `ps aux | grep "[d]ocs.examples._data_server" | awk '{print $$2}'`
kill -9 `ps aux | grep "[e]xamples.server.v1.server" | awk '{print $2}'`

- name: Upload artifacts
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ _test_docs:
pytest --nbmake "./docs"

test_docs:
kill -9 `ps aux | grep "[d]ocs.examples._data_server" | awk '{print $$2}'` || true
kill -9 `ps aux | grep "[e]xamples.server.v1.server" | awk '{print $$2}'` || true
poetry run python3 -m docs.examples._data_server &
poetry run python3 -m examples.server.v1.server 0.0.0.0 9091 quiet &
sleep 3
poetry run $(MAKE) _test_docs
kill -9 `ps aux | grep "[d]ocs.examples._data_server" | awk '{print $$2}'`
kill -9 `ps aux | grep "[e]xamples.server.v1.server" | awk '{print $$2}'`

tox:
Expand Down
137 changes: 137 additions & 0 deletions docs/examples/_data_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import asyncio

from grpclib.utils import graceful_exit
from grpclib.server import Server, Stream
from google.protobuf.struct_pb2 import Struct

from viam.proto.app.data import (
AddBoundingBoxToImageByIDResponse,
AddBoundingBoxToImageByIDRequest,
AddTagsToBinaryDataByFilterRequest,
AddTagsToBinaryDataByFilterResponse,
AddTagsToBinaryDataByIDsRequest,
AddTagsToBinaryDataByIDsResponse,
BinaryDataByFilterRequest,
BinaryDataByFilterResponse,
BinaryDataByIDsRequest,
BinaryDataByIDsResponse,
BoundingBoxLabelsByFilterRequest,
BoundingBoxLabelsByFilterResponse,
DataServiceBase,
DeleteBinaryDataByFilterRequest,
DeleteBinaryDataByFilterResponse,
DeleteBinaryDataByIDsRequest,
DeleteBinaryDataByIDsResponse,
DeleteTabularDataByFilterRequest,
DeleteTabularDataByFilterResponse,
RemoveBoundingBoxFromImageByIDResponse,
RemoveBoundingBoxFromImageByIDRequest,
RemoveTagsFromBinaryDataByFilterRequest,
RemoveTagsFromBinaryDataByFilterResponse,
RemoveTagsFromBinaryDataByIDsRequest,
RemoveTagsFromBinaryDataByIDsResponse,
TabularData,
TabularDataByFilterRequest,
TabularDataByFilterResponse,
TagsByFilterRequest,
TagsByFilterResponse,
)
from viam.proto.app.datasync import (
DataCaptureUploadRequest,
DataCaptureUploadResponse,
DataSyncServiceBase,
FileUploadRequest,
FileUploadResponse,
)


class MockData(DataServiceBase):
def __init__(self):
self.tabular_data_requested = False
self.tabular_response = [{"PowerPct": 0, "IsPowered": False}, {"PowerPct": 0, "IsPowered": False}, {"Position": 0}]

async def TabularDataByFilter(self, stream: [TabularDataByFilterRequest, TabularDataByFilterResponse]) -> None:
if self.tabular_data_requested:
await stream.send_message(TabularDataByFilterResponse())
return
self.tabular_data_requested = True
_ = await stream.recv_message()
n = len(self.tabular_response)
tabular_structs = [None] * n
for i in range(n):
s = Struct()
s.update(self.tabular_response[i])
tabular_structs[i] = s
await stream.send_message(TabularDataByFilterResponse(data=[TabularData(data=struct) for struct in tabular_structs]))

async def BinaryDataByFilter(self, stream: Stream[BinaryDataByFilterRequest, BinaryDataByFilterResponse]) -> None:
pass

async def BinaryDataByIDs(self, stream: Stream[BinaryDataByIDsRequest, BinaryDataByIDsResponse]) -> None:
pass

async def DeleteTabularDataByFilter(self, stream: Stream[DeleteTabularDataByFilterRequest, DeleteTabularDataByFilterResponse]) -> None:
pass

async def DeleteBinaryDataByFilter(self, stream: Stream[DeleteBinaryDataByFilterRequest, DeleteBinaryDataByFilterResponse]) -> None:
pass

async def DeleteBinaryDataByIDs(self, stream: Stream[DeleteBinaryDataByIDsRequest, DeleteBinaryDataByIDsResponse]) -> None:
pass

async def AddTagsToBinaryDataByIDs(self, stream: Stream[AddTagsToBinaryDataByIDsRequest, AddTagsToBinaryDataByIDsResponse]) -> None:
pass

async def AddTagsToBinaryDataByFilter(
self,
stream: Stream[AddTagsToBinaryDataByFilterRequest, AddTagsToBinaryDataByFilterResponse]
) -> None:
pass

async def RemoveTagsFromBinaryDataByIDs(
self,
stream: Stream[RemoveTagsFromBinaryDataByIDsRequest, RemoveTagsFromBinaryDataByIDsResponse]
) -> None:
pass

async def RemoveTagsFromBinaryDataByFilter(
self,
stream: Stream[RemoveTagsFromBinaryDataByFilterRequest, RemoveTagsFromBinaryDataByFilterResponse]
) -> None:
pass

async def TagsByFilter(self, stream: Stream[TagsByFilterRequest, TagsByFilterResponse]) -> None:
pass

async def AddBoundingBoxToImageByID(
self,
stream: Stream[AddBoundingBoxToImageByIDRequest, AddBoundingBoxToImageByIDResponse]
) -> None:
pass

async def RemoveBoundingBoxFromImageByID(
self,
stream: Stream[RemoveBoundingBoxFromImageByIDRequest, RemoveBoundingBoxFromImageByIDResponse]
) -> None:
pass

async def BoundingBoxLabelsByFilter(self, stream: Stream[BoundingBoxLabelsByFilterRequest, BoundingBoxLabelsByFilterResponse]) -> None:
pass


class MockDataSync(DataSyncServiceBase):
async def DataCaptureUpload(self, stream: Stream[DataCaptureUploadRequest, DataCaptureUploadResponse]) -> None:
await stream.send_message(DataCaptureUploadResponse())

async def FileUpload(self, stream: Stream[FileUploadRequest, FileUploadResponse]) -> None:
pass


async def main(*, host: str = '127.0.0.1', port: int = 9092) -> None:
data_server = Server([MockData(), MockDataSync()])
with graceful_exit([data_server]):
await data_server.start(host, port)
await data_server.wait_closed()

if __name__ == '__main__':
asyncio.run(main())
172 changes: 171 additions & 1 deletion docs/examples/example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
"source": [
"# Example usage\n",
"\n",
"The Viam SDK can be used in two ways:\n",
"The Viam SDK can be used in three ways:\n",
"1. As a client to connect to a (remote or local) robot\n",
"2. Integrate custom resources to a robot\n",
"3. As a client to connect to app.viam.com to upload and retrieve data\n",
"\n",
"## Connect as a client\n",
"\n",
Expand Down Expand Up @@ -898,6 +899,175 @@
"\n",
"It is extremely important that we check the `Operation` status, as this not only prevents any unnecessary resource usage, but also allows us to respond to urgent cancellation requests and stop components' motion."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connect as a client to app\n",
"\n",
"To connect to app as a client and make calls to the data API, you should instantiate an instance of an `AppClient` and retrieve its `DataClient` member."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"from viam.rpc.dial import DialOptions, Credentials\n",
"from viam.app.client import AppClient\n",
"\n",
"async def connect_to_app() -> AppClient:\n",
" dial_options = DialOptions(\n",
" auth_entity='<ADDRESS>', # The URL of your robot.\n",
" credentials=Credentials(\n",
" type='robot-location-secret',\n",
" payload='<SECRET>'\n",
" )\n",
" )\n",
" return await AppClient.create(dial_options)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Once you have a connected `AppClient`, you can then obtain a `DataClient` as a property."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"tags": [
"remove-input"
]
},
"outputs": [],
"source": [
"# Hidden.\n",
"from typing_extensions import Self\n",
"\n",
"from viam.app.data.client import DataClient\n",
"from viam.rpc.dial import _dial_direct\n",
"\n",
"class MockAppClient:\n",
"\n",
" @classmethod\n",
" async def create_app_client(cls) -> Self:\n",
" self = cls()\n",
" self._channel = await _dial_direct(address=\"localhost:9092\", options=DialOptions(insecure=True))\n",
" self.data_client = DataClient(channel=self._channel, metadata=None)\n",
" return self\n",
"\n",
" def close(self):\n",
" self._channel.close()\n",
"\n",
"app_client = await MockAppClient.create_app_client()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"tags": [
"hide-output"
]
},
"outputs": [],
"source": [
"data_client = app_client.data_client"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This `DataClient` can be used to make method calls that retrieve data from app."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[{'IsPowered': False, 'PowerPct': 0.0}, {'IsPowered': False, 'PowerPct': 0.0}, {'Position': 0.0}]\n"
]
}
],
"source": [
"from datetime import datetime\n",
"\n",
"from viam.proto.app.data import Filter, CaptureInterval, TagsFilter\n",
"\n",
"left_motor_filter = Filter(\n",
" component_name=\"left_motor\",\n",
" interval=CaptureInterval(\n",
" # datetime_to_timestamp() converts a datetime to a Timestamp.\n",
" start=DataClient.datetime_to_timestamp(datetime(2023, 6, 5, 11)),\n",
" end=DataClient.datetime_to_timestamp(datetime(2023, 6, 5, 13, 30))\n",
" ),\n",
" tags_filter=TagsFilter(\n",
" tags=[\"speed_test\"]\n",
" )\n",
")\n",
"\n",
"data = await data_client.tabular_data_by_filter(filter=left_motor_filter)\n",
"print(data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also use your `DataClient` to upload data to app."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"tags": [
"hide-output"
]
},
"outputs": [],
"source": [
"await data_client.tabular_data_capture_upload(\n",
" part_id='',\n",
" component_type='rdk:component:motor',\n",
" component_name='left_motor',\n",
" method_name='IsPowered',\n",
" method_parameters=None,\n",
" tags=[\"tag_1\", \"tag_2\"],\n",
" data_request_times=None,\n",
" tabular_data=[{'PowerPCT': 0, 'IsPowered': False}, {'PowerPCT': 10, 'IsPowered': True}]\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"At the end, you may close the connection"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"async def cleanup():\n",
" await app_client.close()"
]
}
],
"metadata": {
Expand Down
Binary file added docs/examples/foo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Full Viam Docs <https://docs.viam.com>
autoapi/viam/components/index
autoapi/viam/services/index
autoapi/viam/robot/index
autoapi/viam/app/index
autoapi/viam/resource/index
autoapi/viam/module/index
autoapi/viam/media/index
Expand All @@ -25,4 +26,5 @@ autoapi/viam/proto/index
```

```{include} ../README.md

```
Empty file added src/viam/app/__init__.py
Empty file.
Loading