async def basic_make_script(self, index): self.script = Stop(index=index) # A dict of name: number of calls # where name is {controller_name_index}.{command_name} self.num_calls = dict() # A dict of name_index: numer of calls # where name_index means the SAL component name and index # in the form name[:index] and [:index] is only wanted for # indexed SAL components. self.controllers = dict() for name_index in ("ATDome", "ATDomeTrajectory", "ATPtg", "ATMCS"): name, index = salobj.name_to_name_index(name_index) controller = salobj.Controller(name=name, index=index) self.controllers[name_index] = controller await controller.evt_summaryState.set_write( summaryState=salobj.State.ENABLED) for command_name in controller.salinfo.command_names: name = f"{name_index}.{command_name}" self.num_calls[name] = 0 command = getattr(controller, f"cmd_{command_name}") command.callback = functools.partial(self.callback, name) return (self.script, ) + tuple(self.controllers.values())
def test_name_to_name_index(self) -> None: for name, expected_result in ( ("Script", ("Script", 0)), ("Script:0", ("Script", 0)), ("Script:15", ("Script", 15)), ("MTM1M3", ("MTM1M3", 0)), ("MTM1M3:47", ("MTM1M3", 47)), ): with self.subTest(name=name): result = salobj.name_to_name_index(name) assert result == expected_result for bad_name in ( (" Script:15"), # leading space ("Script:15 "), # trailing space ("Script:"), # colon with no index ("Script:zero"), # index is not an integer ): with self.subTest(bad_name=bad_name): with pytest.raises(ValueError): salobj.name_to_name_index(bad_name)
def __init__(self, config): remote_name, remote_index = salobj.name_to_name_index(config.name) remote_info = base.RemoteInfo( name=remote_name, index=remote_index, callback_names=["evt_summaryState"], poll_names=[], ) super().__init__( config=config, name=f"Enabled.{remote_info.name}:{remote_info.index}", remote_info_list=[remote_info], )
def __init__(self, config): remote_name, remote_index = salobj.name_to_name_index(config.name) remote_info = base.RemoteInfo( name=remote_name, index=remote_index, callback_names=["evt_heartbeat"], poll_names=[], ) super().__init__( config=config, name=f"Heartbeat.{remote_info.name}:{remote_info.index}", remote_info_list=[remote_info], ) self.heartbeat_timer_task = salobj.make_done_future()
async def configure(self, config): """Configure the script. Specify the CSCs to command, the command to run, the parameters for the command. Optionally, specify an event to wait and if the event should be flushed before sending the command. Parameters ---------- config : `types.SimpleNamespace` Raises ------ RuntimeError: If `config.command` is not a valid command from the CSC. """ self.log.info("Configure started") self.config = config self.name, self.index = salobj.name_to_name_index(config.component) self.event = config.event if hasattr(config, "event") else None self.remote = salobj.Remote( domain=self.domain, name=self.name, index=self.index, include=[self.event] if self.event is not None else [], ) if config.cmd in self.remote.salinfo.command_names: self.cmd = config.cmd else: raise RuntimeError( f"Command {config.cmd} not a valid command for {self.name}.") getattr(self.remote, f"cmd_{self.cmd}").set(**dict([(k, config.parameters[k]) for k in config.parameters if k != "timeout"])) self.flush = config.flush if self.event is not None else False if self.event is not None and self.event not in self.remote.salinfo.event_names: raise RuntimeError( f"Event {self.event} not a valid event for {self.name}.")
def __init__(self, config): remote_name, remote_index = salobj.name_to_name_index(config.name) remote_info = base.RemoteInfo( name=remote_name, index=remote_index, callback_names=["evt_heartbeat"], poll_names=[], ) super().__init__( config=config, name=f"Clock.{remote_info.name}:{remote_info.index}", remote_info_list=[remote_info], ) self.threshold = config.threshold # An array of up to `min_errors` recent measurements of clock error # (seconds), oldest first. self.clock_errors = np.zeros(self.min_errors, dtype=float) # The number of values in `clock_errors`; maxes out at `min_errors` self.n_clock_errors = 0
async def do_requestAuthorization(self, data): """Implement the requestAuthorization command. Parameters ---------- data : ``cmd_requestAuthorization.DataType`` Command data. """ self.assert_enabled() await self.cmd_requestAuthorization.ack_in_progress( data, timeout=self.config.timeout_request_authorization) cscs_to_command = await self.validate_request(data=data) cscs_failed_to_set_auth_list = set() for csc_name_index in cscs_to_command: csc_name, csc_index = salobj.name_to_name_index(csc_name_index) try: async with salobj.Remote( domain=self.salinfo.domain, name=csc_name, index=csc_index, include=[], ) as remote: await remote.cmd_setAuthList.set_start( authorizedUsers=data.authorizedUsers, nonAuthorizedCSCs=data.nonAuthorizedCSCs, timeout=TIMEOUT_SET_AUTH_LIST, ) self.log.info(f"Set authList for {csc_name_index}") except salobj.AckError as e: cscs_failed_to_set_auth_list.update({csc_name_index}) self.log.warning( f"Failed to set authList for {csc_name_index}: {e.args[0]}" ) if len(cscs_failed_to_set_auth_list) > 0: raise RuntimeError( f"Failed to set authList for the following CSCs: {cscs_failed_to_set_auth_list}. " "The following CSCs were successfully updated: " f"{cscs_to_command-cscs_failed_to_set_auth_list}")
async def make_model(self, names, enable, escalation=()): """Make a Model as self.model, with one or more Enabled rules. Parameters ---------- names : `list` [`str`] Name and index of one or more CSCs. Each entry is of the form "name" or name:index". The associated alarm names have a prefix of "Enabled.". enable : `bool` Enable the model? escalation : `list` of `dict`, optional Escalation information. See `CONFIG_SCHEMA` for the format of entries. """ if not names: raise ValueError("Must specify one or more CSCs") self.name_index_list = [ salobj.name_to_name_index(name) for name in names ] configs = [dict(name=name_index) for name_index in names] watcher_config_dict = dict( disabled_sal_components=[], auto_acknowledge_delay=3600, auto_unacknowledge_delay=3600, rules=[dict(classname="Enabled", configs=configs)], escalation=escalation, ) watcher_config = types.SimpleNamespace(**watcher_config_dict) self.read_severities = dict() self.read_max_severities = dict() self.controllers = [] for name_index in names: name, index = salobj.name_to_name_index(name_index) self.controllers.append(salobj.Controller(name=name, index=index)) self.model = watcher.Model( domain=self.controllers[0].domain, config=watcher_config, alarm_callback=self.alarm_callback, ) for name in self.model.rules: self.read_severities[name] = [] self.read_max_severities[name] = [] controller_start_tasks = [ controller.start_task for controller in self.controllers ] await asyncio.gather(self.model.start_task, *controller_start_tasks) if enable: self.model.enable() await self.model.enable_task for rule in self.model.rules.values(): self.assertTrue(rule.alarm.nominal) self.assertFalse(rule.alarm.acknowledged) self.assertFalse(rule.alarm.muted) self.assertNotMuted(rule.alarm) try: yield finally: await self.model.close() controller_close_tasks = [ asyncio.create_task(controller.close()) for controller in self.controllers ] await asyncio.gather(*controller_close_tasks)
def __init__( self, components: typing.List[str], domain: typing.Optional[salobj.Domain] = None, log: typing.Optional[logging.Logger] = None, intended_usage: typing.Optional[int] = None, concurrent_operation: bool = True, ) -> None: if log is None: self.log = logging.getLogger(type(self).__name__) else: self.log = log.getChild(type(self).__name__) self.fast_timeout = 5.0 self.long_timeout = 30.0 self.long_long_timeout = 120.0 self._concurrent_operation = concurrent_operation self._components = dict([(component, component.lower().replace(":", "_")) for component in components]) self.domain, self._close_domain = ((domain, False) if domain is not None else (salobj.Domain(), True)) self._usages: typing.Union[None, typing.Dict[int, UsagesResources]] = None self.rem = types.SimpleNamespace() for component in self._components: name, index = salobj.name_to_name_index(component) rname = self._components[component] resources = self.get_required_resources(rname, intended_usage) if resources.add_this: setattr( self.rem, rname, salobj.Remote( domain=self.domain, name=name, index=index, readonly=resources.readonly, include=resources.include, ), ) else: setattr(self.rem, rname, None) self.scheduled_coro: typing.List[asyncio.Task] = [] # Dict of component attribute name: remote, if present, else None attr_remotes = { attr: getattr(self.rem, attr) for attr in self.components_attr } # Mark components that were excluded from the resources to not be # checked. self.check = types.SimpleNamespace( **{c: remote is not None for c, remote in attr_remotes.items()}) start_task_list = [ remote.start_task for remote in attr_remotes.values() if remote is not None ] self.start_task = (asyncio.gather( *start_task_list) if len(start_task_list) > 0 else None)
def __init__(self, domain, config, alarm_callback=None): self.domain = domain self.alarm_callback = alarm_callback self._enabled = False self.enable_task = salobj.make_done_future() # Dict of (sal_component_name, sal_index): lsst.ts.salobj.Remote self.remotes = dict() # Dict of rule_name: Rule self.rules = dict() # Convert the name of each disabled sal component from a string # in the form ``name`` or ``name:index`` to a tuple ``(name, index)``. config.disabled_sal_components = [ salobj.name_to_name_index(name) for name in config.disabled_sal_components ] self.config = config # Make the rules. for ruledata in self.config.rules: ruleclassname = ruledata["classname"] ruleclass = get_rule_class(ruleclassname) try: ruleschema = ruleclass.get_schema() if ruleschema is None: validator = None else: validator = salobj.DefaultingValidator(ruleschema) except Exception as e: raise ValueError( f"Schema for rule class {ruleclassname} not valid") from e for i, ruleconfig_dict in enumerate(ruledata["configs"]): try: if validator is None: if ruleconfig_dict: raise ValueError("Rule config dict must be empty") full_ruleconfig_dict = dict() else: full_ruleconfig_dict = validator.validate( ruleconfig_dict) ruleconfig = types.SimpleNamespace(**full_ruleconfig_dict) except Exception as e: raise ValueError( f"Config {i+1} for rule class {ruleclassname} not valid: " f"config={ruleconfig_dict}") from e rule = ruleclass(config=ruleconfig) if rule.is_usable(disabled_sal_components=config. disabled_sal_components): self.add_rule(rule) # Accumulate a list of topics that have callback functions. self._topics_with_callbacks = list() for remote in self.remotes.values(): for name in dir(remote): if name[0:4] in ("evt_", "tel_"): topic = getattr(remote, name) if topic.callback is not None: self._topics_with_callbacks.append(topic) # Set escalation information in the alarms. remaining_names = set(self.rules) for escalation_item in config.escalation: for name_glob in escalation_item["alarms"]: name_regex = fnmatch.translate(name_glob) compiled_name_regex = re.compile(name_regex, re.IGNORECASE) matched_names = [ name for name in remaining_names if compiled_name_regex.match(name) ] remaining_names = remaining_names.difference(matched_names) for name in matched_names: alarm = self.rules[name].alarm alarm.escalate_to = escalation_item["to"] alarm.escalate_delay = escalation_item["delay"] self.start_task = asyncio.ensure_future(self.start())