Skip to content

Commit ec1bf39

Browse files
Keith PrickettMichelle Sintov
authored andcommitted
VIC-11554 On camera close, wait for streaming done (#76)
Fixes issue where opening and closing a streaming connection would put the gRPC stuff in a bad state and we could never get a picture again.
1 parent 53d19ab commit ec1bf39

7 files changed

Lines changed: 272 additions & 74 deletions

File tree

anki_vector/camera.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import asyncio
3030
from concurrent.futures import CancelledError
31+
import time
3132
import sys
3233

3334
try:
@@ -130,8 +131,47 @@ def close_camera_feed(self) -> None:
130131
self._camera_feed_task.cancel()
131132
future = self.conn.run_coroutine(self._camera_feed_task)
132133
future.result()
134+
# wait for streaming to end, up to 10 seconds
135+
iterations = 0
136+
max_iterations = 100
137+
while self.image_streaming_enabled():
138+
time.sleep(0.1)
139+
iterations += 1
140+
if iterations > max_iterations:
141+
# leave loop, even if streaming is still enabled
142+
# because other SDK functions will still work and
143+
# the RPC should have had enough time to finish
144+
# which means we _should_ be in a good state.
145+
self.logger.info('Camera Feed closed, but streaming on'
146+
' robot remained enabled. This is unexpected.')
147+
break;
133148
self._camera_feed_task = None
134149

150+
async def _image_streaming_enabled(self) -> bool:
151+
"""request streaming enabled status from the robot"""
152+
request = protocol.IsImageStreamingEnabledRequest()
153+
response = await self.conn.grpc_interface.IsImageStreamingEnabled(request)
154+
enabled = False
155+
if response:
156+
enabled = response.is_image_streaming_enabled
157+
return enabled
158+
159+
def image_streaming_enabled(self) -> bool:
160+
"""True if image streaming is enabled on the robot
161+
162+
.. testcode::
163+
164+
import anki_vector
165+
with anki_vector.Robot() as robot:
166+
image_streaming_enabled = robot.camera.image_streaming_enabled()
167+
if image_streaming_enabled:
168+
print("Robot is streaming video")
169+
else:
170+
print("Robot is not streaming video")
171+
"""
172+
future = self.conn.run_coroutine(self._image_streaming_enabled())
173+
return future.result()
174+
135175
def _unpack_image(self, msg: protocol.CameraFeedResponse) -> None:
136176
"""Processes raw data from the robot into a more more useful image structure."""
137177
size = len(msg.data)

anki_vector/messaging/external_interface.proto

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,28 +154,35 @@ service ExternalInterface {
154154
body: "*"
155155
};
156156
}
157-
157+
158158
rpc EnableMotionDetection(EnableMotionDetectionRequest) returns (EnableMotionDetectionResponse) {
159159
option (google.api.http) = {
160160
post: "/v1/enable_motion_detection",
161161
body: "*"
162162
};
163163
}
164-
164+
165165
rpc EnableMirrorMode(EnableMirrorModeRequest) returns (EnableMirrorModeResponse) {
166166
option (google.api.http) = {
167167
post: "/v1/enable_mirror_mode",
168168
body: "*"
169169
};
170170
}
171-
171+
172172
rpc EnableImageStreaming(EnableImageStreamingRequest) returns (EnableImageStreamingResponse) {
173173
option (google.api.http) = {
174174
post: "/v1/enable_image_streaming",
175175
body: "*"
176176
};
177177
}
178-
178+
179+
rpc IsImageStreamingEnabled(IsImageStreamingEnabledRequest) returns (IsImageStreamingEnabledResponse) {
180+
option (google.api.http) = {
181+
post: "/v1/is_image_streaming_enabled",
182+
body: "*"
183+
};
184+
}
185+
179186
// Tells Vector to drive to the specified pose and orientation.
180187
rpc GoToPose(GoToPoseRequest) returns (GoToPoseResponse) {
181188
option (google.api.http) = {

anki_vector/messaging/external_interface_pb2.py

Lines changed: 42 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

anki_vector/messaging/external_interface_pb2_grpc.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ def __init__(self, channel):
128128
request_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingRequest.SerializeToString,
129129
response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingResponse.FromString,
130130
)
131+
self.IsImageStreamingEnabled = channel.unary_unary(
132+
'/Anki.Vector.external_interface.ExternalInterface/IsImageStreamingEnabled',
133+
request_serializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledRequest.SerializeToString,
134+
response_deserializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledResponse.FromString,
135+
)
131136
self.GoToPose = channel.unary_unary(
132137
'/Anki.Vector.external_interface.ExternalInterface/GoToPose',
133138
request_serializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.SerializeToString,
@@ -444,6 +449,13 @@ def EnableImageStreaming(self, request, context):
444449
context.set_details('Method not implemented!')
445450
raise NotImplementedError('Method not implemented!')
446451

452+
def IsImageStreamingEnabled(self, request, context):
453+
# missing associated documentation comment in .proto file
454+
pass
455+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
456+
context.set_details('Method not implemented!')
457+
raise NotImplementedError('Method not implemented!')
458+
447459
def GoToPose(self, request, context):
448460
"""Tells Vector to drive to the specified pose and orientation.
449461
"""
@@ -783,6 +795,11 @@ def add_ExternalInterfaceServicer_to_server(servicer, server):
783795
request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingRequest.FromString,
784796
response_serializer=anki__vector_dot_messaging_dot_messages__pb2.EnableImageStreamingResponse.SerializeToString,
785797
),
798+
'IsImageStreamingEnabled': grpc.unary_unary_rpc_method_handler(
799+
servicer.IsImageStreamingEnabled,
800+
request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledRequest.FromString,
801+
response_serializer=anki__vector_dot_messaging_dot_messages__pb2.IsImageStreamingEnabledResponse.SerializeToString,
802+
),
786803
'GoToPose': grpc.unary_unary_rpc_method_handler(
787804
servicer.GoToPose,
788805
request_deserializer=anki__vector_dot_messaging_dot_messages__pb2.GoToPoseRequest.FromString,

anki_vector/messaging/messages.proto

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,15 @@ message EnableImageStreamingResponse
874874
ResponseStatus status = 1;
875875
}
876876

877+
// Request whether or not image streaming is enabled on the robot
878+
message IsImageStreamingEnabledRequest {
879+
}
880+
881+
// Indicates whether or not image streaming is enabled on the robot
882+
message IsImageStreamingEnabledResponse {
883+
bool is_image_streaming_enabled = 1;
884+
}
885+
877886
// Sent when vision modes are automatically disabled due to the SDK no longer having control
878887
// of the robot
879888
message VisionModesAutoDisabled

anki_vector/messaging/messages_pb2.py

Lines changed: 108 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/source/proto.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,14 @@ <h2>Table of Contents</h2>
410410
<a href="#Anki.Vector.external_interface.ImageChunk"><span class="badge">M</span>ImageChunk</a>
411411
</li>
412412

413+
<li>
414+
<a href="#Anki.Vector.external_interface.IsImageStreamingEnabledRequest"><span class="badge">M</span>IsImageStreamingEnabledRequest</a>
415+
</li>
416+
417+
<li>
418+
<a href="#Anki.Vector.external_interface.IsImageStreamingEnabledResponse"><span class="badge">M</span>IsImageStreamingEnabledResponse</a>
419+
</li>
420+
413421
<li>
414422
<a href="#Anki.Vector.external_interface.KeepAlivePing"><span class="badge">M</span>KeepAlivePing</a>
415423
</li>
@@ -2810,6 +2818,38 @@ <h3 id="Anki.Vector.external_interface.ImageChunk">ImageChunk</h3>
28102818

28112819

28122820

2821+
<h3 id="Anki.Vector.external_interface.IsImageStreamingEnabledRequest">IsImageStreamingEnabledRequest</h3>
2822+
Request whether or not image streaming is enabled on the robot
2823+
<p></p>
2824+
2825+
2826+
2827+
2828+
2829+
<h3 id="Anki.Vector.external_interface.IsImageStreamingEnabledResponse">IsImageStreamingEnabledResponse</h3>
2830+
Indicates whether or not image streaming is enabled on the robot
2831+
<p></p>
2832+
2833+
2834+
<table class="field-table">
2835+
<thead>
2836+
<tr><td>Field</td><td>Type</td><td>Description</td></tr>
2837+
</thead>
2838+
<tbody>
2839+
2840+
<tr>
2841+
<td>is_image_streaming_enabled</td>
2842+
<td><a href="#bool">bool</a> </td>
2843+
<td><p> </p></td>
2844+
</tr>
2845+
2846+
</tbody>
2847+
</table>
2848+
<p></p>
2849+
2850+
2851+
2852+
28132853
<h3 id="Anki.Vector.external_interface.KeepAlivePing">KeepAlivePing</h3>
28142854
A null message used by streams to verify that the client is
28152855
still connected.
@@ -6227,6 +6267,11 @@ <h3 id="Anki.Vector.external_interface.ExternalInterface">ExternalInterface</h3>
62276267
<td><p></p></td>
62286268
</tr>
62296269

6270+
<tr>
6271+
<td>IsImageStreamingEnabled</td>
6272+
<td><p></p></td>
6273+
</tr>
6274+
62306275
<tr>
62316276
<td>GoToPose</td>
62326277
<td><p>Tells Vector to drive to the specified pose and orientation.</p></td>

0 commit comments

Comments
 (0)