Skip to content

Commit d0a2ebb

Browse files
authored
Retry D585S service-mode switch to handle post-enumeration FW readiness race (realsenseai#15329)
2 parents ed7d8e3 + 1e2e5f9 commit d0a2ebb

5 files changed

Lines changed: 51 additions & 17 deletions

File tree

unit-tests/conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,14 +895,17 @@ def test_device_wrapped(test_device):
895895
is_d585s = dev.supports(rs.camera_info.name) and "D585S" in dev.get_info(rs.camera_info.name)
896896
safety_sensor = None
897897
if is_d585s:
898+
from rspy import tests_wrapper # local import: pulls in pyrealsense2, unavailable in infra-tests
898899
safety_sensor = dev.first_safety_sensor()
899900
if safety_sensor.get_option(rs.option.safety_mode) != rs.safety_mode.service:
900901
# Will throw on failure — intentional so we fail the test rather than run without service mode.
901-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
902+
# Retries internally: the FW needs a few seconds after enumeration before it accepts the switch.
903+
tests_wrapper.set_safety_mode(safety_sensor, rs.safety_mode.service)
902904
yield dev, ctx
903905
if safety_sensor is not None:
904906
try:
905-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
907+
# tests_wrapper already imported in the setup block above (still bound across the yield)
908+
tests_wrapper.set_safety_mode(safety_sensor, rs.safety_mode.run)
906909
except Exception as e:
907910
# Best-effort: don't mask test failures, and the device may already be reset by teardown time.
908911
log.warning(f"safety_mode restore skipped for {sn}: {e}")

unit-tests/live/d500/safety/pytest-interface-config-get-set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def test_verify_same_table_after_camera_reboot(test_device):
210210
safety_sensor = new_dev.first_safety_sensor()
211211

212212
log.debug("Setting operational mode to service")
213-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
213+
tw.set_safety_mode(safety_sensor, rs.safety_mode.service)
214214
assert safety_sensor.get_option(rs.option.safety_mode) == float(rs.safety_mode.service)
215215

216216
config_after_reboot = safety_sensor.get_safety_interface_config(rs.calib_location.flash)

unit-tests/live/d500/safety/pytest-operational-mode-stress.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55
import pyrealsense2 as rs
66
from pytest_check import check
7+
from rspy import tests_wrapper as tw
78
import logging
89
log = logging.getLogger(__name__)
910

@@ -25,13 +26,13 @@ def test_operational_mode_stress(test_device):
2526
for i in range(ITERATIONS_COUNT):
2627
log.debug("stress test iteration: %s", i)
2728
log.debug("command service mode")
28-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
29+
tw.set_safety_mode(safety_sensor, rs.safety_mode.service)
2930
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.service))
3031

3132
log.debug("command standby mode")
32-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.standby)
33+
tw.set_safety_mode(safety_sensor, rs.safety_mode.standby)
3334
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.standby))
3435

3536
log.debug("command run mode")
36-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
37+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
3738
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))

unit-tests/live/d500/safety/pytest-operational-mode.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import time
66
import pyrealsense2 as rs
77
from pytest_check import check
8+
from rspy import tests_wrapper as tw
89
import logging
910
log = logging.getLogger(__name__)
1011

@@ -41,7 +42,7 @@ def test_pause_resume_no_impact_on_streaming(test_context):
4142
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run)) # verify default
4243

4344
log.debug("Command standby mode")
44-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.standby)
45+
tw.set_safety_mode(safety_sensor, rs.safety_mode.standby)
4546
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.standby))
4647
verify_frames_received(pipe, count=10)
4748

@@ -51,7 +52,7 @@ def test_pause_resume_no_impact_on_streaming(test_context):
5152
verify_frames_received(pipe, count=10)
5253

5354
log.debug("Command run mode")
54-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
55+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
5556
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))
5657
verify_frames_received(pipe, count=10)
5758
finally:
@@ -76,7 +77,7 @@ def test_resume_to_maintenance_keeps_video_streaming(test_context):
7677
safety_sensor = pipeline_device.first_safety_sensor()
7778

7879
log.debug("Command run mode")
79-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
80+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
8081
log.debug(f"Current mode: {safety_sensor.get_option(rs.option.safety_mode)}")
8182
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))
8283
# Verify that on RUN mode we get frames
@@ -86,14 +87,14 @@ def test_resume_to_maintenance_keeps_video_streaming(test_context):
8687
time.sleep(2)
8788

8889
log.debug("Command service mode")
89-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
90+
tw.set_safety_mode(safety_sensor, rs.safety_mode.service)
9091
log.debug(f"Current mode: {safety_sensor.get_option(rs.option.safety_mode)}")
9192
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.service))
9293
verify_frames_received(pipe, count=10)
9394

9495
# Restore Run mode
9596
log.debug("Command run mode")
96-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
97+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
9798
log.debug(f"Current mode: {safety_sensor.get_option(rs.option.safety_mode)}")
9899
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))
99100
# Verify that on RUN mode we get frames
@@ -118,20 +119,20 @@ def test_resume_to_maintenance_keeps_safety_streaming(test_context):
118119
safety_sensor = pipeline_device.first_safety_sensor()
119120

120121
log.debug("Command run mode")
121-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
122+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
122123
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))
123124
# Verify that on RUN mode we get frames
124125
verify_frames_received(pipe, count=10)
125126

126127
log.debug("Command service mode")
127-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
128+
tw.set_safety_mode(safety_sensor, rs.safety_mode.service)
128129
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.service))
129130
# Verify that on SERVICE mode we still get frames
130131
verify_frames_received(pipe, count=10)
131132

132133
# Restore Run mode
133134
log.debug("Command run mode")
134-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
135+
tw.set_safety_mode(safety_sensor, rs.safety_mode.run)
135136
check.equal(safety_sensor.get_option(rs.option.safety_mode), float(rs.safety_mode.run))
136137

137138
# We know that returning to run mode will not restart the safety stream.

unit-tests/py/rspy/tests_wrapper.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,48 @@
44
# Some future models might need to wrap the tests in setup and teardown steps.
55
# Don't remove this file even if current implementation is empty...
66

7+
import time
78
import pyrealsense2 as rs
89
from rspy import log
10+
from rspy.stopwatch import Stopwatch
11+
12+
13+
# The D585S FW can transiently reject a safety_mode change (RuntimeError "Failed to set the
14+
# option to value N") for several seconds after USB enumeration and after disruptive operations
15+
# (heavy streaming, hardware_reset). Retry until it takes or we time out. Use this for every
16+
# D585S safety_mode transition so the tests don't rely on luck / pytest-retry.
17+
def set_safety_mode( safety_sensor, mode, timeout = 8, interval = 0.5 ):
18+
sw = Stopwatch()
19+
last_exc = None
20+
attempt = 0
21+
while True:
22+
attempt += 1
23+
last_exc = None # reset so a stale error from an earlier iteration can't leak out at timeout
24+
try:
25+
safety_sensor.set_option( rs.option.safety_mode, mode )
26+
if safety_sensor.get_option( rs.option.safety_mode ) == float( mode ):
27+
log.i( f"safety_mode set to {mode} after {attempt} attempt(s), {sw.get_elapsed():.1f}s" )
28+
return
29+
log.w( f"safety_mode set to {mode} accepted but read-back mismatched on attempt {attempt}, retrying" )
30+
except Exception as e:
31+
last_exc = e
32+
if sw.get_elapsed() >= timeout:
33+
if last_exc:
34+
raise last_exc
35+
raise RuntimeError( f"failed to set safety_mode to {mode} within {timeout}s" )
36+
time.sleep( interval )
37+
938

1039
# Many operations, such as setting options, can take place only in safety service mode
1140
def start_wrapper( dev = None ):
1241
if "D585S" in dev.get_info(rs.camera_info.name):
1342
safety_sensor = dev.first_safety_sensor()
14-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.service)
43+
set_safety_mode( safety_sensor, rs.safety_mode.service )
1544

1645
def stop_wrapper( dev = None ):
1746
if "D585S" in dev.get_info(rs.camera_info.name):
1847
try:
1948
safety_sensor = dev.first_safety_sensor()
20-
safety_sensor.set_option(rs.option.safety_mode, rs.safety_mode.run)
49+
set_safety_mode( safety_sensor, rs.safety_mode.run )
2150
except Exception as e:
22-
log.e(f"Cleanup failed: could not set safety_mode back to run: {e}")
51+
log.e(f"Cleanup failed: could not set safety_mode back to run: {e}")

0 commit comments

Comments
 (0)