From bddd1ff83e3f992dcae387bb89928a6b908d8ec1 Mon Sep 17 00:00:00 2001 From: JessamyT <75634662+JessamyT@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:48:13 -0700 Subject: [PATCH 1/3] Clarify base coordinate system in comments (#324) --- src/viam/components/base/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/viam/components/base/base.py b/src/viam/components/base/base.py index 164a94622..2189c653f 100644 --- a/src/viam/components/base/base.py +++ b/src/viam/components/base/base.py @@ -66,9 +66,9 @@ async def spin( Args: angle (float): The angle (in degrees) to spin. - Negative implies backwards. - velocity (float): The angular velocity (in degrees per second). - to spin. Negative implies backwards. + velocity (float): The angular velocity (in degrees per second) + to spin. + Given a positive angle and a positive velocity, the base will turn to the left. """ ... @@ -91,7 +91,7 @@ async def set_power( Args: linear (Vector3): The linear component. Only the Y component is used - for wheeled base. Negative implies backwards. + for wheeled base. Positive implies forwards. angular (Vector3): The angular component. Only the Z component is used for wheeled base. Positive turns left; negative turns right. """ From 1a4d8ad2058e5cd0a2610d38adb30679174a4a4d Mon Sep 17 00:00:00 2001 From: 8ashar <96631404+8ashar@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:57:26 -0400 Subject: [PATCH 2/3] [RSDK-3628] Create Data Location Client Able to Connect to App (#332) --- src/viam/app/client.py | 46 +++++++++++++++++ src/viam/app/data/client.py | 100 ++++++++++++++++++++++++++++++++++++ src/viam/rpc/dial.py | 4 ++ 3 files changed, 150 insertions(+) create mode 100644 src/viam/app/client.py create mode 100644 src/viam/app/data/client.py diff --git a/src/viam/app/client.py b/src/viam/app/client.py new file mode 100644 index 000000000..1f21d83e4 --- /dev/null +++ b/src/viam/app/client.py @@ -0,0 +1,46 @@ +from typing_extensions import Self + +from grpclib.client import Channel + +from viam.rpc.dial import DialOptions, _dial_app, _get_access_token +from viam import logging +from viam.app.data.client import DataClient + +LOGGER = logging.getLogger(__name__) + + +class AppClient: + """gRPC client for all communication and interaction with app. + + Use create() to instantiate an AppClient:: + + AppClient.create(...) + """ + + @classmethod + async def create(cls, dial_options: DialOptions) -> Self: + """ + Create an app client that establishes a connection to app.viam.com. + + Args: + dial_options (DialOptions): Required information for authorization and connection to app, creds and auth_entity are necessary. + + Returns: + Self: the AppClient. + """ + self = cls() + self._channel = await _dial_app(dial_options) + access_token = await _get_access_token(self._channel, dial_options.auth_entity, dial_options) + self._metadata = {"authorization": f"Bearer {access_token}"} + return self + + _channel: Channel + _metadata: str + _closed: bool = False + + @property + def data_client(self) -> DataClient: + return DataClient(self._channel, self._metadata) + + async def close(self): + raise NotImplementedError() diff --git a/src/viam/app/data/client.py b/src/viam/app/data/client.py new file mode 100644 index 000000000..966fbae8b --- /dev/null +++ b/src/viam/app/data/client.py @@ -0,0 +1,100 @@ +from typing import List, Optional, Mapping, Any + +from grpclib.client import Channel + +from viam.proto.app.data import ( + DataServiceStub, + Filter, +) +from viam.proto.app.datasync import ( + UploadMetadata, + SensorData, + FileData +) +from viam.proto.app.datasync import DataSyncServiceStub +from viam import logging + +LOGGER = logging.getLogger(__name__) + + +class DataClient: + """gRPC client for uploading and retreiving data from app + + Constructor is used by AppClient to instantiate relevant service stubs. Calls to DataClient methods should be made through AppClient. + """ + + def __init__(self, channel: Channel, metadata: str): + """ + Create a data client that establishes a connection to app. + + Args: + channel (Channel): an already-established connection to app. + metadata (str): the required authorization token to send requests to app. + """ + self._metadata = metadata + self._data_client = DataServiceStub(channel) + self._data_sync_client = DataSyncServiceStub(channel) + + _data_client: DataServiceStub + _data_sync_client: DataSyncServiceStub + _metadata: str + + async def tabular_data_by_filter( + self, + filter: Optional[Filter], + dest: Optional[str] + ) -> List[Mapping[str, Any]]: + raise NotImplementedError() + + async def binary_data_by_filter( + self, + data_request: Optional[Filter], + dest: Optional[str] + ) -> List[bytes]: + raise NotImplementedError() + + async def binary_data_by_ids(self, file_ids: Optional[List[str]]) -> List[bytes]: + raise NotImplementedError() + + async def delete_tabular_data_by_filter(self, filter: Optional[Filter]) -> None: + raise NotImplementedError() + + async def delete_binary_data_by_filter(self, filter: Optional[Filter]) -> None: + raise NotImplementedError() + + async def delete_binary_data_by_ids(self, file_ids: Optional[List[str]]) -> None: + raise NotImplementedError() + + async def add_tags_to_binary_data_by_file_ids(self, file_ids: Optional[List[str]], tags: Optional[List[str]]) -> None: + raise NotImplementedError() + + async def add_tags_to_binary_data_by_filter(self, filter: Optional[Filter], tags: Optional[List[str]]) -> None: + raise NotImplementedError() + + async def remove_tags_from_binary_data_by_file_ids(self, file_ids: Optional[List[str]], tags: Optional[List[str]]) -> None: + raise NotImplementedError() + + async def remove_tags_from_binary_data_by_filter(self, filter: Optional[Filter], tags: Optional[List[str]]) -> None: + raise NotImplementedError() + + async def tags_by_filter(self, filter: Optional[Filter]) -> List[str]: + raise NotImplementedError() + + # to be defined and implemented last + async def add_bounding_box_to_image_by_id(self): + raise NotImplementedError() + + # to be defined and implemented last + async def remove_bounding_box_from_image_by_id(self): + raise NotImplementedError() + + async def bounding_box_labels_by_filter(self, filter: Optional[Filter]) -> List[str]: + raise NotImplementedError() + + # TODO (RSDK-3637): confirm arguments + async def data_capture_upload(self, metadata: Optional[UploadMetadata], sensor_contents: Optional[List[SensorData]]) -> None: + raise NotImplementedError() + + # TODO (RSDK-3637): confirm arguments + async def file_upload(self, metadata: Optional[UploadMetadata], file_contents: Optional[FileData]) -> None: + raise NotImplementedError() diff --git a/src/viam/rpc/dial.py b/src/viam/rpc/dial.py index 10105fbfa..f7e95687c 100644 --- a/src/viam/rpc/dial.py +++ b/src/viam/rpc/dial.py @@ -306,3 +306,7 @@ async def _dial_direct(address: str, options: Optional[DialOptions] = None) -> C async def dial_direct(address: str, options: Optional[DialOptions] = None) -> Channel: warnings.warn("dial_direct is deprecated. Use rpc.dial.dial instead.", DeprecationWarning, stacklevel=2) return await _dial_direct(address, options) + + +async def _dial_app(options: DialOptions) -> Channel: + return await _dial_direct("app.viam.com:443") From 6fe4858c7f6a0097171bb71c29d0a5dfedbf7e34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 13:55:02 -0400 Subject: [PATCH 3/3] rc-0.4.2 (#330) Co-authored-by: clintpurser Co-authored-by: Cheuk <90270663+cheukt@users.noreply.github.com> Co-authored-by: Cheuk Tse Co-authored-by: cheukt Fixes: RSDK-3775 - Fix unawaited warning --- pyproject.toml | 2 +- src/viam/sessions_client.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ee1dc2ae9..c2421803e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "viam-sdk" -version = "0.4.1" +version = "0.4.2" description = "Viam Robotics Python SDK" authors = [ "Naveed " ] license = "Apache-2.0" diff --git a/src/viam/sessions_client.py b/src/viam/sessions_client.py index 7ceef8740..0bc9b9877 100644 --- a/src/viam/sessions_client.py +++ b/src/viam/sessions_client.py @@ -36,11 +36,6 @@ def loop_kwargs(): return {} -async def delay(coro, seconds): - await asyncio.sleep(seconds, **loop_kwargs()) - await coro - - class SessionsClient: """ A Session allows a client to express that it is actively connected and @@ -116,10 +111,22 @@ async def metadata(self) -> _MetadataLike: self._heartbeat_interval = response.heartbeat_window.ToTimedelta() self._current_id = response.id + # tick once to ensure heartbeats are supported await self._heartbeat_tick() + if self._supported: + # We send heartbeats slightly faster than the interval window to + # ensure that we don't fall outside of it and expire the session. + wait = self._heartbeat_interval.total_seconds() / 5 + asyncio.create_task(self._heartbeat_task(wait), name=f"{_TASK_PREFIX}-heartbeat") + return self._metadata + async def _heartbeat_task(self, wait: float): + while self._supported: + await asyncio.sleep(wait) + await self._heartbeat_tick() + async def _heartbeat_tick(self): if not self._supported: return @@ -139,10 +146,6 @@ async def _heartbeat_tick(self): self.reset() else: LOGGER.debug("Sent heartbeat successfully") - # We send heartbeats slightly faster than the interval window to - # ensure that we don't fall outside of it and expire the session. - wait = self._heartbeat_interval.total_seconds() / 5 - asyncio.create_task(delay(self._heartbeat_tick(), wait), name=f"{_TASK_PREFIX}-heartbeat") @property def _metadata(self) -> _MetadataLike: