You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
13 KiB

# License: Apache 2.0. See LICENSE file in root directory.
# Copyright(c) 2024 Intel Corporation. All Rights Reserved.
#test:donotrun:!dds
#test:retries:gha 2
from rspy import log, test
import pyrealdds as dds
from time import sleep
import re
with test.remote.fork( nested_indent=' S' ) as remote:
if remote is None: # we're the server fork
dds.debug( log.is_debug_on(), log.nested )
participant = dds.participant()
participant.init( 123, 'server' )
def create_device_info( props ):
global broadcasters, publisher
serial = props.setdefault( 'serial', str( participant.create_guid() ) )
props.setdefault( 'name', f'device{serial}' )
props.setdefault( 'topic-root', f'device{serial}' )
di = dds.message.device_info.from_json( props )
return di
def create_server( root ):
s1p1 = dds.video_stream_profile( 9, dds.video_encoding.rgb, 10, 10 )
s1profiles = [s1p1]
s1 = dds.color_stream_server( 's1', 'sensor' )
s1.init_profiles( s1profiles, 0 )
s1.init_options( [
dds.option.from_json( ['Backlight Compensation', 0, 0, 1, 1, 0, 'Backlight custom description'] ),
dds.option.from_json( ['Custom Option', 5., -10, 10, 1, -5., 'Description'] )
] )
server = dds.device_server( participant, root )
server.init( [s1], [], {} )
return server
def create_server_2( root ):
s1p1 = dds.video_stream_profile( 3, dds.video_encoding.z16, 100, 100 )
s1profiles = [s1p1]
s1 = dds.color_stream_server( 's2', 'sensor2' )
s1.init_profiles( s1profiles, 0 )
s1.init_options( [
dds.option.from_json( ['Another Option', 7., 5, 15, 2, 7., 'Another Option'] )
] )
server = dds.device_server( participant, root )
server.init( [s1], [], {} )
return server
# From here down, we're in "interactive" mode (see test-watcher.py)
# ...
raise StopIteration() # quit the remote
dds.debug( log.is_debug_on(), 'C ' )
log.nested = 'C '
import threading
from rspy.stopwatch import Stopwatch
participant = dds.participant()
participant.init( 123, "client" )
# We listen directly on the device-info topic
device_info_topic = dds.message.device_info.create_topic( participant, dds.topics.device_info )
device_info = dds.topic_reader( device_info_topic )
broadcast_received = threading.Event()
broadcast_devices = []
def on_device_info_available( reader ):
while True:
msg = dds.message.flexible.take_next( reader )
if not msg:
break
j = msg.json_data()
log.d( f'on_device_info_available {j}' )
global broadcast_devices
broadcast_devices.append( j )
broadcast_received.set()
device_info.on_data_available( on_device_info_available )
device_info.run( dds.topic_reader.qos() )
def detect_broadcast():
global broadcast_received, broadcast_devices
broadcast_received.clear()
broadcast_devices = []
def wait_for_broadcast( count=1, timeout=1 ):
while timeout > 0:
sw = Stopwatch()
if not broadcast_received.wait( timeout ):
raise TimeoutError( 'timeout waiting for broadcast' )
if count <= len(broadcast_devices):
return
broadcast_received.clear()
timeout -= sw.get_elapsed()
if count is None:
raise TimeoutError( 'timeout waiting broadcast' )
raise TimeoutError( f'timeout waiting for {count} broadcasts; {len(broadcast_devices)} received' )
class broadcast_expected:
def __init__( self, n_expected=1, timeout=1 ):
self._timeout = timeout
self._n_expected = n_expected
def __enter__( self ):
detect_broadcast()
def __exit__( self, type, value, traceback ):
if type is None: # If an exception is thrown, don't do anything
wait_for_broadcast( count=self._n_expected, timeout=self._timeout )
# Start a watcher, too...
change_received = threading.Event()
devices_added = 0
devices_removed = 0
devices = dict()
def on_device_added( watcher, dev ):
global devices_added, devices
devices_added += 1
log.d( '+++-> device added', dev )
devices[dev.device_info().topic_root] = dev
change_received.set()
test.check( dev.is_online() )
def on_device_removed( watcher, dev ):
global devices_removed, devices
devices_removed += 1
log.d( '<---- device removed', dev )
del devices[dev.device_info().topic_root]
change_received.set()
def detect_change():
global devices_added, devices_removed
change_received.clear()
devices_added = 0
devices_removed = 0
def wait_for_change( n_added=0, n_removed=0, timeout=3 ):
global devices_added, devices_removed
while timeout > 0:
sw = Stopwatch()
if not change_received.wait( timeout ):
raise TimeoutError( 'timeout waiting for add/remove' )
change_received.clear()
if n_added <= devices_added and n_removed <= devices_removed:
return
timeout -= sw.get_elapsed()
raise TimeoutError( f'timeout waiting for {count} add/removes; {n_changes} received' )
class change_expected:
def __init__( self, n_added=0, n_removed=0, timeout=3 ):
self._timeout = timeout
self._n_added = n_added
self._n_removed = n_removed
def __enter__( self ):
detect_change()
def __exit__( self, type, value, traceback ):
if type is None: # If an exception is thrown, don't do anything
wait_for_change( n_added=self._n_added, n_removed=self._n_removed, timeout=self._timeout )
global devices_added, devices_removed
test.check_equal( devices_added, self._n_added )
test.check_equal( devices_removed, self._n_removed )
watcher = dds.device_watcher( participant )
watcher.on_device_added( on_device_added )
watcher.on_device_removed( on_device_removed )
watcher.start()
#############################################################################################
with test.closure( "Broadcast one device" ):
with change_expected( n_added=1 ):
remote.run( 'di1 = create_device_info({ "serial" : "123" })' )
remote.run( 'd1 = create_server( di1.topic_root )' )
remote.run( 'd1.broadcast( di1 )' )
test.check_equal( len(broadcast_devices), 1 )
test.check_equal( len(devices), 1 )
d1 = devices[f'device123'] # remember it -- we'll re-add it later and want to test it's the same!
d1guid = d1.guid()
#############################################################################################
with test.closure( "Broadcast second device" ):
with change_expected( n_added=1 ):
remote.run( 'di2 = create_device_info({ "serial" : "456" })' )
remote.run( 'd2 = create_server( di2.topic_root )' )
remote.run( 'd2.broadcast( di2 )' )
test.check_equal( len(broadcast_devices), 3 ) # each broadcast is of ALL the devices
test.check_equal( len(devices), 2 )
d2 = devices[f'device456'] # remember it -- we'll re-add it later and want to test it's the same!
d2.wait_until_ready()
d2option = d2.streams()[0].options()[0]
d2.query_option_value( d2option )
#############################################################################################
with test.closure( "Add another client; expect rebroadcast of all" ):
with broadcast_expected( 2 ):
reader_2 = dds.topic_reader( device_info_topic )
reader_2.run( dds.topic_reader.qos() )
test.check_equal( len(broadcast_devices), 2 )
del reader_2
#############################################################################################
with test.closure( "We should see both in the watcher" ):
test.check_equal( len(devices), 2 )
for dev in devices.values():
test.info( 'device', dev )
test.check( watcher.is_device_broadcast( dev ) )
#############################################################################################
with test.closure( "Disconnect one & remove the other" ):
with change_expected( n_removed=2 ):
remote.run( 'd1.broadcast_disconnect( dds.time( 2. ) )' )
remote.run( 'del d2' )
test.check_equal( len(watcher.devices()), 0 )
#############################################################################################
with test.closure( "Both should go offline & not ready" ):
test.check_false( watcher.is_device_broadcast( d1 ) )
test.check_false( d1.is_online() )
test.check_false( d1.is_ready() )
test.check_false( watcher.is_device_broadcast( d2 ) )
test.check_false( d2.is_online() )
test.check_false( d2.is_ready() )
#############################################################################################
with test.closure( "Offline device shouldn't accept controls" ):
test.check_throws( lambda:
d1.query_option_value( d1.streams()[0].options()[0] ),
RuntimeError, 'device is offline' )
#############################################################################################
with test.closure( "Unbroadcast server still sends out init messages" ):
info = dds.message.device_info()
info.name = 'Test Device'
info.topic_root = 'device123'
dds.device( participant, info ).wait_until_ready() # New subscriber to notifications will trigger new handshake
#############################################################################################
with test.closure( "Offline device should not get ready (ready means online)" ):
test.check_false( d1.is_ready() )
test.check_false( d1.is_online() )
#############################################################################################
with test.closure( "Rebroadcast the disconnected device" ):
with change_expected( n_added=1 ):
remote.run( 'd1.broadcast( di1 )' )
test.check( watcher.is_device_broadcast( d1 ) )
test.check( d1.is_online() )
#############################################################################################
with test.closure( "It needs to reinitialize to get ready again" ):
d1.wait_until_ready() # NOTE: requires server to resend init messages on broadcast
test.check( d1.is_ready() )
test.check_equal( len(devices), 1 )
test.check_equal( devices['device123'].guid(), d1guid ) # Same device
d1.query_option_value( d1.streams()[0].options()[0] )
#############################################################################################
with test.closure( "Recreate device456, new content, without a broadcast" ):
detect_broadcast()
detect_change()
remote.run( 'd2 = create_server_2( di2.topic_root )' )
sleep( 2 );
#############################################################################################
with test.closure( "It should not get ready (because it's not online)" ):
test.check_equal( len(broadcast_devices), 0 )
test.check_equal( devices_added, 0 )
test.check_false( d2.is_online() )
#############################################################################################
with test.closure( "Broadcast it again; it should come online" ):
with change_expected( n_added=1 ):
remote.run( 'd2.broadcast( di2 )' )
test.check( d2.is_online() )
test.check( watcher.is_device_broadcast( d2 ) )
#############################################################################################
with test.closure( "It should get ready, too" ):
d2.wait_until_ready()
test.check( d2.is_ready() )
#############################################################################################
with test.closure( "Check new content" ):
test.check_throws( lambda:
d2.query_option_value( d2option ),
RuntimeError, r'''["query-option" error] device option 'Backlight Compensation' not found''' )
if test.check_equal( len(d2.streams()), 1 ):
stream = d2.streams()[0]
test.check_equal( stream.name(), 's2' )
options = stream.options()
if test.check_equal( len(options), 1 ):
d2.query_option_value( options[0] )
del watcher
del device_info
del participant
test.print_results_and_exit()