Files
controller-wisetopdataserver/python/biopro/device/manager.py
T
peterlu14 d866d48cbf -testing
2023-06-14 12:08:04 +08:00

1158 lines
39 KiB
Python

from typing import AnyStr
from ast import literal_eval
from biopro.devlib.data import DataDecodeFormat
from biopro.devlib.library import *
from biopro.devlib.menu import Menu
from biopro.devlib.parameter import *
from biopro.impl.interface_uart import UARTInterface
from biopro.impl.selector import Selector
from biopro.server.socket import ServerShutdownException
from biopro.server.api import ApiRequests
from biopro.text import *
from biopro.util.address import address_str
from biopro.util.cli_base import CliOptions, cli_options
from biopro.util.console import GREEN
from biopro.util.lock import Synchronized, synchronized
from biopro.util.logger import Logger, logging_verbose, logging_info
from biopro.util.stack import print_exception
from .device_cc2650 import CC2650DeviceOptions
_RUNTIME_COMPILE = False
T = TypeVar('T')
def build_url(path: str, **options: Any) -> str:
if len(options) > 0:
return path + '?' + '&'.join(map(lambda it: '%s=%s' % (it[0], str(it[1])), options.items()))
else:
return path
# noinspection PyUnusedLocal
class DeviceManagerOptions(CC2650DeviceOptions, CliOptions):
DEFAULT_INTERFACE = 'fallback:CC2650,Dummy'
DEFAULT_USB_PORT = '/dev/ttyAMA0'
def __init__(self):
super().__init__()
self.repository = DeviceLibraryRepository()
self.interface: str = self.DEFAULT_INTERFACE
# selector
self.select_device: Optional[Tuple[int, ...]] = None
self.device_log_level = Logger.LEVEL_INFO
@cli_options('-L', '--library', value='PATH')
def _library_path(self, opt: str, value: str):
self.repository.add_library_path(value)
@cli_options('-i', '--interface', value='INT')
def _interface(self, opt: str, value: str):
"""hardware interface. could be:
CC2650 : Bluetooth Low Energy CC2650 with multiple board
CC2650_S : Bluetooth Low Energy CC2650 with single board
NULL : null hardware (debug use).
Dummy : Dummy hardware
fallback:INT,... : try using interface (default: %DEFAULT_INTERFACE%)
"""
self.interface = value
@cli_options('--selector', value='DEVICE,...')
def _select_device(self, opt: str, value: str):
"""only use certain selector channel, it can reduce the controller setup times."""
self.select_device = tuple(map(int, value.split(',')))
@cli_options('--device-log-level', value='LEVEL')
def _device_log_level(self, opt: str, value: str):
"""device log level. could be (case insensitive):
verbose
info
warn
error
disable, quiet
"""
if not _RUNTIME_COMPILE:
value = value.lower()
if value == 'verbose':
self.device_log_level = Logger.LEVEL_VERBOSE
elif value == 'info':
self.device_log_level = Logger.LEVEL_INFO
elif value == 'warn':
self.device_log_level = Logger.LEVEL_WARN
elif value == 'error':
self.device_log_level = Logger.LEVEL_ERROR
elif value == 'disable' or value == 'quiet':
self.device_log_level = Logger.LEVEL_QUIET
else:
raise ValueError('illegal log level : ' + str(value))
else:
print('ignore', opt, 'in product release')
class DeviceHardwareInterface:
INTERFACE_CC2650 = 'CC2650'
INTERFACE_CC2650_S = 'CC2650_S'
INTERFACE_NULL = 'NULL'
INTERFACE_DUMMY = 'Dummy'
INTERFACE_SHUTDOWN = 'Shutdown'
__slots__ = '_master', '_interface', '_options'
def __init__(self, master: MasterDevice, interface: str, options: DeviceManagerOptions):
self._master = master
self._interface = interface
self._options = options
@classmethod
def fallback(cls, *interface: str):
return 'fallback:' + ','.join(interface)
@property
def interface(self) -> str:
return self._interface
def new_master(self, interface: Optional[str] = None) -> MasterDevice:
if interface is None:
interface = self._interface
if interface == self.INTERFACE_SHUTDOWN:
raise ServerShutdownException()
elif interface == self.INTERFACE_NULL or interface == self.INTERFACE_DUMMY:
self._interface = interface
return NullMasterDevice()
elif interface == self.INTERFACE_CC2650_S:
self._interface = interface
return self.__new_cc2650_single_master()
elif interface == self.INTERFACE_CC2650:
self._interface = interface
return self.__new_cc2650_master()
elif interface.startswith('fallback:'):
for fallback in interface[9:].split(','):
self._master.log_verbose('fallback device', fallback)
if fallback == self.INTERFACE_SHUTDOWN:
raise ServerShutdownException()
try:
return self.new_master(fallback)
except BaseException as e:
self._master.log_warn(e)
print_exception(self._master)
continue
raise RuntimeError('unsupported master device : ' + interface)
def new_central_master(self, interface: Optional[str] = None) -> MasterDevice:
if interface is None:
interface = self._interface
if interface == self.INTERFACE_SHUTDOWN:
raise ServerShutdownException()
elif interface == self.INTERFACE_NULL or interface == self.INTERFACE_DUMMY:
self._interface = interface
return NullMasterDevice()
elif interface == self.INTERFACE_CC2650_S:
self._interface = interface
return self.__new_cc2650_single_master()
elif interface == self.INTERFACE_CC2650:
self._interface = interface
return self.__new_cc2650_central_master()
elif interface.startswith('fallback:'):
for fallback in interface[9:].split(','):
self._master.log_verbose('fallback device', fallback)
if fallback == self.INTERFACE_SHUTDOWN:
raise ServerShutdownException()
try:
return self.new_central_master(fallback)
except BaseException as e:
self._master.log_warn(e)
print_exception(self._master)
continue
def __new_cc2650_single_master(self) -> MasterDevice:
from .device_cc2650 import CC2650SingleMasterDevice
uart = self.__new_uart_interface()
uart.reset()
uart.flush()
return CC2650SingleMasterDevice(self._master, uart, self._options)
def __new_cc2650_master(self) -> MasterDevice:
from .device_cc2650 import CC2650MultiMasterDevice
options = self._options
if options.select_device is not None:
Selector.set_selector(options.select_device)
uart = self.__new_uart_interface()
uart.reset()
uart.flush()
self._master.log_verbose('selector interface', Selector.UART_SELECTOR)
selector = Selector.get(Selector.UART_SELECTOR)
return CC2650MultiMasterDevice(self._master, uart, selector, options)
def __new_cc2650_central_master(self) -> MasterDevice:
from .device_cc2650 import CC2650MultiMasterCentralDevice
options = self._options
if options.select_device is not None:
Selector.set_selector(options.select_device)
uart = self.__new_uart_interface()
uart.reset()
uart.flush()
self._master.log_verbose('selector interface', Selector.UART_SELECTOR)
selector = Selector.get(Selector.UART_SELECTOR)
mem_selector = Selector.get(Selector.MEM_SELECTOR)
return CC2650MultiMasterCentralDevice(self._master, uart, mem_selector, selector, options)
def __new_uart_interface(self) -> UARTInterface:
tty = DeviceManagerOptions.DEFAULT_USB_PORT
if Path(tty).exists():
self._master.log_verbose('uart interface', tty)
return UARTInterface(tty)
raise FileNotFoundError(tty)
class DeviceInternalCommandHandler:
__slots__ = ()
def device_internal_command(self, device: int, oper: str, value: Any) -> bool:
"""handle device internal command.
:param device: device ID
:param oper: operator
:param value: operator value
:return consumed or not
"""
return True
# why inherit MasterDevice ?
class DeviceManager(MasterDevice, Synchronized):
"""device manager. It manager the device libraries, found device, connected device
and the control of the master device.
"""
__slots__ = ('_handler', '_options',
'_library_path', '_library',
'_interface', '_master', '_found_history', '_found', '_device', '_master_log_level')
def __init__(self,
options: DeviceManagerOptions,
handler: Optional[DeviceInternalCommandHandler] = None):
"""
:param options: manager options.
:param handler: device internal command handler
"""
super().__init__(log_name_color=GREEN)
Synchronized.__init__(self)
if handler is None:
handler = DeviceInternalCommandHandler()
# control server api
self._handler: DeviceInternalCommandHandler = handler
self._options: DeviceManagerOptions = options
# library
self._repository = DeviceLibraryRepository(options.repository.library_path, no_system_default=True)
# hardware
self._interface = options.interface
self._master: Optional[MasterDevice] = None
# memory board
self._centralMaster: Optional[MasterDevice] = None
# connected device
self._device: List[CompletedDevice] = []
# demo device
self._demo: Set[DeviceResponseInfo] = set()
# history found device
self._found: Set[DeviceResponseInfo] = set()
self._master_log_level = options.device_log_level
@synchronized
@logging_info
def setup(self, interface: Optional[str] = None):
"""initialize the master device.
:param interface: use specific hardware interface
"""
if interface is None:
interface = self._options.interface
# dhi = DeviceHardwareInterface(self, interface, self._options)
dhi_central = DeviceHardwareInterface(self, interface, self._options)
# self._master = m = dhi.new_master()
# m.set_log_level(self._master_log_level)
self._centralMaster = central_m = dhi_central.new_central_master()
# self._interface = dhi.interface
self._interface = dhi_central.interface
self.log_info('use interface', self._interface)
@property
def master_device(self) -> MasterDevice:
"""
:return: master device implementation
"""
return self._centralMaster
@property
def interface(self) -> str:
return self._interface
@property
def library_path(self) -> List[Path]:
return self._repository.library_path
def get_device_status(self, mac_address) -> Optional[DeviceLibrary]:
device = self.get_device(mac_address)
return device.status
@logging_info
def reload_library(self):
"""reload device library"""
# clear demo device
self._demo.clear()
# invalid cache
self._repository.invalid_cache()
def _on_error(f: Path, _: BaseException):
self.log_warn('load library fail', f)
print_exception()
self._repository.recursive = True
self._repository.on_error = _on_error
for devlib in self._repository.get_library_all():
self.log_verbose('load library', devlib.name, devlib.version, devlib.file_path)
def list_library(self) -> List[DeviceLibrary]:
return self._repository.get_library_all()
def get_library(self, name: str, version: Union[None, str, DeviceLibraryVersion] = None) -> Optional[DeviceLibrary]:
return self._repository.get_library(name, version)
def match_library(self, response: DeviceResponseInfo) -> Optional[DeviceLibrary]:
"""get the library match to the *response*"""
return self._repository.match_library(response)
def _ensure_initialized(self):
if self._centralMaster is None:
raise RuntimeError('master device not init')
@logging_info
def reset(self, device: Optional[List[int]] = None, software_reset=True):
self._ensure_initialized()
self.disconnect_all()
self._reset(device, software_reset)
def _reset(self, device: Optional[List[int]] = None, software_reset=True):
"""reset master device"""
self._device.clear()
self._demo.clear()
self._centralMaster.reset(device, software_reset)
@logging_info
def shutdown(self, release_resource=True):
# shutdown reset memeryboard
self.reset()
m: Optional[MasterDevice] = self._centralMaster
if m is not None:
self.disconnect_all()
m.shutdown()
self._master = None
self._centralMaster = None
self._interface = None
self._device.clear()
self._demo.clear()
@logging_verbose
def scan_callback(self,
callback: Callable[[DeviceResponseInfo], None],
timeout=5,
all_device=False) -> bool:
"""perform scan.
:param callback: notify scanned device.
:param timeout: timeout
:param all_device: all device perform scanning if not working
:return: does manager perform scan, False if another thread is perform scanning.
"""
self._ensure_initialized()
if not self.lock_acquire():
return False
error = None
try:
result = self._scan_callback(callback, timeout=timeout, all_device=all_device)
except RuntimeError as e:
error = e
else:
if not result:
raise RuntimeError(HARDWARE_SCAN_FAIL)
finally:
self.lock_release()
if error is not None:
# IncorrectError, due to a device is connected incorrectly (?),
# try to disconnect all devices
self.log_warn('suppressed error : ' + str(error))
raise RuntimeError(HARDWARE_SCAN_FAIL)
return True
def _scan_callback(self,
callback: Callable[[DeviceResponseInfo], None],
timeout=5,
all_device=False) -> bool:
self._ensure_initialized()
if not self.lock_acquire():
# require lock fail, because hardware is doing something now.
# Most case are another scanning is performing.
return True
def _callback(response: DeviceResponseInfo):
library = self.match_library(response)
# append library information
if library is not None:
response = DeviceResponseInfo(response.device_name,
response.serial_number,
response.mac_address,
library.name,
str(library.version))
self.log_verbose('found', address_str(response.mac_address), library.name, library.version)
else:
self.log_verbose('found', address_str(response.mac_address), 'unknown', response.serial_number)
# add history
self._found.add(response)
callback(response)
try:
result = self._centralMaster.scan_callback(_callback, timeout=timeout, all_device=all_device)
except RuntimeError as e:
self.log_warn('suppressed error : ' + str(e))
result = False
finally:
self.lock_release()
if not result:
self._reset()
return result
def found(self) -> List[DeviceResponseInfo]:
ret = []
for d in self._device:
r = d.response_info
ret.append(r)
def _contain(response: DeviceResponseInfo) -> bool:
for _response in ret:
if response.match(_response):
return True
return False
for r in self._centralMaster.found():
# force state
if not _contain(r):
r.device_status = DeviceResponseInfo.STATUS_SCANNED
# print('found ', r)
ret.append(r)
for r in self._demo:
# force state
if not _contain(r):
r.device_status = DeviceResponseInfo.STATUS_SCANNED
# print('history', r)
ret.append(r)
for r in self._found:
# force state
if not _contain(r):
r.device_status = DeviceResponseInfo.STATUS_NOT_FOUND
# print('history', r)
ret.append(r)
return ret
def found_device(self, response: DeviceInfo) -> Optional[DeviceResponseInfo]:
# find from connected device
for device in self._device:
if response.match(device):
return device.response_info
# find from found device
for device in self._centralMaster.found():
if response.match(device):
return device
# find from demo device
for device in self._demo:
if response.match(device):
return device
# find from history device
for device in self._found:
if response.match(device):
return device
# not found
return None
@logging_verbose
def clear_found_device(self):
"""clear history found device"""
self._found.clear()
self._demo.clear()
def list_device(self) -> List[CompletedDevice]:
"""list connected device
:return: list of CompletedDevice
"""
return list(self._device)
@logging_verbose
def get_device(self, device: Union[int, DeviceInfo]) -> Optional[CompletedDevice]:
"""get connected device
:param device: device ID or response info
:return: CompletedDevice, None if not found.
"""
try:
if isinstance(device, int):
for slave in self._device:
if slave.device_id == device:
return slave
elif isinstance(device, str):
for slave in self._device:
if ':'.join('{:02x}'.format(b) for b in slave.mac_address) == device.lower():
return slave
elif isinstance(device, DeviceInfo):
for slave in self._device:
if device.match(slave):
return slave
except BaseException as e:
self.log_error('device not found', str(device))
return None
def direct_connect(self, device_mac_str: str, device_serial_number:str) -> Optional[CompletedDevice]:
# neulive2.0 product number is 1, 1, 2, 0
# version is 2021/01
serial_number_list = literal_eval(device_serial_number)
serial_number = DeviceSerialNumber(serial_number_list[0], serial_number_list[1], serial_number_list[2], serial_number_list[3], serial_number_list[4], serial_number_list[5])
# def mac_str_to_addr(mac_str: str) -> ADDRESS:
# list_mac = list(mac_str)
# device_mac_int = []
# for i in range(6):
# mac_int = int(list_mac[i * 2], 16) << 4 | int(list_mac[i * 2 + 1], 16)
# device_mac_int.append(mac_int)
# return device_mac_int[0], device_mac_int[1], device_mac_int[2], \
# device_mac_int[3], device_mac_int[4], device_mac_int[5]
# device_addr: ADDRESS = mac_str_to_addr(device_mac_str)
device_addr = literal_eval(device_mac_str)
# response = DeviceResponseInfo("Neulive-v3.0", serial_number, device_addr, addr_type=0)
response = DeviceResponseInfo("Elite", serial_number, device_addr, addr_type=0)
print(response)
# get device library
library = None
if response.library_name is not None:
library = self.get_library(response.library_name, response.library_version)
if library is None:
library = self.match_library(response)
'''library not found'''
if library is None:
self.log_warn(DEVICE_CONNECT_FAIL, device_addr, LIBRARY_NOT_FOUND)
return None
# raise RuntimeError(LIBRARY_NOT_FOUND, address)
try:
device = self._centralMaster.connect(response, direct_connect=True)
device = CompletedDevice(self, library, device.device_id, device)
except Exception as e:
print(e)
return None
# Clean the CC2650 found devices when it has connected a device.
# self._master._cc2650[device.device_id]._found.clear()
# add connected device list
self._device.append(device)
self.log_info(DEVICE_CONNECTED, device_addr)
self.log_verbose('device reset')
try:
device.reset()
except DeviceInstructionError as e:
'''instruction reset fail. device disconnect'''
self.disconnect(device.device_id, True)
return None
except RunTimeError as e:
print('runtime', e)
else:
# Clean all found devices when connecting a device.
for i in range(8):
if self._centralMaster._cc2650[i] is not None:
for device_response in self._centralMaster._cc2650[i]._found:
if str(list(device_response.mac_address)) == str(device.mac_address):
self._centralMaster._cc2650[i]._found.clear()
self._centralMaster._cc2650[i]._found_with_id.clear()
self._centralMaster._cc2650[device.device_id]._found.clear()
self._centralMaster._cc2650[device.device_id]._found_with_id.clear()
self._found.clear()
return device
@logging_info
def connect(self, response: DeviceInfo, user_name: Optional[str] = None) -> Optional[CompletedDevice]:
# TODO connect handle user_name, acquired lock
self._ensure_initialized()
device = self.get_device(response)
if device is not None:
# device has connected
return device
address = address_str(response.mac_address)
self.log_info(DEVICE_CONNECTING, address)
# response = self.found_device(response)
# ''' device not found'''
# if response is None:
# self.log_warn(DEVICE_CONNECT_FAIL, address, DEVICE_NOT_FOUND)
# return None
# raise RuntimeError(DEVICE_NOT_FOUND, address)
# get device library
library = None
if response.library_name is not None:
library = self.get_library(response.library_name, response.library_version)
if library is None:
library = self.match_library(response)
'''library not found'''
if library is None:
self.log_warn(DEVICE_CONNECT_FAIL, address, LIBRARY_NOT_FOUND)
return None
# raise RuntimeError(LIBRARY_NOT_FOUND, address)
# get device instance
with self:
if response.mac_address == EMPTY_ADDRESS:
# demo device
device = DebugDevice(self, library,
device_id=10 + len(self._device),
device_name=response.device_name,
mac_address=EMPTY_ADDRESS,
serial_number=response.serial_number)
else:
try:
print('here!!!!!!!!!!!!!!!!!!!!!!!', response)
device = self._centralMaster.connect(response)
print('connect', device)
device = CompletedDevice(self, library, device.device_id, device)
except Exception as e:
print(e)
return None
# Clean the CC2650 found devices when it has connected a device.
# self._master._cc2650[device.device_id]._found.clear()
# add connected device list
self._device.append(device)
self.log_info(DEVICE_CONNECTED, address)
self.log_verbose('device reset')
try:
device.reset()
except DeviceInstructionError as e:
'''instruction reset fail. device disconnect'''
self.disconnect(device.device_id, True)
return None
else:
# Clean all found devices when connecting a device.
self._centralMaster._cc2650[device.device_id]._found.clear()
self._centralMaster._cc2650[device.device_id]._found_with_id.clear()
self._found.clear()
return device
@logging_info
def disconnect(self, device: int, force=False) -> bool:
self._ensure_initialized()
device = self.get_device(device)
if device is None:
return False
try:
return self._disconnect(device, force=force)
finally:
for i, d in enumerate(self._device):
if d.device_id == device.device_id:
del self._device[i]
break
@logging_info
def disconnect_all(self, force=False):
self._ensure_initialized()
self.log_verbose(HARDWARE_DISCONNECTING_ALL)
for device in self._device:
self._disconnect(device, force=force)
self.log_info(HARDWARE_DISCONNECTED_ALL)
self._device.clear()
def _disconnect(self, device: CompletedDevice, force=False) -> bool:
address = address_str(device.mac_address)
self.log_verbose(DEVICE_DISCONNECTING, device.device_name, address)
if not force:
try:
device.interrupt()
except SendInstructionTimeoutError:
self.log_warn(DEVICE_NO_RESPONSE, device.device_name, address)
except BaseException as e:
self.log_warn(DEVICE_RESPONSE_ERROR, str(e))
try:
return self._centralMaster.disconnect(device.device_id, force=force)
except SendInstructionTimeoutError:
self.log_warn(DEVICE_NO_RESPONSE, device.device_name, address)
return False
except BaseException as e:
self.log_warn(DEVICE_RESPONSE_ERROR, str(e))
return False
finally:
self.log_info(DEVICE_DISCONNECTED, device.device_name, address)
def call_device(self, device: int):
self._ensure_initialized()
device = self.get_device(device)
if device is not None:
device.call_instruction('calling')
def handle_internal_instruction(self, device: Device, func: str, *para: Any) -> bool:
if func == InternalInstruction.PREDEFINED_DATA_FORMAT:
self._device_data_format(device, *para)
elif func == InternalInstruction.PREDEFINED_DATA_FORMAT_CALI:
self._device_data_format_cali(device, *para)
elif func == InternalInstruction.PREDEFINED_SYNC:
self._device_sync(device, *para)
elif func == InternalInstruction.PREDEFINED_LOG:
self._device_log(device, *para)
elif func == InternalInstruction.PREDEFINED_SET:
self._device_set(device, *para)
elif func == InternalInstruction.PREDEFINED_DISABLE_CACHE:
self._device_disable_cache(device, *para)
elif func == InternalInstruction.PREDEFINED_IDLE:
self._idle(device, *para)
elif isinstance(device, DebugDevice):
if func == InternalInstruction.PREDEFINED_NOTIFY:
return True
return False
else:
return self._centralMaster.handle_internal_instruction(device, func, *para)
return True
def _device_data_format(self, device: Device, expr: AnyStr):
f = DataDecodeFormat.parse(expr)
self._handler.device_internal_command(device.device_id,
InternalInstruction.PREDEFINED_DATA_FORMAT,
f)
def _idle(self, device: Device, expr: AnyStr):
self._handler.device_internal_command(device.device_id,
InternalInstruction.PREDEFINED_IDLE,
None)
def _device_data_format_cali(self, device: Device, expr: str, cali: bytes = None):
if cali is None:
cali = device.calibration_info(expr)
if cali is None:
data = expr
else:
data = expr.encode() + b':' + cali
f = DataDecodeFormat.parse(data)
self._handler.device_internal_command(device.device_id,
InternalInstruction.PREDEFINED_DATA_FORMAT,
f)
def _device_disable_cache(self, device: Device, disable: bool = True):
self._handler.device_internal_command(device.device_id,
InternalInstruction.PREDEFINED_DISABLE_CACHE,
disable)
def _device_sync(self, device: Device, start: bool):
self._handler.device_internal_command(device.device_id,
InternalInstruction.PREDEFINED_SYNC,
start)
def _device_log(self, device: Device, message: str):
self.log_verbose('device', device.device_id, ':', message)
def _device_set(self, device: Device, parameter: str, value: int):
if not isinstance(device, CompletedDevice):
device = self.get_device(device.device_id)
if device is not None:
device.set_parameter(parameter, value)
def get_menu(self) -> Optional[Menu]:
self._ensure_initialized()
m1 = Menu('create-demo-device', 'create demo device')
for lib in sorted(self.list_library(), key=lambda k: (k.name, k.version)):
m1.append_item('%s-%s' % (lib.name, lib.version),
'%s %s' % (lib.name, lib.version),
command=[
build_url('create_demo_device', library=lib.name, version=lib.version)
])
ml = [m1]
m2 = self._centralMaster.get_menu()
if m2 is not None:
ml.append(m2)
return Menu('device', 'device', ml)
@logging_info
def create_demo_device(self, library: str, version: Optional[str] = None) -> DeviceResponseInfo:
"""create a dummy device with :class:`DebugDevice`
:param library: library name
:param version: library version
"""
lib = self.get_library(library, version)
if lib is None:
raise RuntimeError('library ' + library + ' not found')
device_name = 'Demo:' + library
device_index = None
while True:
for d in self._demo:
if d.device_name == device_name:
if device_index is None:
device_index = 1
else:
device_index += 1
device_name = 'Demo:%s (%d)' % (library, device_index)
# next for loop
break
else:
# break while loop
break
response = DeviceResponseInfo(device_name,
lib.match_rule.baseline_serial(),
EMPTY_ADDRESS,
library_name=lib.name,
library_version=str(lib.version),
status=DeviceResponseInfo.STATUS_SCANNED)
self._demo.add(response)
return response
@logging_verbose
def handle_command(self, device: Optional[int], command: str, options: Dict[str, str]) -> DeviceCommandAction:
if device is None:
message = command
else:
message = "%d:%s" % (device, command)
try:
if device is None:
return self._handle_command(command, options)
else:
return self._centralMaster.handle_command(device, command, options)
except CommandNotFoundError as e:
self.log_warn(DEVICE_COMMAND_NOT_FOUND, message)
raise e
except SendInstructionTimeoutError as e:
self.log_warn(DEVICE_NO_RESPONSE, message)
raise RuntimeError(DEVICE_NO_RESPONSE, message, str(e))
except BaseException as e:
self.log_warn(DEVICE_COMMAND_ERROR, message)
print_exception(self)
raise RuntimeError(DEVICE_COMMAND_ERROR, message, str(e)) from e
def _handle_command(self, command: str, options: Dict[str, str]) -> DeviceCommandAction:
if command == 'create_demo_device':
try:
self.create_demo_device(**options)
return DeviceCommandAction.SCAN
except TypeError as e:
raise RuntimeError('create_demo_device lost parameter : library or version') from e
elif command == 'scan':
self.scan()
return DeviceCommandAction.SCAN
elif DeviceCommonInstruction(command) is not None:
# command for all device
ret = DeviceCommandAction.SUCCESS
for device in self._device:
try:
device.call_instruction(command)
except SendInstructionTimeoutError:
self.log_warn(DEVICE_NO_RESPONSE, '%d:%s' % (device.device_id, command))
self.disconnect(device.device_id, force=True)
ret = DeviceCommandAction.DISCONNECTED
return ret
elif self._centralMaster is not None:
# master specific command
return self._centralMaster.handle_command(None, command, options)
else:
raise CommandNotFoundError(command)
def list_device_instruction(self, device_id: int) -> List[str]:
device = self.get_device(device_id)
if device is None:
raise RuntimeError(DEVICE_NOT_FOUND, device)
ret = set()
ret.update(DeviceCommonInstruction.ALL)
ret.update(device.library.instruction_table.export_keys())
return list(ret)
def device_instruction(self, device_id: int, instruction: str):
device = self.get_device(device_id)
if device is None:
raise RuntimeError(DEVICE_NOT_FOUND, device)
if DeviceCommonInstruction(instruction) is not None:
device.call_instruction(instruction)
elif instruction in device.library.instruction_table:
device.call_instruction(instruction)
else:
raise RuntimeError(INSTRUCTION_UNKNOWN, instruction)
def handle_device_parameter_value(self,
device_id: int,
parameter: str) -> JSON:
device = self.get_device(device_id)
if device is None:
raise RuntimeError(CHIP_NOT_FOUND)
if ',' in parameter:
ret = {}
for p in parameter.split(','):
try:
value = device.get_parameter_value(p)
except (ActionDomainError, KeyError):
pass
else:
ret[p] = JsonSerialize.to_json(value)
return ret
else:
ret = {}
try:
ret[parameter] = JsonSerialize.to_json(device.get_parameter(parameter))
except KeyError as e:
raise RuntimeError(PARAMETER_NOT_FOUND + parameter) from e
except ActionDomainError:
return None
else:
return ret
def handle_device_parameter_command(self,
device_id: int,
parameter: str,
value: Optional[str] = None) -> JSON:
device = self.get_device(device_id)
if device is None:
raise RuntimeError(CHIP_NOT_FOUND)
if ',' in parameter and value is None:
table = device.library.parameter_table
ret = {}
for p in parameter.split(','):
if p in table:
try:
ret[p] = JsonSerialize.to_json(device.get_parameter(p))
except ActionDomainError:
pass
return ret
elif '|' in parameter and '|' in value:
table = device.library.parameter_table
ret = {}
params = parameter.split('|')
values = value.split('|')
try:
for i, v in enumerate(params):
try:
if value is not None:
device.set_parameter(v, values[i])
ret[v] = JsonSerialize.to_json(device.get_parameter(v))
except:
pass
except ActionDomainError:
pass
return ret
else:
ret = {}
try:
device.library.parameter_table[parameter]
except KeyError as e:
raise RuntimeError(PARAMETER_NOT_FOUND + parameter) from e
if value is not None:
device.set_parameter(parameter, value)
try:
ret[parameter] = JsonSerialize.to_json(device.get_parameter(parameter))
except ActionDomainError:
pass
return ret
# try:
# return JsonSerialize.to_json(device.get_parameter(parameter))
# except ActionDomainError:
# return None
def hardware_test(self) -> Dict[str, Any]:
return {
'hardware': 'manager',
'path': list(map(lambda path: str(path), self.library_path)),
'library': [{
'name': lib.name,
'file': lib.file_path,
'version': str(lib.version),
} for lib in self.list_library()],
'interface': self.interface,
'master': self._master.hardware_test(),
'device': [device.as_json() for device in self.list_device()],
'found': [device.as_json() for device in self.found()]
}