"""
.. module:: commandmanager
:platform: Unix
:synopsis: Module to manage various Command Handlers.
.. moduleauthor:: Jonathan Grizou <Jonathan.Grizou@gla.ac.uk>
"""
from .commandhandler import SerialCommandHandler
from .commanddevices.register import create_and_setup_device
from .commanddevices.register import DEFAULT_REGISTER
from .commanddevices.register import DeviceRegisterError
from .lock import Lock
from ._logger import create_logger
import time
from serial import SerialException
#default initialisation timeout.
DEFAULT_INIT_TIMEOUT = 1
#Default amount of times to attempt initialisation
DEFAULT_INIT_N_REPEATS = 5
#Default timeout value.
DEFAULT_BONJOUR_TIMEOUT = 0.1
COMMAND_BONJOUR = 'BONJOUR'
COMMAND_IS_INIT = 'ISINIT'
COMMAND_INIT = 'INIT'
# removing all stuff related to reset because it does not compile on all boards
# COMMAND_RESET = 'RESET'
[docs]class CommandManager(object):
"""
Manages variying amounts of Command Handler objects.
Args:
serialcommand_configs (Tuple): Collection of serial command configurations.
devices_dict (Dict): Dictionary containing the list of devices.
init_timeout (int): Initialisation timeout, default se tto DEFAULT_INIT_TIMEOUT (1).
init_n_repeats (int): Number of times to attempt initialisation, default set to DEFAULT_INIT_N_REPEATS (5).
Raises:
OSError: Port was not found.
SerialException: Port was not found.
InitError: CommandManager on port was not initialised.
"""
def __init__(self, serialcommand_configs, devices_dict, init_timeout=DEFAULT_INIT_TIMEOUT, init_n_repeats=DEFAULT_INIT_N_REPEATS):
self.logger = create_logger(self.__class__.__name__)
self.init_n_repeats = init_n_repeats
self.init_lock = Lock(init_timeout)
self.serialcommandhandlers = []
for idx, config in enumerate(serialcommand_configs):
try:
cmdHdl = SerialCommandHandler.from_config(config)
cmdHdl.add_default_handler(self.unrecognized)
cmdHdl.start()
except (SerialException, OSError):
self.logger.warning('Port {} was not found'.format(config['port']))
continue
try:
elapsed = self.wait_serial_device_for_init(cmdHdl)
self.logger.info('Found CommandManager on port "{port}", init time was {init_time} seconds'.format(port=cmdHdl._serial.port, init_time=round(elapsed, 3)))
except InitError:
self.logger.warning('CommandManager on port "{port}" has not initialized'.format(port=cmdHdl._serial.port))
self.serialcommandhandlers.append(cmdHdl)
self.register_all_devices(devices_dict)
self.set_devices_as_attributes()
[docs] def set_devices_as_attributes(self):
"""
Sets the list of devices as attributes.
"""
for device_name, device in list(self.devices.items()):
if hasattr(self, device_name):
self.logger.warning("Device named {device_name} is already a reserved attribute, please change name or do not use this pump in attribute mode, rather use pumps[{device_name}]".format(device_name=device_name))
else:
setattr(self, device_name, device)
[docs] def handle_init(self, *arg):
"""
Handles the initialisation of the Manager, ensuring that the threading locks are released.
Args:
*arg: Variable argument.
"""
if arg[0] and bool(int(arg[0])):
self.init_lock.ensure_released()
[docs] def request_init(self, serialcommandhandler):
"""
Requests initialisation over serial communication.
Args:
serialcommandhandler (SerialCommandHandler): Serial Command Handler object for communication.
"""
serialcommandhandler.send(COMMAND_IS_INIT)
[docs] def request_and_wait_for_init(self, serialcommandhandler):
"""
Requests initialisation and waits until it obtains a threading lock.
Args:
serialcommandhandler (SerialCommandHandler): Serial Command Handler object for communication.
"""
start_time = time.time()
self.init_lock.acquire()
for i in range(self.init_n_repeats):
self.request_init(serialcommandhandler)
is_init, _ = self.init_lock.wait_until_released()
if is_init:
break
self.init_lock.ensure_released()
elapsed = time.time() - start_time
return is_init, elapsed
[docs] def wait_serial_device_for_init(self, cmdHdl):
"""
Waits for initialisation using serial communication.
Args:
cmdHdl (CommandHandler): CommandHandler object to add/remove commands.
Returns:
elapsed (flaot): Time waited for initialisation.
Raises:
InitError: CommandManager on the port was not initialised.
"""
self.logger.debug('Waiting for init on port "{port}"...'.format(port=cmdHdl._serial.port))
cmdHdl.add_command(COMMAND_INIT, self.handle_init)
is_init, elapsed = self.request_and_wait_for_init(cmdHdl)
cmdHdl.remove_command(COMMAND_INIT, self.handle_init)
if is_init:
return elapsed
raise InitError(cmdHdl._serial.port)
# removing all stuff related to reset because it does not compile on all boards
# def send_reset(self, serialcommandhandler):
# serialcommandhandler.send(COMMAND_RESET)
#
# def reset_and_wait_for_init(self, cmdHdl):
# self.send_reset(cmdHdl)
# self.wait_serial_device_for_init(cmdHdl)
#
# def reset_all_and_wait_for_init(self):
# for cmdHdl in self.serialcommandhandlers:
# self.send_reset(cmdHdl)
# for cmdHdl in self.serialcommandhandlers:
# self.wait_serial_device_for_init(cmdHdl)
[docs] def register_all_devices(self, devices_dict):
"""
Registers all available Arduino devices.
Args:
devices_dict (Dict): Dictionary containing all devices.
"""
self.devices = {}
for device_name, device_info in list(devices_dict.items()):
self.register_device(device_name, device_info)
[docs] def register_device(self, device_name, device_info):
"""
Registers an individual Arduino device.
Args:
device_name (str): Name of the device.
device_info (Dict): Dictionary containing the device information.
Raises:
DeviceRegisterError: Device is not in the device register.
BonjourError: Device has not been found.
"""
command_id = device_info['command_id']
if 'config' in device_info:
device_config = device_info['config']
else:
device_config = {}
try:
bonjour_service = CommandBonjour(self.serialcommandhandlers)
cmdHdl, bonjour_id, elapsed = bonjour_service.detect_device(command_id)
self.logger.info('Device "{name}" with id "{id}" and of type "{type}" found in {bonjour_time}s'.format(name=device_name, id=command_id, type=bonjour_id, bonjour_time=round(elapsed, 3)))
try:
device = create_and_setup_device(cmdHdl, command_id, bonjour_id, device_config)
self.logger.info('Device "{name}" with id "{id}" and of type "{type}" found in the register, creating it'.format(name=device_name, id=command_id, type=bonjour_id, bonjour_time=round(elapsed, 3)))
except DeviceRegisterError:
device = create_and_setup_device(cmdHdl, command_id, DEFAULT_REGISTER, device_config)
self.logger.warning('Device "{name}" with id "{id}" and of type "{type}" is not in the device register, creating a blank minimal device instead'.format(name=device_name, id=command_id, type=bonjour_id))
self.devices[device_name] = device
except BonjourError:
self.logger.warning('Device "{name}" with id "{id}" has not been found'.format(name=device_name, id=command_id))
@classmethod
[docs] def from_config(cls, config):
"""
Obtains the necessary information from a configuration setup.
Args:
cls (CommandManager): The instantiating class.
config (Dict): Dictionary contianing the configuration data.
"""
serialcommand_configs = config['ios']
devices = config['devices']
return cls(serialcommand_configs, devices)
@classmethod
[docs] def from_configfile(cls, configfile):
"""
Obtains the configuration data from a configuration file.
Args:
cls (CommandManager): The instantiating class.
configfile (File): The configuration file.
"""
import json
with open(configfile) as f:
return cls.from_config(json.load(f))
[docs] def unrecognized(self, cmd):
"""
Received command is unrecognised.
Args:
cmd (str): The received command.
"""
self.logger.warning('Received unknown command "{}"'.format(cmd))
[docs]class InitError(Exception):
"""
Exception for when the manager fails to initialise.
"""
def __init__(self, port):
self.port = port
def __str__(self):
return 'Manager on port {self.port} did not inititalize'.format(self=self)
[docs]class CommandBonjour(object):
"""
Represents a Command Manager for Bonjour devices.
Args:
serialcommandhandlers: Collection of SerialCommandHandler objects.
timeout (float): Time to wait before timeout, default set to DEFAULT_BONJOUR_TIMEOUT (0.1)
"""
def __init__(self, serialcommandhandlers, timeout=DEFAULT_BONJOUR_TIMEOUT):
self.logger = create_logger(self.__class__.__name__)
self.serialcommandhandlers = serialcommandhandlers
self.lock = Lock(timeout)
self.init_bonjour_info()
[docs] def init_bonjour_info(self):
"""
Initialises the Bonjour information.
"""
self.device_bonjour_id = ''
self.device_bonjour_id_valid = False
[docs] def handle_bonjour(self, *arg):
"""
Handles the Bonjour initialisation.
Args:
*arg: Variable argument.
"""
if arg[0]:
self.device_bonjour_id = arg[0]
self.device_bonjour_id_valid = True
self.lock.ensure_released()
[docs] def send_bonjour(self, serialcommandhandler, command_id):
"""
Sends a message to the device.
.. todo:: Fix this up
Args:
serialcommandhandler (SerialCommandHandler): The Serial Command Handler object.
command_id (str): The ID of the command.
"""
serialcommandhandler.send(command_id, COMMAND_BONJOUR)
[docs] def get_bonjour_id(self, serialcommandhandler, command_id):
"""
Obtains the device's bonjour ID.
Args:
serialcommandhandler (SerialCommandHandler): The Serial Command Handler object.
command_id (str): The ID of the command.
Returns:
self.device_bonjour_id (str): The Bonjour ID.
is_valid (bool): Validity of the ID.
elapsed (float): Time elapsed since request.
"""
self.lock.acquire()
self.init_bonjour_info()
self.send_bonjour(serialcommandhandler, command_id)
is_valid, elapsed = self.lock.wait_until_released()
self.lock.ensure_released()
return self.device_bonjour_id, is_valid, elapsed
[docs] def detect_device(self, command_id):
"""
Attempts to detect the Bonjour device.
Args:
command_id (str): The ID of the command.
Returns:
cmdHdl (SerialCommandHandler): The Serial Command Handler.
bonjour_id (str): The Bonjour ID.
elapsed (float): Time elapsed since request.
"""
for cmdHdl in self.serialcommandhandlers:
self.logger.debug('Scanning for "{id}" on port "{port}"...'.format(id=command_id, port=cmdHdl._serial.port))
cmdHdl.add_relay(command_id, cmdHdl.handle)
cmdHdl.add_command(COMMAND_BONJOUR, self.handle_bonjour)
bonjour_id, is_valid, elapsed = self.get_bonjour_id(cmdHdl, command_id)
cmdHdl.remove_command(COMMAND_BONJOUR, self.handle_bonjour)
cmdHdl.remove_relay(command_id, cmdHdl.handle)
if is_valid:
return cmdHdl, bonjour_id, elapsed
raise BonjourError(command_id)
[docs]class BonjourError(Exception):
"""
Exception for when Bonjour device is not available/not found.
"""
def __init__(self, command_id):
self.command_id = command_id
def __str__(self):
return '{self.command_id} seems to not be existing/available'.format(self=self)