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.

249 lines
8.4 KiB

# License: Apache 2.0. See LICENSE file in root directory.
# Copyright(c) 2023 Intel Corporation. All Rights Reserved.
"""
Yepkit USB Switchable Hub
Pip page:
https://pypi.org/project/yepkit-pykush/
Github:
https://github.com/Yepkit/pykush
"""
from rspy import log
import time
import platform, re
from rspy import device_hub
if __name__ == '__main__':
import os, sys, getopt
def usage():
ourname = os.path.basename( sys.argv[0] )
print( 'Syntax: ykush [options]' )
print( ' Control the YKUSH USB hub' )
print( 'Options:' )
print( ' --enable Enable all ports' )
print( ' --disable Disable all ports' )
print( ' --recycle Recycle all ports' )
print( ' --reset Reset the YKUSH' )
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 pykush is not installed, it is not in use
try:
import pykush
except ModuleNotFoundError:
log.d( 'no pykush library is available' )
raise
class NoneFoundError(pykush.YKUSHNotFound):
def __init__(self, message=None):
super().__init__(self, message or 'no YKUSH module found')
class Ykush(device_hub.device_hub):
def __init__(self):
super().__init__()
if discover() is None:
raise NoneFoundError() # raise an error if there is no hub connected
self._ykush = None
self.NUM_PORTS = 3
def connect(self, reset = False, serial = None, path = None):
"""
Connect to a YKUSH device
:param reset: When true, the YKUSH will be reset as part of the connection process
"""
if reset:
log.d("resetting YKUSH...")
import subprocess
subprocess.run("ykushcmd ykush3 --reset".split())
time.sleep(5)
if not self._ykush:
self._ykush = discover(serial=serial, path=path)
def is_connected(self):
return self._ykush is not None
def disconnect(self):
if self._ykush:
del self._ykush
self._ykush = None
def all_ports(self):
"""
:return: a list of all possible ports, even if currently unoccupied or disabled
"""
return range(1,self.NUM_PORTS+1)
def ports(self):
"""
:return: a list of all ports currently enabled
"""
occupied_ports = []
for port in self.all_ports():
if self._ykush.get_port_state(port):
occupied_ports.append( port )
return occupied_ports
def is_port_enabled( self, port ):
"""
query if the input port number is enabled through YKUSH
:param port: port number;
:return: True if YKUSH enabled this port, False otherwise
"""
return self.port_state(port) == "Enabled"
def port_state( self, port ):
if port < 1 or port > self.NUM_PORTS:
raise ValueError( f"port number must be [1-{self.NUM_PORTS}]")
if not self._ykush:
raise NoneFoundError("No YKUSH found")
return "Enabled" if self._ykush.get_port_state(port) else "Disabled"
def enable_ports( self, ports = None, disable_other_ports = False, sleep_on_change = 0 ):
"""
Set enable state to provided ports
:param ports: List of port numbers; if not provided, enable all ports
:param disable_other_ports: if True, the ports not in the list will be disabled
:param sleep_on_change: Number of seconds to sleep if any change is made
:return: True if no errors found, False otherwise
"""
result = True
changed = False
for port in self.all_ports():
if ports is None or port in ports:
if not self.is_port_enabled( port ):
action_result = self._ykush.set_port_state(port, pykush.YKUSH_PORT_STATE_UP)
if action_result:
changed = True
else:
result = False
log.e("Failed to enable port", port)
#
elif disable_other_ports:
if self.is_port_enabled( port ):
action_result = self._ykush.set_port_state(port, pykush.YKUSH_PORT_STATE_DOWN)
if action_result:
changed = True
else:
result = False
log.e("Failed to disable port", port)
if changed and sleep_on_change:
time.sleep(sleep_on_change)
return result
def disable_ports( self, ports = None, sleep_on_change = 0 ):
"""
:param ports: List of port numbers; if not provided, disable all ports
:param sleep_on_change: Number of seconds to sleep if any change is made
:return: True if no errors found, False otherwise
"""
result = True
changed = False
for port in self.all_ports():
if ports is None or port in ports:
if self.is_port_enabled( port ):
action_result = self._ykush.set_port_state(port,pykush.YKUSH_PORT_STATE_DOWN)
if action_result:
changed = True
else:
result = False
log.e("Failed to disable port", port)
if changed and sleep_on_change:
time.sleep(sleep_on_change)
return result
if 'windows' in platform.system().lower():
def get_port_by_location(self, usb_location):
"""
"""
if usb_location:
#
# T265 locations look differently...
match = re.fullmatch(r'Port_#(\d+)\.Hub_#(\d+)', usb_location, re.IGNORECASE)
if match:
# We don't know how to get the port from these yet!
return None # int(match.group(2))
else:
split_location = [int(x) for x in usb_location.split('.')]
# return the last non-zero numbers, used when connecting using an additional hub
# ex: laptop -> hub -> ykush
index = [i for i in split_location[::-1] if i != 0][0]
# only the last digit is necessary
return get_port_from_usb(index)
else:
def get_port_by_location(self, usb_location):
"""
"""
# if needed at some point, YKUSH VendorID is '04d8'
return get_port_from_usb(int(usb_location.split(".")[1]))
ykush_dev = None
def discover(retries = 0, serial = None, path = None):
"""
Return a YKUSH device. Raise YKUSHNotFound if none found
If it was called more than once when there's only one device connected, return it
"""
global ykush_dev
if ykush_dev is None:
log.d('discovering YKUSH modules ...')
for i in range(retries + 1):
try:
ykush_dev = pykush.YKUSH(serial=serial, path=path)
except pykush.YKUSHNotFound as e:
log.w("YKUSH device not found!")
except Exception as e:
log.w("Unexpected error occurred!", e)
finally:
if not ykush_dev and i < retries:
time.sleep(1)
else:
break
return ykush_dev
def get_port_from_usb( usb_index ):
"""
Based on last USB location index, provide the port number
"""
ykush_port_usb_map = {1: 3,
2: 2,
3: 1}
return ykush_port_usb_map[usb_index]
if __name__ == '__main__':
ykush = Ykush()
for opt,arg in opts:
if opt in ('--enable'):
ykush.connect()
ykush.enable_ports() # so ports() will return all
elif opt in ('--disable'):
ykush.connect()
ykush.disable_ports()
elif opt in ('--recycle'):
ykush.connect()
ykush.enable_ports() # so ports() will return all
ykush.recycle_ports()
elif opt in ('--reset'):
ykush.connect( reset = True )