Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions unit-tests/live/metadata/test-connection-type-found.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# License: Apache 2.0. See LICENSE file in root directory.
# Copyright(c) 2025 Intel Corporation. All Rights Reserved.


#test:device each(D400*)
#test:device each(D500*)


import pyrealsense2 as rs
from rspy import test


test.start("Testing connection type can be detected")

dev, _ = test.find_first_device_or_exit()

if test.check(dev.supports(rs.camera_info.connection_type)):
connection_type = dev.get_info(rs.camera_info.connection_type)
camera_name = dev.get_info(rs.camera_info.name)
if test.check(connection_type):
if 'D457' in camera_name:
test.check(connection_type == "GMSL")
elif 'D555' in camera_name:
test.check(connection_type == "DDS")
else:
test.check(connection_type == "USB")

test.finish()
test.print_results_and_exit()
14 changes: 14 additions & 0 deletions unit-tests/py/rspy/device_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def _find_active_hub():
ykush_hub = _create_ykush()
if ykush_hub:
return ykush_hub
unifi_hub = _create_unifi()
if unifi_hub:
return unifi_hub
import sys
log.d('sys.path=', sys.path)
return None
Expand Down Expand Up @@ -175,3 +178,14 @@ def _create_ykush():
return None
except BaseException:
return None

def _create_unifi():
try:
from rspy import unifi
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have 2 places with this hard coded data, please define it in 1 place and use on both
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return unifi.UniFiSwitch()
except ModuleNotFoundError:
return None
except unifi.NoneFoundError:
return None
except BaseException as e:
return None
29 changes: 23 additions & 6 deletions unit-tests/py/rspy/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def usage():
pyrs_dir = repo.find_pyrs_dir()
sys.path.insert( 1, pyrs_dir )

MAX_ENUMERATION_TIME = 15 # [sec]
MAX_ENUMERATION_TIME = 17 # [sec]

# We need both pyrealsense2 and hub. We can work without hub, but
# without pyrealsense2 no devices at all will be returned.
Expand Down Expand Up @@ -84,14 +84,20 @@ def __init__( self, sn, dev ):
self._usb_location = _get_usb_location(self._physical_port)
except Exception as e:
log.e('Failed to get usb location:', e)

self._mac_address = get_mac_address(dev, self._is_dds)

self._port = None
if hub:
try:
self._port = hub.get_port_by_location(self._usb_location)
if self._is_dds:
self._port = hub.get_port_by_location(self._mac_address)
else:
self._port = hub.get_port_by_location(self._usb_location)
except Exception as e:
log.e('Failed to get device port:', e)
log.d(' physical port is', self._physical_port)
log.d(' USB location is', self._usb_location)
log.d(' location is', self._mac_address if self._is_dds else self._usb_location)

self._removed = False

Expand Down Expand Up @@ -280,7 +286,6 @@ def _device_change_callback( info ):
for device in _device_by_sn.values():
if device.enabled and info.was_removed( device.handle ):
device._removed = True
device._dev = None
log.d( 'device removed:', device.serial_number )
for handle in info.get_new_devices():
sn = handle.get_info( rs.camera_info.firmware_update_id )
Expand Down Expand Up @@ -677,11 +682,11 @@ def _get_usb_location( physical_port ):
#
import winreg
if mi:
registry_path = "SYSTEM\CurrentControlSet\Enum\{}\VID_{}&PID_{}&MI_{}\{}".format(
registry_path = r"SYSTEM\CurrentControlSet\Enum\{}\VID_{}&PID_{}&MI_{}\{}".format(
dev_type, vid, pid, mi, unique_identifier
)
else:
registry_path = "SYSTEM\CurrentControlSet\Enum\{}\VID_{}&PID_{}\{}".format(
registry_path = r"SYSTEM\CurrentControlSet\Enum\{}\VID_{}&PID_{}\{}".format(
dev_type, vid, pid, unique_identifier
)
try:
Expand Down Expand Up @@ -715,6 +720,18 @@ def _get_usb_location( physical_port ):
return port_location


def get_mac_address(dev, is_dds):
if not is_dds:
return None

GET_ETH_CONFIG_OPCODE = 187
raw_command = rs.debug_protocol(dev).build_command(GET_ETH_CONFIG_OPCODE,1)
raw_result = rs.debug_protocol(dev).send_and_receive_raw_data(raw_command)
if raw_result[0] == GET_ETH_CONFIG_OPCODE: # success
return ":".join([hex(num)[2:] for num in raw_result[52:58]]) # bytes for the MAC address

return None

###############################################################################################
if __name__ == '__main__':
import os, sys, getopt
Expand Down
241 changes: 241 additions & 0 deletions unit-tests/py/rspy/unifi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# License: Apache 2.0. See LICENSE file in root directory.
# Copyright(c) 2025 Intel Corporation. All Rights Reserved.

from rspy import log
import time
import platform, re
from rspy import device_hub
import os

if __name__ == '__main__':
import os, sys, getopt
def usage():
ourname = os.path.basename( sys.argv[0] )
print( 'Syntax: unifi [options]' )
print( ' Control the Unifi Ubiquiti hub' )
print( 'Options:' )
print( ' --enable Enable all ports' )
print( ' --disable Disable all ports' )
print( ' --recycle Recycle all ports' )
print( ' --reset Reset the Unifi' )
sys.exit(2)
try:
opts,args = getopt.getopt( sys.argv[1:], '',
longopts = [ 'help', 'recycle', 'enable', 'disable', 'reset' ])
except getopt.GetoptError as err:
print( '-F-', err ) # something like "option -a not recognized"
usage()
if args or not opts:
usage()
# See the end of the file for all the option handling

# we assume that if paramiko is not installed, unifi is not in use
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add to the wiki what should be pip installed

try:
import paramiko
except ModuleNotFoundError:
log.d( 'no paramiko library is available' )
raise

SWITCH_IP = "192.168.11.20"
SWITCH_SSH_USER = "admin"
SWITCH_SSH_PASS = os.environ["UNIFI_SSH_PASSWORD"]


def discover(ip=SWITCH_IP, ssh_username=SWITCH_SSH_USER, ssh_password=SWITCH_SSH_PASS, retries = 0):
"""
Return a UniFi device and connect to it via SSH
"""
log.d("Discovering UniFi devices...")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
for i in range(retries+1):
try:
client.connect(hostname=ip, username=ssh_username,
password=ssh_password, timeout=10)
return client
except Exception as e:
log.w(f"Failed connecting in SSH to UniFi! {e}{', retrying...' if i != retries else ''}")
time.sleep(2)

log.w(f"Failed connecting in SSH to UniFi!")
return None


class NoneFoundError( RuntimeError ):
def __init__( self, message = None ):
super().__init__( message or 'no UniFi device found' )


class UniFiSwitch(device_hub.device_hub):
def __init__(self, ip=SWITCH_IP, ssh_username=SWITCH_SSH_USER, ssh_password=SWITCH_SSH_PASS):
"""
:param ip: IP address of the switch.
:param ssh_username: SSH username.
:param ssh_password: SSH password.
"""
self.ip = ip
self.username = ssh_username
self.password = ssh_password

self.client = discover(self.ip, self.username, self.password)
if self.client is None:
raise NoneFoundError()

# ports 1-4 are PoE, 5-8 are regular ports
self.POE_PORTS = [1, 2, 3, 4]
self.REGULAR_PORTS = [5, 6, 7, 8]

self.mac_port_dict = None

def _init_mac_port_dict(self):
"""
Initialize mac_port_dict which maps MAC addresses to port numbers
"""
cmd_out = self._run_command("swctrl mac show")
self.mac_port_dict = {}

for line in cmd_out.splitlines()[2:-1]:
port, vlan, mac, ip = line.split()[:4] # ip is missing sometimes, best to use MAC address
self.mac_port_dict[mac] = port


def _get_port_stats(self):
"""
Get the status of all ports on the switch
return: a dictionary of port numbers to whether they are up or down
"""
cmd_out = self._run_command("swctrl port show")
port_stats = {}
for line in cmd_out.splitlines()[3:]:
port_num = int(line.split()[0])
is_port_up = line.split()[1][0]=="U"
port_stats[port_num] = is_port_up

return port_stats

def connect(self, reset=False):
if self.client is None:
self.client = discover(self.ip, self.username, self.password) # assuming no throw because it's done in the c'tor

if reset:
# rebooting the switch takes over a minute, so the reboot code is commented out
log.w("reset flag passed to unifi switch, ignoring it")
return
# self._run_command("reboot")
# time.sleep(5)
# # we need to reconnect to the device after it's rebooted, might take several tries
# self.disconnect()
# self.client = discover(retries=5)

def is_connected(self):
if self.client is None:
return False
# if the client exists, make sure the connection is open
transport = self.client.get_transport()
return transport is not None and transport.is_active()

def disconnect(self):
if self.client:
self.client.close()
self.client = None

def all_ports(self):
"""
:return: a list of all PoE ports on the switch
"""
return self.POE_PORTS

def ports(self):
Copy link
Copy Markdown
Collaborator

@Nir-Az Nir-Az Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add functions description like we have on Acroname.
e.g.

        """
        :return: a list of all ports currently occupied (and enabled)
        """

"""
:return: a list of all PoE ports that are currently enabled
"""
return [port for port, is_on in self._get_port_stats().items() if is_on and port in self.all_ports()]

def is_port_enabled(self, port):
return port in self.ports()

def port_state(self, port):
"""
Get the state of a port
:param port: port number
:return: "enabled" if the port is enabled, "disabled" otherwise
"""
return "enabled" if self.is_port_enabled(port) else "disabled"

def _run_command(self, command):
"""
Run a command on the switch
:param command: the command to run
:return: the output of the command
"""
if not self.is_connected():
raise Exception("Not connected to switch.")
stdin, stdout, stderr = self.client.exec_command(command)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
log.f(f"Error running command '{command}': {err}")
# print(f"executed \"{command}\" successfully")
return out

def enable_ports(self, ports=None, disable_other_ports=False, sleep_on_change=0):
if ports is None:
ports = self.all_ports()

if disable_other_ports:
other_ports = set(self.all_ports()) - set(ports)
self.disable_ports(list(other_ports), sleep_on_change)

ports_str = ','.join(map(str, ports))
cmd = f"swctrl poe set auto id {ports_str}" # cmd should be able to get multiple ports at once
self._run_command(cmd)

if sleep_on_change:
time.sleep(sleep_on_change)
return True

def disable_ports(self, ports=None, sleep_on_change=0):
if ports is None:
ports = self.all_ports()

if any(ports) not in self.POE_PORTS:
log.w("Attempted to disable a non-poe port! ignoring.")
ports = [port for port in ports if port in self.POE_PORTS]

ports_str = ','.join(map(str, ports))
cmd = f"swctrl poe set off id {ports_str}"
self._run_command(cmd)

if sleep_on_change:
time.sleep(sleep_on_change)
return True

def get_port_by_location(self, mac_address):
"""
Get the port number of a device connected to the switch
Since this isn't usb, we use MAC address ("network location")
:param mac_address: MAC address of the device
:return: port number
"""
if self.mac_port_dict is None:
self._init_mac_port_dict()
return self.mac_port_dict[mac_address]


# Example usage:
if __name__ == '__main__':
unifi = UniFiSwitch()

for opt,arg in opts:
if opt in ('--enable'):
unifi.connect()
unifi.enable_ports() # so ports() will return all
elif opt in ('--disable'):
unifi.connect()
unifi.disable_ports()
elif opt in ('--recycle'):
unifi.connect()
unifi.enable_ports() # so ports() will return all
unifi.recycle_ports()
elif opt in ('--reset'):
unifi.connect( reset = True )
Loading