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
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()
|