viam-sdk 0.55.0__py3-none-win_amd64.whl → 0.56.0__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of viam-sdk might be problematic. Click here for more details.
- viam/app/app_client.py +12 -4
- viam/components/camera/camera.py +1 -1
- viam/components/camera/service.py +2 -3
- viam/gen/app/agent/v1/agent_pb2.py +1 -1
- viam/gen/app/cloudslam/v1/cloud_slam_pb2.py +1 -1
- viam/gen/app/data/v1/data_pb2.py +1 -1
- viam/gen/app/datapipelines/v1/data_pipelines_pb2.py +1 -1
- viam/gen/app/dataset/v1/dataset_pb2.py +1 -1
- viam/gen/app/datasync/v1/data_sync_pb2.py +1 -1
- viam/gen/app/mlinference/v1/ml_inference_pb2.py +1 -1
- viam/gen/app/mltraining/v1/ml_training_pb2.py +1 -1
- viam/gen/app/packages/v1/packages_pb2.py +1 -1
- viam/gen/app/v1/app_pb2.py +1 -1
- viam/gen/app/v1/billing_pb2.py +52 -48
- viam/gen/app/v1/billing_pb2.pyi +59 -3
- viam/gen/app/v1/end_user_pb2.py +1 -1
- viam/gen/app/v1/robot_pb2.py +1 -1
- viam/gen/common/v1/common_pb2.py +1 -1
- viam/gen/component/arm/v1/arm_pb2.py +1 -1
- viam/gen/component/audioinput/v1/audioinput_pb2.py +1 -1
- viam/gen/component/base/v1/base_pb2.py +1 -1
- viam/gen/component/board/v1/board_pb2.py +1 -1
- viam/gen/component/button/v1/button_pb2.py +1 -1
- viam/gen/component/camera/v1/camera_pb2.py +1 -1
- viam/gen/component/encoder/v1/encoder_pb2.py +1 -1
- viam/gen/component/gantry/v1/gantry_pb2.py +1 -1
- viam/gen/component/generic/v1/generic_pb2.py +1 -1
- viam/gen/component/gripper/v1/gripper_pb2.py +1 -1
- viam/gen/component/inputcontroller/v1/input_controller_pb2.py +1 -1
- viam/gen/component/motor/v1/motor_pb2.py +1 -1
- viam/gen/component/movementsensor/v1/movementsensor_pb2.py +1 -1
- viam/gen/component/posetracker/v1/pose_tracker_pb2.py +1 -1
- viam/gen/component/powersensor/v1/powersensor_pb2.py +1 -1
- viam/gen/component/sensor/v1/sensor_pb2.py +1 -1
- viam/gen/component/servo/v1/servo_pb2.py +1 -1
- viam/gen/component/switch/v1/switch_pb2.py +1 -1
- viam/gen/component/testecho/v1/testecho_pb2.py +1 -1
- viam/gen/module/v1/module_pb2.py +1 -1
- viam/gen/proto/rpc/examples/echo/v1/echo_pb2.py +1 -1
- viam/gen/proto/rpc/examples/echoresource/v1/echoresource_pb2.py +1 -1
- viam/gen/proto/rpc/v1/auth_pb2.py +1 -1
- viam/gen/proto/rpc/webrtc/v1/grpc_pb2.py +1 -1
- viam/gen/proto/rpc/webrtc/v1/signaling_pb2.py +1 -1
- viam/gen/provisioning/v1/provisioning_pb2.py +1 -1
- viam/gen/robot/v1/robot_pb2.py +1 -1
- viam/gen/service/datamanager/v1/data_manager_pb2.py +1 -1
- viam/gen/service/discovery/v1/discovery_pb2.py +1 -1
- viam/gen/service/generic/v1/generic_pb2.py +1 -1
- viam/gen/service/mlmodel/v1/mlmodel_pb2.py +1 -1
- viam/gen/service/motion/v1/motion_pb2.py +87 -63
- viam/gen/service/motion/v1/motion_pb2.pyi +92 -66
- viam/gen/service/navigation/v1/navigation_pb2.py +1 -1
- viam/gen/service/sensors/v1/sensors_pb2.py +1 -1
- viam/gen/service/shell/v1/shell_pb2.py +1 -1
- viam/gen/service/slam/v1/slam_pb2.py +1 -1
- viam/gen/service/vision/v1/vision_pb2.py +1 -1
- viam/gen/service/worldstatestore/v1/world_state_store_pb2.py +1 -1
- viam/gen/stream/v1/stream_pb2.py +1 -1
- viam/gen/tagger/v1/tagger_pb2.py +1 -1
- viam/proto/app/billing.py +4 -0
- viam/rpc/libviam_rust_utils.dll +0 -0
- viam/services/motion/client.py +8 -9
- viam/services/motion/motion.py +36 -38
- viam/services/worldstatestore/worldstatestore.py +1 -1
- viam/sessions_client.py +34 -25
- viam/version_metadata.py +2 -2
- {viam_sdk-0.55.0.dist-info → viam_sdk-0.56.0.dist-info}/METADATA +1 -1
- {viam_sdk-0.55.0.dist-info → viam_sdk-0.56.0.dist-info}/RECORD +70 -70
- {viam_sdk-0.55.0.dist-info → viam_sdk-0.56.0.dist-info}/WHEEL +0 -0
- {viam_sdk-0.55.0.dist-info → viam_sdk-0.56.0.dist-info}/licenses/LICENSE +0 -0
viam/services/motion/motion.py
CHANGED
|
@@ -7,7 +7,7 @@ if sys.version_info >= (3, 10):
|
|
|
7
7
|
else:
|
|
8
8
|
from typing_extensions import TypeAlias
|
|
9
9
|
|
|
10
|
-
from viam.proto.common import GeoGeometry, Geometry, GeoPoint, Pose, PoseInFrame,
|
|
10
|
+
from viam.proto.common import GeoGeometry, Geometry, GeoPoint, Pose, PoseInFrame, Transform, WorldState
|
|
11
11
|
from viam.proto.service.motion import Constraints, GetPlanResponse, MotionConfiguration, PlanStatusWithID
|
|
12
12
|
from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_SERVICE
|
|
13
13
|
from viam.utils import ValueTypes
|
|
@@ -33,7 +33,7 @@ class Motion(ServiceBase):
|
|
|
33
33
|
@abc.abstractmethod
|
|
34
34
|
async def move(
|
|
35
35
|
self,
|
|
36
|
-
component_name:
|
|
36
|
+
component_name: str,
|
|
37
37
|
destination: PoseInFrame,
|
|
38
38
|
world_state: Optional[WorldState] = None,
|
|
39
39
|
constraints: Optional[Constraints] = None,
|
|
@@ -44,18 +44,17 @@ class Motion(ServiceBase):
|
|
|
44
44
|
"""Plan and execute a movement to move the component specified to its goal destination.
|
|
45
45
|
|
|
46
46
|
Note: Frames designated with respect to components can also be used as the ``component_name`` when calling for a move. This
|
|
47
|
-
technique allows for planning and moving the frame itself to the ``destination``.
|
|
48
|
-
|
|
47
|
+
technique allows for planning and moving the frame itself to the ``destination``.
|
|
48
|
+
To do so, simply pass in a string into ``component_name``. Ex::
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
success = await MotionServiceClient.move(resource_name, ...)
|
|
50
|
+
success = await MotionServiceClient.move("externalFrame", ...)
|
|
52
51
|
|
|
53
52
|
::
|
|
54
53
|
|
|
55
54
|
motion = MotionClient.from_robot(robot=machine, name="builtin")
|
|
56
55
|
|
|
57
|
-
# Assumes
|
|
58
|
-
gripper_name =
|
|
56
|
+
# Assumes "my_gripper" on the machine
|
|
57
|
+
gripper_name = "my_gripper"
|
|
59
58
|
my_frame = "my_gripper_offset"
|
|
60
59
|
|
|
61
60
|
goal_pose = Pose(x=0, y=0, z=300, o_x=0, o_y=0, o_z=1, theta=0)
|
|
@@ -69,7 +68,7 @@ class Motion(ServiceBase):
|
|
|
69
68
|
extra={})
|
|
70
69
|
|
|
71
70
|
Args:
|
|
72
|
-
component_name (
|
|
71
|
+
component_name (str): Name of a component on a given robot.
|
|
73
72
|
destination (viam.proto.common.PoseInFrame): The destination to move to, expressed as a ``Pose`` and the frame in which it was
|
|
74
73
|
observed.
|
|
75
74
|
world_state (viam.proto.common.WorldState): When supplied, the motion service will create a plan that obeys any constraints
|
|
@@ -95,9 +94,9 @@ class Motion(ServiceBase):
|
|
|
95
94
|
@abc.abstractmethod
|
|
96
95
|
async def move_on_globe(
|
|
97
96
|
self,
|
|
98
|
-
component_name:
|
|
97
|
+
component_name: str,
|
|
99
98
|
destination: GeoPoint,
|
|
100
|
-
movement_sensor_name:
|
|
99
|
+
movement_sensor_name: str,
|
|
101
100
|
obstacles: Optional[Sequence[GeoGeometry]] = None,
|
|
102
101
|
heading: Optional[float] = None,
|
|
103
102
|
configuration: Optional[MotionConfiguration] = None,
|
|
@@ -120,24 +119,23 @@ class Motion(ServiceBase):
|
|
|
120
119
|
|
|
121
120
|
motion = MotionClient.from_robot(robot=machine, name="builtin")
|
|
122
121
|
|
|
123
|
-
# Get the
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"my_movement_sensor")
|
|
122
|
+
# Get the names of the base and movement sensor
|
|
123
|
+
my_base_name = "my_base"
|
|
124
|
+
mvmnt_sensor_name = "my_movement_sensor"
|
|
127
125
|
# Define a destination GeoPoint at the GPS coordinates [0, 0]
|
|
128
126
|
my_destination = movement_sensor.GeoPoint(latitude=0, longitude=0)
|
|
129
127
|
|
|
130
128
|
# Move the base component to the designated geographic location, as reported by the movement sensor
|
|
131
129
|
execution_id = await motion.move_on_globe(
|
|
132
|
-
component_name=
|
|
130
|
+
component_name=my_base_name,
|
|
133
131
|
destination=my_destination,
|
|
134
|
-
movement_sensor_name=
|
|
132
|
+
movement_sensor_name=mvmnt_sensor_name)
|
|
135
133
|
|
|
136
134
|
Args:
|
|
137
|
-
component_name (
|
|
135
|
+
component_name (str): The name of the base to move.
|
|
138
136
|
destination (GeoPoint): The location of the component's destination, represented in geographic notation as a
|
|
139
137
|
GeoPoint (lat, lng).
|
|
140
|
-
movement_sensor_name (
|
|
138
|
+
movement_sensor_name (str): The name of the movement sensor that you want to use to check
|
|
141
139
|
the machine's location.
|
|
142
140
|
obstacles (Optional[Sequence[GeoGeometry]]): Obstacles to consider when planning the motion of the component,
|
|
143
141
|
with each represented as a GeoGeometry. Default: None
|
|
@@ -170,9 +168,9 @@ class Motion(ServiceBase):
|
|
|
170
168
|
@abc.abstractmethod
|
|
171
169
|
async def move_on_map(
|
|
172
170
|
self,
|
|
173
|
-
component_name:
|
|
171
|
+
component_name: str,
|
|
174
172
|
destination: Pose,
|
|
175
|
-
slam_service_name:
|
|
173
|
+
slam_service_name: str,
|
|
176
174
|
configuration: Optional[MotionConfiguration] = None,
|
|
177
175
|
obstacles: Optional[Sequence[Geometry]] = None,
|
|
178
176
|
*,
|
|
@@ -194,23 +192,23 @@ class Motion(ServiceBase):
|
|
|
194
192
|
|
|
195
193
|
motion = MotionClient.from_robot(robot=machine, name="builtin")
|
|
196
194
|
|
|
197
|
-
# Get the
|
|
198
|
-
|
|
199
|
-
my_slam_service_name =
|
|
195
|
+
# Get the names of the base component and SLAM service
|
|
196
|
+
my_base_name = "my_base"
|
|
197
|
+
my_slam_service_name = "my_slam_service"
|
|
200
198
|
|
|
201
199
|
# Define a destination pose with respect to the origin of the map from the SLAM service "my_slam_service"
|
|
202
200
|
my_pose = Pose(y=10)
|
|
203
201
|
|
|
204
202
|
# Move the base component to the destination pose of Y=10, a location of
|
|
205
203
|
# (0, 10, 0) in respect to the origin of the map
|
|
206
|
-
execution_id = await motion.move_on_map(component_name=
|
|
204
|
+
execution_id = await motion.move_on_map(component_name=my_base_name,
|
|
207
205
|
destination=my_pose,
|
|
208
206
|
slam_service_name=my_slam_service_name)
|
|
209
207
|
|
|
210
208
|
Args:
|
|
211
|
-
component_name (
|
|
209
|
+
component_name (str): The name of the base to move.
|
|
212
210
|
destination (Pose): The destination, which can be any Pose with respect to the SLAM map's origin.
|
|
213
|
-
slam_service_name (
|
|
211
|
+
slam_service_name (str): The name of the SLAM service from which the SLAM map is requested.
|
|
214
212
|
configuration (Optional[MotionConfiguration]): The configuration you want to set across this machine for this motion service.
|
|
215
213
|
This parameter and each of its fields are optional.
|
|
216
214
|
|
|
@@ -237,7 +235,7 @@ class Motion(ServiceBase):
|
|
|
237
235
|
@abc.abstractmethod
|
|
238
236
|
async def stop_plan(
|
|
239
237
|
self,
|
|
240
|
-
component_name:
|
|
238
|
+
component_name: str,
|
|
241
239
|
*,
|
|
242
240
|
extra: Optional[Mapping[str, ValueTypes]] = None,
|
|
243
241
|
timeout: Optional[float] = None,
|
|
@@ -251,11 +249,11 @@ class Motion(ServiceBase):
|
|
|
251
249
|
# Assuming a `move_on_globe()` started the execution
|
|
252
250
|
# Stop the base component which was instructed to move by `move_on_globe()`
|
|
253
251
|
# or `move_on_map()`
|
|
254
|
-
|
|
252
|
+
my_base_name = "my_base"
|
|
255
253
|
await motion.stop_plan(component_name=mvmnt_sensor)
|
|
256
254
|
|
|
257
255
|
Args:
|
|
258
|
-
component_name (
|
|
256
|
+
component_name (str): The component to stop
|
|
259
257
|
|
|
260
258
|
For more information, see `Motion service <https://docs.viam.com/dev/reference/apis/services/motion/#stopplan>`_.
|
|
261
259
|
"""
|
|
@@ -264,7 +262,7 @@ class Motion(ServiceBase):
|
|
|
264
262
|
@abc.abstractmethod
|
|
265
263
|
async def get_plan(
|
|
266
264
|
self,
|
|
267
|
-
component_name:
|
|
265
|
+
component_name: str,
|
|
268
266
|
last_plan_only: bool = False,
|
|
269
267
|
execution_id: Optional[str] = None,
|
|
270
268
|
*,
|
|
@@ -291,12 +289,12 @@ class Motion(ServiceBase):
|
|
|
291
289
|
::
|
|
292
290
|
|
|
293
291
|
motion = MotionClient.from_robot(robot=machine, name="builtin")
|
|
294
|
-
|
|
292
|
+
my_base_name = "my_base"
|
|
295
293
|
# Get the plan(s) of the base component which was instructed to move by `MoveOnGlobe()` or `MoveOnMap()`
|
|
296
|
-
resp = await motion.get_plan(component_name=
|
|
294
|
+
resp = await motion.get_plan(component_name=my_base_name)
|
|
297
295
|
|
|
298
296
|
Args:
|
|
299
|
-
component_name (
|
|
297
|
+
component_name (str): The component to stop
|
|
300
298
|
last_plan_only (Optional[bool]): If supplied, the response will only return the last plan for the component / execution.
|
|
301
299
|
execution_id (Optional[str]): If supplied, the response will only return plans with the provided execution_id.
|
|
302
300
|
|
|
@@ -343,7 +341,7 @@ class Motion(ServiceBase):
|
|
|
343
341
|
@abc.abstractmethod
|
|
344
342
|
async def get_pose(
|
|
345
343
|
self,
|
|
346
|
-
component_name:
|
|
344
|
+
component_name: str,
|
|
347
345
|
destination_frame: str,
|
|
348
346
|
supplemental_transforms: Optional[Sequence[Transform]] = None,
|
|
349
347
|
*,
|
|
@@ -359,7 +357,7 @@ class Motion(ServiceBase):
|
|
|
359
357
|
# (``Arm``, ``Base``, etc).
|
|
360
358
|
|
|
361
359
|
# Create a `component_name`:
|
|
362
|
-
component_name =
|
|
360
|
+
component_name = "my_gripper"
|
|
363
361
|
|
|
364
362
|
from viam.components.gripper import Gripper
|
|
365
363
|
from viam.services.motion import MotionClient
|
|
@@ -368,12 +366,12 @@ class Motion(ServiceBase):
|
|
|
368
366
|
robot = await connect()
|
|
369
367
|
|
|
370
368
|
motion = MotionClient.from_robot(robot=machine, name="builtin")
|
|
371
|
-
gripperName =
|
|
369
|
+
gripperName = "my_gripper"
|
|
372
370
|
gripperPoseInWorld = await motion.get_pose(component_name=gripperName,
|
|
373
371
|
destination_frame="world")
|
|
374
372
|
|
|
375
373
|
Args:
|
|
376
|
-
component_name (
|
|
374
|
+
component_name (str): Name of a component on a robot.
|
|
377
375
|
destination_frame (str): Name of the desired reference frame.
|
|
378
376
|
supplemental_transforms (Optional[List[viam.proto.common.Transform]]): Transforms used to augment the robot's frame while
|
|
379
377
|
calculating pose.
|
|
@@ -17,7 +17,7 @@ class WorldStateStore(ServiceBase):
|
|
|
17
17
|
changes to world state transforms, which represent the pose of objects in different
|
|
18
18
|
reference frames. This functionality can be used to create custom visualizations of the world state.
|
|
19
19
|
|
|
20
|
-
For more information, see `WorldStateStore service <https://docs.viam.com/dev/reference/apis/services/
|
|
20
|
+
For more information, see `WorldStateStore service <https://docs.viam.com/dev/reference/apis/services/world-state-store/>`_.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
API: Final = API( # pyright: ignore [reportIncompatibleVariableOverride]
|
viam/sessions_client.py
CHANGED
|
@@ -2,6 +2,8 @@ import asyncio
|
|
|
2
2
|
import importlib
|
|
3
3
|
import pkgutil
|
|
4
4
|
import sys
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
5
7
|
from copy import deepcopy
|
|
6
8
|
from datetime import timedelta
|
|
7
9
|
from enum import IntEnum
|
|
@@ -46,6 +48,7 @@ class SessionsClient:
|
|
|
46
48
|
_heartbeat_interval: Optional[timedelta]
|
|
47
49
|
_supported: _SupportedState
|
|
48
50
|
_thread: Optional[Thread]
|
|
51
|
+
_pool: ThreadPoolExecutor
|
|
49
52
|
|
|
50
53
|
_HEARTBEAT_MONITORED_METHODS: MutableMapping[str, bool] = {}
|
|
51
54
|
|
|
@@ -71,6 +74,7 @@ class SessionsClient:
|
|
|
71
74
|
self._heartbeat_interval = None
|
|
72
75
|
self._supported = _SupportedState.UNKNOWN
|
|
73
76
|
self._thread = None
|
|
77
|
+
self._pool = ThreadPoolExecutor()
|
|
74
78
|
|
|
75
79
|
listen(self.channel, SendRequest, self._send_request)
|
|
76
80
|
listen(self.channel, RecvTrailingMetadata, self._recv_trailers)
|
|
@@ -105,39 +109,45 @@ class SessionsClient:
|
|
|
105
109
|
LOGGER.debug("Session expired")
|
|
106
110
|
self.reset()
|
|
107
111
|
|
|
112
|
+
@asynccontextmanager
|
|
113
|
+
async def _acquire_lock_async(self):
|
|
114
|
+
loop = asyncio.get_event_loop()
|
|
115
|
+
await loop.run_in_executor(self._pool, self._lock.acquire)
|
|
116
|
+
try:
|
|
117
|
+
yield
|
|
118
|
+
finally:
|
|
119
|
+
self._lock.release()
|
|
120
|
+
|
|
108
121
|
@property
|
|
109
122
|
async def metadata(self) -> _MetadataLike:
|
|
110
|
-
with self.
|
|
123
|
+
async with self._acquire_lock_async():
|
|
111
124
|
if self._disabled or self._supported != _SupportedState.UNKNOWN:
|
|
112
125
|
return self._metadata
|
|
113
126
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
with self._lock:
|
|
127
|
+
request = StartSessionRequest(resume=self._current_id)
|
|
128
|
+
try:
|
|
129
|
+
response: StartSessionResponse = await self.client.StartSession(request)
|
|
130
|
+
except GRPCError as error:
|
|
131
|
+
if error.status == Status.UNIMPLEMENTED:
|
|
120
132
|
self._reset()
|
|
121
133
|
self._supported = _SupportedState.FALSE
|
|
122
134
|
return self._metadata
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
else:
|
|
136
|
+
raise
|
|
125
137
|
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
if response is None:
|
|
139
|
+
raise GRPCError(status=Status.INTERNAL, message="Expected response to start session")
|
|
128
140
|
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
if response.heartbeat_window is None:
|
|
142
|
+
raise GRPCError(status=Status.INTERNAL, message="Expected heartbeat window in response to start session")
|
|
131
143
|
|
|
132
|
-
with self._lock:
|
|
133
144
|
self._supported = _SupportedState.TRUE
|
|
134
145
|
self._heartbeat_interval = response.heartbeat_window.ToTimedelta()
|
|
135
146
|
self._current_id = response.id
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
# tick once to ensure heartbeats are supported
|
|
149
|
+
await self._heartbeat_tick(self.client)
|
|
139
150
|
|
|
140
|
-
with self._lock:
|
|
141
151
|
if self._thread is not None:
|
|
142
152
|
self._reset()
|
|
143
153
|
if self._supported == _SupportedState.TRUE:
|
|
@@ -156,17 +166,16 @@ class SessionsClient:
|
|
|
156
166
|
return self._metadata
|
|
157
167
|
|
|
158
168
|
async def _heartbeat_tick(self, client: RobotServiceStub):
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
request = SendSessionHeartbeatRequest(id=self._current_id)
|
|
169
|
+
if not self._current_id:
|
|
170
|
+
LOGGER.debug("Failed to send heartbeat, session client reset")
|
|
171
|
+
return
|
|
172
|
+
request = SendSessionHeartbeatRequest(id=self._current_id)
|
|
164
173
|
|
|
165
174
|
try:
|
|
166
175
|
await client.SendSessionHeartbeat(request)
|
|
167
176
|
except (GRPCError, StreamTerminatedError):
|
|
168
177
|
LOGGER.debug("Heartbeat terminated", exc_info=True)
|
|
169
|
-
self.
|
|
178
|
+
self._reset()
|
|
170
179
|
else:
|
|
171
180
|
LOGGER.debug("Sent heartbeat successfully")
|
|
172
181
|
|
|
@@ -185,10 +194,10 @@ class SessionsClient:
|
|
|
185
194
|
channel = await dial(address=addr, options=self._dial_options)
|
|
186
195
|
client = RobotServiceStub(channel.channel)
|
|
187
196
|
while True:
|
|
188
|
-
with self.
|
|
197
|
+
async with self._acquire_lock_async():
|
|
189
198
|
if self._supported != _SupportedState.TRUE:
|
|
190
199
|
return
|
|
191
|
-
|
|
200
|
+
await self._heartbeat_tick(client)
|
|
192
201
|
await asyncio.sleep(wait)
|
|
193
202
|
|
|
194
203
|
@property
|
viam/version_metadata.py
CHANGED