async def basic_make_script(self, index): """Make script and controllers and return a list of all made.""" self.script = CalSysTakeData(index=index) # mock controllers that use callback functions defined below # to handle the expected commands self.electrometer = salobj.Controller(name="Electrometer", index=1) self.monochromator = salobj.Controller(name="ATMonochromator") self.fiberspec = salobj.Controller(name="FiberSpectrograph") # data that is set by the command callback functions # scan durations from Electrometer startScanDt command self.scan_durations = [] # data from FiberSpectrograph captureSpectImage command self.image_data = [] # wavelengths from ATMonochromator changeWavelength command self.wavelengths = [] # slit width data from ATMonochromator changeSlitWidth command self.slit_data = [] # grating types from ATMonochromator gratingType command self.grating_types = [] # assign the command callback functions self.electrometer.cmd_startScanDt.callback = self.startScanDt self.fiberspec.cmd_expose.callback = self.captureSpectImage self.monochromator.cmd_changeWavelength.callback = self.changeWavelength self.monochromator.cmd_changeSlitWidth.callback = self.changeSlitWidth self.monochromator.cmd_selectGrating.callback = self.selectGrating return (self.script, self.electrometer, self.monochromator, self.fiberspec)
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 connect_to_love_controller(): global csc try: csc = salobj.Controller("LOVE", index=None, do_callbacks=False) except Exception as e: logging.warning(e) csc = None
async def basic_make_script(self, index): logger.debug("Starting basic_make_script") self.script = LatissAcquireAndTakeSequence(index=index) # Mock the telescope slews and offsets self.script.atcs.slew_object = unittest.mock.AsyncMock() self.script.atcs.slew_icrs = unittest.mock.AsyncMock() self.script.atcs.offset_xy = unittest.mock.AsyncMock() self.script.atcs.add_point_data = unittest.mock.AsyncMock() self.script.latiss.ready_to_take_data = unittest.mock.AsyncMock( return_value=True) # Mock the latiss instrument setups self.script.latiss.setup_atspec = unittest.mock.AsyncMock( wraps=self.cmd_setup_atspec_callback) # Mock method that returns the BestEffortIsr class if it is # not available for import if not DATA_AVAILABLE: self.script.get_best_effort_isr = unittest.mock.Mock() # Load controllers and required callbacks to simulate # telescope/instrument behaviour self.atcamera = salobj.Controller(name="ATCamera") self.atcamera.cmd_takeImages.callback = unittest.mock.AsyncMock( wraps=self.cmd_take_images_callback) self.atheaderservice = salobj.Controller(name="ATHeaderService") self.atoods = salobj.Controller(name="ATOODS") # Need ataos as the script waits for corrections to be applied on # grating/filter changes self.ataos = salobj.Controller(name="ATAOS") self.atspectrograph = salobj.Controller(name="ATSpectrograph") self.end_image_tasks = [] # things to track self.nimages = 0 self.date = None # Used to fake dataId output from takeImages self.seq_num_start = None # Used to fake proper dataId from takeImages logger.debug("Finished initializing from basic_make_script") # Return a single element tuple return (self.script, )
async def test_write_only_true(self) -> None: index = next(index_gen) # Build a controller and check that callbacks are asigned. async with salobj.Controller(name="Test", index=index, write_only=True) as controller: for name in controller.salinfo.command_names: assert not hasattr(controller, f"cmd_{name}") for name in controller.salinfo.event_names: assert hasattr(controller, f"evt_{name}") for name in controller.salinfo.telemetry_names: assert hasattr(controller, f"tel_{name}") with pytest.raises(ValueError): salobj.Controller(name="Test", index=index, do_callbacks=True, write_only=True)
async def basic_make_script(self, index): self.script = TakeImageLatiss(index=index) self.atcam = salobj.Controller(name="ATCamera") self.atspec = salobj.Controller(name="ATSpectrograph") self.atheaderservice = salobj.Controller(name="ATHeaderService") self.nimages = 0 self.selected_filter = [] self.selected_disperser = [] self.selected_linear_stage = [] self.atcam.cmd_takeImages.callback = self.cmd_take_images_callback self.atspec.cmd_changeFilter.callback = self.cmd_changeFilter_callback self.atspec.cmd_changeDisperser.callback = self.cmd_changeDisperser_callback self.atspec.cmd_moveLinearStage.callback = self.cmd_moveLinearStage_callback self.end_image_tasks = [] return self.atspec, self.atcam, self.atheaderservice, self.script
async def test_do_callbacks_false(self) -> None: index = next(index_gen) async with salobj.Controller("Test", index, do_callbacks=False) as controller: command_names = controller.salinfo.command_names for name in command_names: with self.subTest(name=name): cmd = getattr(controller, "cmd_" + name) assert not cmd.has_callback assert controller.salinfo.identity == f"Test:{index}"
async def mtmount_emulator(elevation): async with salobj.Controller("MTMount") as mtmount: # xml 7/8 compatibility if hasattr(mtmount.tel_elevation.DataType(), "actualPosition"): mtmount.tel_elevation.set(actualPosition=elevation) else: mtmount.tel_elevation.set(angleActual=elevation) while mtmount.isopen: mtmount.tel_elevation.put() await asyncio.sleep(1.0)
def emit_largeFileObjectAvailable(self, args): cam = salobj.Controller(name="EFD", index=0) kwInt = { 'byteSize': args.byteSize, 'checkSum': args.checkSum, 'generator': args.generator, 'mimeType': args.mimeType, 'url': args.url, 'version': args.version, 'id': args.identifier } cam.evt_largeFileObjectAvailable.set_put(**kwInt)
async def basic_make_script(self, index): self.script = ATGetStdFlatDataset(index=index) # Adds controller to Test self.at_cam = salobj.Controller(name="ATCamera") self.at_spec = salobj.Controller(name="ATSpectrograph") self.at_headerservice = salobj.Controller(name="ATHeaderService") self.n_bias = 0 self.n_dark = 0 self.n_flat = 0 self.filter = None self.grating = None self.linear_stage = None self.shutter_time = 1.0 self.end_readout_tasks = [] return (self.script, self.at_cam, self.at_spec, self.at_headerservice)
async def mock_auxtel(self) -> typing.AsyncGenerator[None, None]: try: async with salobj.Controller( "ATMCS") as self.atmcs, salobj.Controller( "ATPtg") as self.atptg, salobj.Controller( "ATPneumatics" ) as self.pnematics, salobj.Controller( "ATHexapod") as self.hexapod, salobj.Controller( "ATCamera") as self.camera, salobj.Controller( "ATSpectrograph") as self.atspectrograph: self.set_pneumatics_callbacks() self.set_atptg_callbacks() self.set_athexapod_callbacks() await self.publish_pneumatics_initial_data() await self.publish_atspectrograph_initial_data() self.running = True mount_telemetry_task = asyncio.create_task( self.publish_mount_encoders()) yield self.running = False try: await asyncio.wait_for(mount_telemetry_task, timeout=STD_TIMEOUT) except asyncio.TimeoutError: mount_telemetry_task.cancel() except Exception as exception: raise exception
def __init__(self): self.ptg = salobj.Controller("MTPtg") self.location = ObservatoryLocation() self.location.for_lsst() self.model = ObservatoryModel(self.location) self.model.configure_from_module() self.telemetry_sleep_time = 0.02 self.run_current_target_status_loop = True self.current_target_status_task = None self._started = False self.start_task = asyncio.create_task(self.start())
def emit_startIntegration(self, args): cam = salobj.Controller(name="ATCamera", index=0) kwInt = { 'imagesInSequence': args.imagesInSequence, 'imageName': str(args.imageName), 'imageIndex': args.imageIndex, 'imageSource': str(args.imageSource), 'imageController': str(args.imageController), 'imageDate': str(args.imageDate), 'imageNumber': args.imageNumber, 'timeStampAcquisitionStart': args.timeStampAcquisitionStart, 'exposureTime': args.exposureTime, 'imageType': args.imageType, 'groupId': args.groupId, 'priority': 1 } cam.evt_startIntegration.set_put(**kwInt)
async def test_call(self): name = "ScriptQueue" index = 5 timeout = 0.2 watcher_config_dict = yaml.safe_load(f""" disabled_sal_components: [] auto_acknowledge_delay: 3600 auto_unacknowledge_delay: 3600 rules: - classname: Heartbeat configs: - name: {name}:{index} timeout: {timeout} escalation: [] """) watcher_config = types.SimpleNamespace(**watcher_config_dict) async with salobj.Controller(name=name, index=index) as controller: async with watcher.Model(domain=controller.domain, config=watcher_config) as model: model.enable() self.assertEqual(len(model.rules), 1) rule_name = f"Heartbeat.{name}:{index}" rule = model.rules[rule_name] alarm = rule.alarm controller.evt_heartbeat.put() await asyncio.sleep(0.001) self.assertTrue(alarm.nominal) await asyncio.sleep(timeout / 2) controller.evt_heartbeat.put() await asyncio.sleep(0.001) self.assertTrue(alarm.nominal) await asyncio.sleep(timeout * 2) self.assertFalse(alarm.nominal) self.assertEqual(alarm.severity, AlarmSeverity.SERIOUS) controller.evt_heartbeat.put() await asyncio.sleep(0.001) self.assertFalse(alarm.nominal) self.assertEqual(alarm.severity, AlarmSeverity.NONE) self.assertEqual(alarm.max_severity, AlarmSeverity.SERIOUS)
async def basic_make_script(self, index): self.script = LatissCWFSAlign(index=index, remotes=True) self.visit_id_angles = {} self.end_image_tasks = [] self.img_cnt_override_list = None # Load controllers and required callbacks to simulate # telescope/instrument behaviour self.atcamera = salobj.Controller(name="ATCamera") self.atheaderservice = salobj.Controller(name="ATHeaderService") self.atoods = salobj.Controller(name="ATOODS") self.ataos = salobj.Controller(name="ATAOS") self.athexapod = salobj.Controller(name="ATHexapod") self.atptg = salobj.Controller(name="ATPtg") self.atmcs = salobj.Controller(name="ATMCS") # Create mocks self.atcamera.cmd_takeImages.callback = unittest.mock.AsyncMock( wraps=self.cmd_take_images_callback) self.script.latiss.ready_to_take_data = unittest.mock.AsyncMock( return_value=True) # mock latiss instrument setup self.script.latiss.setup_atspec = unittest.mock.AsyncMock() self.ataos.cmd_offset.callback = unittest.mock.AsyncMock( wraps=self.ataos_cmd_offset_callback) self.script.atcs.offset_xy = unittest.mock.AsyncMock() self.script.atcs.add_point_data = unittest.mock.AsyncMock() # callback for boresight angle self.script.atcs.get_bore_sight_angle = unittest.mock.AsyncMock( wraps=self.atcs_get_bore_sight_angle) # Mock method that returns the BestEffortIsr class if it is # not available for import if not DATA_AVAILABLE: self.script.get_best_effort_isr = unittest.mock.AsyncMock() # things to track self.nimages = 0 self.date = None # Used to fake dataId output from takeImages self.seq_num_start = None # Used to fake proper dataId from takeImages # Return a single element tuple return (self.script, )
async def make_csc( self, initial_state, config_dir=None, override="", simulation_mode=0, log_level=None, ): async with super().make_csc( initial_state=initial_state, config_dir=config_dir, override=override, simulation_mode=simulation_mode, log_level=log_level, ), ATDomeTrajectory.MockDome( initial_state=salobj.State.ENABLED ) as self.dome_csc, salobj.Remote( domain=self.dome_csc.domain, name="ATDome") as self.dome_remote, salobj.Controller( "ATMCS") as self.atmcs_controller: yield
async def test_write_speed(self) -> None: async with salobj.Controller( name="Test", index=self.index, do_callbacks=False ) as controller: num_samples = 1000 t0 = time.monotonic() for i in range(num_samples): await controller.tel_arrays.write() dt = time.monotonic() - t0 arrays_write_speed = num_samples / dt print( f"Wrote {arrays_write_speed:0.0f} arrays samples/second ({num_samples} samples)" ) self.insert_measurement( verify.Measurement( "salobj.WriteTest_arrays", arrays_write_speed * u.ct / u.second ) ) t0 = time.monotonic() for _ in range(num_samples): await controller.evt_logLevel.write() await asyncio.sleep(0) dt = time.monotonic() - t0 log_level_write_speed = num_samples / dt print( f"Wrote {log_level_write_speed:0.0f} logLevel samples/second ({num_samples} samples)" ) self.insert_measurement( verify.Measurement( "salobj.WriteTest_logLevel", log_level_write_speed * u.ct / u.second, ) )
async def make_csc( self, initial_state, config_dir=None, initial_elevation=0, override="", simulation_mode=0, log_level=None, ): async with super().make_csc( initial_state=initial_state, config_dir=config_dir, override=override, simulation_mode=simulation_mode, log_level=log_level, ), mtdometrajectory.MockDome( initial_state=salobj.State.ENABLED, initial_elevation=initial_elevation ) as self.dome_csc, salobj.Remote( domain=self.dome_csc.domain, name="MTDome") as self.dome_remote, salobj.Controller( "MTMount") as self.mtmount_controller: yield
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)
async def test_call(self): name = "ScriptQueue" index = 5 watcher_config_dict = yaml.safe_load( f""" disabled_sal_components: [] auto_acknowledge_delay: 3600 auto_unacknowledge_delay: 3600 rules: - classname: Enabled configs: - name: {name}:{index} escalation: [] """ ) watcher_config = types.SimpleNamespace(**watcher_config_dict) async with salobj.Controller(name=name, index=index) as controller: async with watcher.Model( domain=controller.domain, config=watcher_config ) as model: model.enable() self.assertEqual(len(model.rules), 1) rule_name = f"Enabled.{name}:{index}" rule = model.rules[rule_name] read_severities = [] def alarm_callback(alarm): nonlocal read_severities read_severities.append(alarm.severity) rule.alarm.callback = alarm_callback expected_severities = [] for state in ( salobj.State.STANDBY, salobj.State.DISABLED, salobj.State.ENABLED, salobj.State.FAULT, salobj.State.STANDBY, salobj.State.DISABLED, salobj.State.FAULT, salobj.State.STANDBY, salobj.State.DISABLED, salobj.State.ENABLED, ): if state == salobj.State.ENABLED: expected_severities.append(AlarmSeverity.NONE) elif state == salobj.State.FAULT: expected_severities.append(AlarmSeverity.SERIOUS) else: expected_severities.append(AlarmSeverity.WARNING) controller.evt_summaryState.set_put( summaryState=state, force_output=True ) # give the remote a chance to read the data await asyncio.sleep(0.001) self.assertEqual(read_severities, expected_severities)
async def basic_make_script(self, index): self.script = standardscripts.RunCommand(index=index) self.controller = salobj.Controller("Test", index=1) self.controller.cmd_setScalars.callback = self.set_scalars_callback return [self.script, self.controller]
async def make_csc( self, initial_state=salobj.State.OFFLINE, config_dir=None, simulation_mode=1, log_level=None, timeout=STD_TIMEOUT, run_mock_ccw=True, # Set False to test failure to enable **kwargs, ): """Override make_csc This exists primarily because we need to start a mock camera cable wrap controller before we can enable the CSC. It also offers the opportunity to make better defaults. Parameters ---------- name : `str` Name of SAL component. initial_state : `lsst.ts.salobj.State` or `int`, optional The initial state of the CSC. Defaults to STANDBY. config_dir : `str`, optional Directory of configuration files, or `None` (the default) for the standard configuration directory (obtained from `ConfigureCsc._get_default_config_dir`). simulation_mode : `int`, optional Simulation mode. Defaults to 0 because not all CSCs support simulation. However, tests of CSCs that support simulation will almost certainly want to set this nonzero. log_level : `int` or `None`, optional Logging level, such as `logging.INFO`. If `None` then do not set the log level, leaving the default behavior of `SalInfo`: increase the log level to INFO. timeout : `float`, optional Time limit for the CSC to start (seconds). run_mock_ccw : `bool`, optional If True then start a mock camera cable wrap controller. **kwargs : `dict`, optional Extra keyword arguments for `basic_make_csc`. For a configurable CSC this may include ``settings_to_apply``, especially if ``initial_state`` is DISABLED or ENABLED. """ # We cannot transition the CSC to ENABLED # until the CCW following loop is running. # So if initial_state is ENABLED # start the CSC in DISABLED, start the CCW following loop, # then transition to ENABLED and swallow the DISABLED state # and associated controller state (make_csc reads all but the final # CSC summary state and controller state). if initial_state == salobj.State.ENABLED: modified_initial_state = salobj.State.DISABLED else: modified_initial_state = initial_state async with super().make_csc( initial_state=modified_initial_state, config_dir=config_dir, simulation_mode=simulation_mode, log_level=log_level, timeout=timeout, **kwargs, ), salobj.Controller(name="MTMount") as self.mtmount_controller: if run_mock_ccw: self.mock_ccw_task = asyncio.create_task(self.mock_ccw_loop()) else: print("do not run mock_ccw_loop") self.mock_ccw_task = salobj.make_done_future() if initial_state != modified_initial_state: await self.remote.cmd_enable.start(timeout=STD_TIMEOUT) await self.assert_next_summary_state(salobj.State.DISABLED) await self.assert_next_sample( topic=self.remote.evt_controllerState, controllerState=ControllerState.DISABLED, ) try: yield finally: self.enable_mock_ccw_telemetry = False await asyncio.wait_for(self.mock_ccw_task, timeout=STD_TIMEOUT)