def __init__(self, index, descr=""): super().__init__(index, descr="A laser coordination script") self.linear_stage_1 = salobj.Remote(self.domain, name="LinearStage", index=1) self.linear_stage_2 = salobj.Remote(self.domain, name="LinearStage", index=2) self.electrometer = salobj.Remote(self.domain, name="Electrometer", index=1) self.tunable_laser = salobj.Remote(self.domain, name="TunableLaser") self.wanted_remotes = None self.wavelengths = None self.steps = None self.integration_time = None self.max_linear_stage_position = None self.linear_stage_set = False self.linear_stage_2_set = False self.electrometer_set = False self.tunable_laser_set = False self.scan_duration = None self.timeout = None self.stablization = False self.number_of_scans = None self.log.setLevel(logging.DEBUG) self.log.debug("END INIT")
async def test_negative_lsst_dds_historysync(self) -> None: """Test that setting LSST_DDS_HISTORYSYNC < 0 prevents waiting for historical data. This setting applies to SalInfo, not Remote, but it requires some topics in order to be tested, and Remote provides topics. """ index = next(index_gen) async with salobj.Domain() as domain: # Make a normal remote that waits for historical data. remote1 = salobj.Remote(domain=domain, name="Test", index=index) await asyncio.wait_for(remote1.start_task, timeout=STD_TIMEOUT) assert len(remote1.salinfo.wait_history_isok) > 0 # Make a remote that does not wait for historical data # by defining the history timeout env variable < 0. os.environ[HISTORY_TIMEOUT_NAME] = "-1" try: remote2 = salobj.Remote(domain=domain, name="Test", index=index) await asyncio.wait_for(remote2.start_task, timeout=STD_TIMEOUT) finally: if INITIAL_HISTORY_TIMEOUT is None: del os.environ[HISTORY_TIMEOUT_NAME] else: os.environ[HISTORY_TIMEOUT_NAME] = INITIAL_HISTORY_TIMEOUT assert len(remote2.salinfo.wait_history_isok) == 0
async def asyncSetUp(self): self.datadir = os.path.abspath( os.path.join(os.path.dirname(__file__), "data")) standardpath = os.path.join(self.datadir, "standard") externalpath = os.path.join(self.datadir, "external") self.queue = scriptqueue.ScriptQueue(index=1, standardpath=standardpath, externalpath=externalpath) self.queue_remote = salobj.Remote(self.queue.domain, "ScriptQueue", index=1) self.process = None self.scheduler = SchedulerCSC(index=1, config_dir=TEST_CONFIG_DIR) self.scheduler_remote = salobj.Remote(self.scheduler.domain, "Scheduler", index=1) self.observatory_mock = ObservatoryStateMock() self.received_targets = 0 self.expected_targets = 2 self.heartbeats = 0 self.heartbeats_tol = 0.4 self.target_test_timeout = 120 await asyncio.gather( self.scheduler.start_task, self.queue.start_task, self.scheduler_remote.start_task, self.queue_remote.start_task, self.observatory_mock.start_task, )
def __init__(self): super().__init__("ATArchiver", initial_state=salobj.State.STANDBY) camera_events = {'endReadout', 'startIntegration'} self.camera_remote = salobj.Remote(self.domain, "ATCamera", readonly=True, include=camera_events, evt_max_history=0) self.camera_remote.evt_endReadout.callback = self.endReadoutCallback self.camera_remote.evt_startIntegration.callback = self.startIntegrationCallback aths_events = {'largeFileObjectAvailable'} self.aths_remote = salobj.Remote(self.domain, "ATHeaderService", readonly=True, include=aths_events, evt_max_history=0) self.aths_remote.evt_largeFileObjectAvailable.callback = self.largeFileObjectAvailableCallback self.director = ATDirector(self, "ATArchiver", "atarchiver_config.yaml", "ATArchiverCSC.log") self.director.configure() self.transitioning_to_fault_evt = asyncio.Event() self.transitioning_to_fault_evt.clear() self.current_state = None LOGGER.info( "************************ Starting ATArchiver ************************" )
def make_comcam_remotes(): d = salobj.Domain() CCArchiver = salobj.Remote(d, 'CCArchiver') CCCamera = salobj.Remote(d, 'CCCamera') CCHeaderService = salobj.Remote(d, 'CCHeaderService') return { "CCArchiver": CCArchiver, "CCCamera": CCCamera, "CCHeaderService": CCHeaderService }
def __init__(self, name='ATHeaderService', initial_state=mystate): # Where we will store the metadata self.clean() # Start the CSC with name super().__init__(name=name, index=0, initial_state=mystate) print(f"Creating for worker for: {name}") print(f"Running {salobj.__version__}") self.atcam = salobj.Remote(domain=self.domain, name="ATCamera", index=0) self.ATPtg = salobj.Remote(domain=self.domain, name="ATPtg", index=0) self.atcam.evt_startIntegration.callback = self.startIntegration_callback
def __init__(self, index): super().__init__( index=index, descr= "Configure and take data from the auxiliary telescope CalSystem.", ) self.cmd_timeout = 10 self.change_grating_time = 60 self.electrometer = salobj.Remote(domain=self.domain, name="Electrometer", index=1) self.monochromator = salobj.Remote(domain=self.domain, name="ATMonochromator") self.fiber_spectrograph = salobj.Remote(domain=self.domain, name="FiberSpectrograph")
def __init__( self, config_dir=None, initial_state=salobj.State.STANDBY, override="", ): super().__init__( name="MTDomeTrajectory", config_schema=CONFIG_SCHEMA, config_dir=config_dir, index=None, initial_state=initial_state, override=override, simulation_mode=0, ) # Telescope target, from the MTMount target event; # an ElevationAzimuth; None before a target is seen. self.telescope_target = None # Next telescope target, eventually from the scheduler; # an ElevationAzimuth; None before the next target is seen; self.next_telescope_target = None # Tasks that start dome azimuth and elevation motion # and wait for the motionState and target events # that indicate the motion has started. # While one of these is running that axis will not be commanded. # This avoids the problem of new telescope target events # causing unwanted motion when the dome has been commanded # but has not yet had a chance to report the fact. self.move_dome_azimuth_task = utils.make_done_future() self.move_dome_elevation_task = utils.make_done_future() # Task that is set to (moved_elevation, moved_azimuth) # whenever the follow_target method runs. self.follow_task = asyncio.Future() self.mtmount_remote = salobj.Remote(domain=self.domain, name="MTMount", include=["target"]) self.dome_remote = salobj.Remote( domain=self.domain, name="MTDome", include=["azMotion", "azTarget", "elMotion", "elTarget"], ) self.mtmount_remote.evt_target.callback = self.update_mtmount_target
async def test_run_command_line(self): exe_name = "run_one_script.py" exe_path = shutil.which(exe_name) if exe_path is None: self.fail( f"Could not find bin script {exe_name}; did you setup and scons this package?" ) index = 135 script = DATA_DIR / "standard" / "subdir" / "script3" config_path = DATA_DIR / "config1.yaml" async with salobj.Domain() as domain, salobj.Remote( domain=domain, name="Script", index=index) as remote: process = await asyncio.create_subprocess_exec( exe_name, str(script), "--config", str(config_path), "--index", str(index), "--loglevel", "10", ) try: t0 = time.monotonic() await asyncio.wait_for(process.wait(), timeout=STD_TIMEOUT) dt = time.monotonic() - t0 print(f"It took {dt:0.2f} seconds to run the script") except Exception: if process.returncode is None: process.terminate() raise final_state = remote.evt_state.get() assert final_state.state == ScriptState.DONE
def __init__( self, index: int, descr: str, scheduler_index: SalIndex, desired_state: salobj.State, ) -> None: super().__init__(index=index, descr=descr) self.scheduler_remote = salobj.Remote( domain=self.domain, name="Scheduler", index=scheduler_index, include=["summaryState", "heartbeat"], ) self.desired_state = desired_state self.timeout_start = 30.0 self.configuration = "" self._state_transition_methods_to_try = ( self._handle_csc_in_standby, self._handle_csc_in_disabled_or_fault, self._handle_csc_in_enabled, )
async def test_successful_command(client, *args, **kwargs): # Arrange remote = salobj.Remote(domain=salobj.Domain(), name="LOVE") await remote.start_task observing_log_msg = { "user": "******", "message": "a message", } # Act remote.evt_observingLog.flush() response = await client.post("/lovecsc/observinglog", data=json.dumps(observing_log_msg)) # Assert assert response.status == 200 response = await response.json() assert response["ack"] == "Added new observing log to SAL" result = await remote.evt_observingLog.next(flush=False) assert result.user == "an user" assert result.message == "a message" # Clean up await remote.close()
async def make_remote( self, identity: str) -> typing.AsyncGenerator[salobj.Remote, None]: """Create a remote to talk to self.csc with a specified identity. Uses the domain created by make_csc. Parameters ---------- identity : `str` Identity for remote. Notes ----- Adds a logging.StreamHandler if one is not already present. """ domain = self.csc.domain original_default_identity = domain.default_identity try: domain.default_identity = identity remote = salobj.Remote( domain=domain, name=self.csc.salinfo.name, index=self.csc.salinfo.index, ) finally: domain.default_identity = original_default_identity assert remote.salinfo.identity == identity try: await remote.start_task yield remote finally: await remote.close()
def __init__( self, config_dir=None, initial_state=salobj.State.STANDBY, settings_to_apply="", simulation_mode=0, ): self.config = None self.simulated_jobs = set() super().__init__( "OCPS", index=0, config_schema=CONFIG_SCHEMA, config_dir=config_dir, initial_state=initial_state, settings_to_apply=settings_to_apply, simulation_mode=simulation_mode, ) self.cmd_execute.allow_multiple_callbacks = True if hasattr(self.config, "triggers"): self.trigger_remotes = [] for trigger in self.config.triggers: remote = salobj.Remote( domain=self.domain, name=trigger.csc, include=[trigger.event], ) event = remote.getattr(trigger.event) event.callback = self.gen_event_callback(trigger) self.trigger_remotes.append(remote) self.log.addHandler(logging.StreamHandler())
async def check_executable(self, script_path): """Check that an executable script can be launched. Parameter --------- script_path : `str` Full path to script. """ salobj.set_random_lsst_dds_partition_prefix() index = self.next_index() script_path = pathlib.Path(script_path).resolve() assert script_path.is_file() async with salobj.Domain() as domain, salobj.Remote( domain=domain, name="Script", index=index) as remote: initial_path = os.environ["PATH"] try: os.environ["PATH"] = str( script_path.parent) + ":" + initial_path process = await asyncio.create_subprocess_exec( str(script_path), str(index)) state = await remote.evt_state.next(flush=False, timeout=MAKE_TIMEOUT) assert state.state == Script.ScriptState.UNCONFIGURED finally: process.terminate() os.environ["PATH"] = initial_path
def create_Remotes(self): """ Create the Remotes to collect telemetry/Events for channels as defined by the meta-data """ self.log.info("*** Starting Connections for Meta-data ***") # The list containing the unique devices (CSCs) to make connection self.devices = [] # The dict containing all of the threads and connections self.Remote = {} self.Remote_get = {} for channel_name, c in self.channels.items(): devname = get_channel_devname(c) # Make sure we only create these once if devname not in self.devices: self.devices.append(devname) self.Remote[devname] = salobj.Remote(domain=self.domain, name=c['device'], index=c['device_index']) self.log.info(f"Created Remote for {devname}") # capture the evt.get() function for the channel if c['Stype'] == 'Event': self.Remote_get[channel_name] = getattr(self.Remote[devname], f"evt_{c['topic']}").get self.log.info(f"Storing Remote.evt_{c['topic']}.get() for {channel_name}") if c['Stype'] == 'Telemetry': self.Remote_get[channel_name] = getattr(self.Remote[devname], f"tel_{c['topic']}").get self.log.info(f"Storing Remote.tel_{c['topic']}.get() for {channel_name}") # Select the start_collection channel self.name_start = get_channel_name(self.config.start_collection_event) # Select the end_collection channel self.name_end = get_channel_name(self.config.end_collection_event)
async def main(): logging.info("main method") async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="GenericCamera", index=args.index) logging.info(f"starting remote with index {args.index}") await remote.start_task logging.info("starting CSC") await remote.cmd_start.set_start(timeout=120) logging.info("disabling") await salobj.set_summary_state( remote=remote, state=salobj.State.DISABLED, timeout=120 ) logging.info("enabling") await salobj.set_summary_state( remote=remote, state=salobj.State.ENABLED, timeout=120 ) logging.info("taking a picture") await remote.cmd_takeImages.set_start( numImages=1, expTime=2.0, shutter=True, sensors="", keyValueMap="", obsNote="image", ) logging.info("disabling again") await salobj.set_summary_state( remote=remote, state=salobj.State.DISABLED, timeout=120 ) logging.info("offline") await salobj.set_summary_state( remote=remote, state=salobj.State.OFFLINE, timeout=120 )
def __init__( self, config_dir=None, initial_state=salobj.base_csc.State.STANDBY, override="", ): # Commanded dome azimuth (deg), from the ATDome azimuthCommandedState # event; None before the event is seen. self.dome_target_azimuth = None # Telescope target, from the ATMCS target event; # an ElevationAzimuth; None before a target is seen. self.telescope_target = None # Task that starts dome azimuth motion # and waits for the motionState and target events # that indicate the motion has started. # While running that axis will not be commanded. # This avoids the problem of new telescope target events # causing unwanted motion when the dome has been commanded # but has not yet had a chance to report the fact. self.move_dome_azimuth_task = utils.make_done_future() # Next telescope target, eventually from the scheduler; # an ElevationAzimuth; None before the next target is seen; self.next_telescope_target = None super().__init__( name="ATDomeTrajectory", config_schema=CONFIG_SCHEMA, config_dir=config_dir, index=None, initial_state=initial_state, override=override, simulation_mode=0, ) self.atmcs_remote = salobj.Remote(domain=self.domain, name="ATMCS", include=["target"]) self.dome_remote = salobj.Remote(domain=self.domain, name="ATDome", include=["azimuthCommandedState"]) self.atmcs_remote.evt_target.callback = self.atmcs_target_callback self.dome_remote.evt_azimuthCommandedState.callback = ( self.atdome_commanded_azimuth_state_callback)
def __init__( self, domain, log, standardpath, externalpath, next_visit_callback=None, next_visit_canceled_callback=None, queue_callback=None, script_callback=None, min_sal_index=MIN_SAL_INDEX, max_sal_index=salobj.MAX_SAL_INDEX, verbose=False, ): if not os.path.isdir(standardpath): raise ValueError(f"No such dir standardpath={standardpath}") if not os.path.isdir(externalpath): raise ValueError(f"No such dir externalpath={externalpath}") for arg, arg_name in ( (next_visit_callback, "next_visit_callback"), (next_visit_canceled_callback, "next_visit_canceled_callback"), (queue_callback, "queue_callback"), (script_callback, "script_callback"), ): if arg is not None and not inspect.iscoroutinefunction(arg): raise TypeError( f"{arg_name}={arg} must be a coroutine or None") self.domain = domain self.log = log.getChild("QueueModel") self.standardpath = os.path.abspath(standardpath) self.externalpath = os.path.abspath(externalpath) self.next_visit_callback = next_visit_callback self.next_visit_canceled_callback = next_visit_canceled_callback self.queue_callback = queue_callback self.script_callback = script_callback self.min_sal_index = min_sal_index self.max_sal_index = max_sal_index self.verbose = verbose # queue of ScriptInfo instances self.queue = collections.deque() self.history = collections.deque(maxlen=MAX_HISTORY) self.current_script = None self._running = True self._enabled = False self._index_generator = index_generator(imin=min_sal_index, imax=max_sal_index) self._scripts_being_stopped = set() # use index=0 so we get messages for all scripts self.remote = salobj.Remote(domain=domain, name="Script", index=0, evt_max_history=0) self.remote.evt_metadata.callback = self._script_metadata_callback self.remote.evt_state.callback = self._script_state_callback if self.verbose: self.remote.evt_logMessage.callback = self._log_message_callback self.start_task = self.remote.start_task
def setUp(self): self.csc = WhiteLightSourceCSC(sim_mode=1) self.csc.summary_state = salobj.State.ENABLED # set short cooldown and warmup periods so the tests don't take hours self.csc.model.cooldownPeriod = 3 self.csc.model.warmupPeriod = 3 self.remote = salobj.Remote(SALPY_ATWhiteLight, index=None)
async def test_all_names(self): async with salobj.Domain() as domain: remote = salobj.Remote( domain=domain, name="Test", index=self.index, readonly=True, include=(), start=False, ) topic_names = [ f"evt_{name}" for name in remote.salinfo.event_names ] topic_names += [ f"tel_{name}" for name in remote.salinfo.telemetry_names ] # Check that no topics have been added yet for name in topic_names: self.assertFalse(hasattr(remote, name)) wrapper = watcher.base.RemoteWrapper(remote=remote, topic_names=topic_names) desired_attr_name = (remote.salinfo.name.lower() + "_" + str(remote.salinfo.index)) self.assertEqual(wrapper.attr_name, desired_attr_name) # Check that all topics have been added for name in topic_names: self.assertTrue(hasattr(remote, name)) wrapper_dir = set(dir(wrapper)) self.assertTrue(set(topic_names).issubset(wrapper_dir)) await asyncio.wait_for(remote.start(), timeout=LONG_TIMEOUT) # Check that the initial value for each topic is None. for name in topic_names: self.assertIsNone(getattr(wrapper, name)) # Write one event and one telemetry topic evt_scalars_writer = salobj.topics.ControllerEvent( salinfo=remote.salinfo, name="scalars") tel_scalars_writer = salobj.topics.ControllerTelemetry( salinfo=remote.salinfo, name="scalars") evtint = -3 telint = 47 evt_scalars_writer.set_put(int0=evtint) tel_scalars_writer.set_put(int0=telint) # Wait for the read topics to read the data. await remote.evt_scalars.next(flush=False, timeout=STD_TIMEOUT) await remote.tel_scalars.next(flush=False, timeout=STD_TIMEOUT) # Verify that the wrapper produces the expected values. self.assertEqual(wrapper.evt_scalars.int0, evtint) self.assertEqual(wrapper.tel_scalars.int0, telint)
def __init__(self, initial_state): salobj.test_utils.set_random_lsst_dds_domain() self.csc = linearStage.csc.LinearStageCSC(port="/dev/null", address=1, index=1) self.csc.model._ls = linearStage.hardware.MockLinearStageComponent() self.remote = salobj.Remote(domain=self.csc.domain, name="LinearStage", index=1)
async def test_start_false(self) -> None: """Test the start argument of Remote.""" index = next(index_gen) async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="Test", index=index, start=False) assert not hasattr(remote, "start_task")
async def test_evt_max_history(self) -> None: """Test non-default evt_max_history Remote constructor argument.""" evt_max_history = 0 index = next(index_gen) async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="Test", index=index, evt_max_history=evt_max_history) self.assert_max_history(remote, evt_max_history=evt_max_history)
async def run_command(self): async with salobj.Domain() as domain: arc = salobj.Remote(domain=domain, name=self.device_name, index=0) await arc.start_task try: cmd = getattr(arc, f"cmd_{self.command}") await cmd.set_start(timeout=self.timeout) except Exception as e: print(e)
async def shutdown(opts): end_state = getattr(salobj.State, opts.state.upper()) domain = salobj.Domain() try: remote = salobj.Remote(domain=domain, name="DSM", index=opts.index) await remote.start_task await salobj.set_summary_state(remote, end_state) finally: await domain.close()
def add_rule(self, rule): """Add a rule. Parameters ---------- rule : `BaseRule` Rule to add. Raises ------ ValueError If a rule by this name already exists RuntimeError If the rule uses a remote for which no IDL file is available in the ts_idl package. RuntimeEror: If the rule references a topic that does not exist. """ if rule.name in self.rules: raise ValueError(f"A rule named {rule.name} already exists") rule.alarm.configure( callback=self.alarm_callback, auto_acknowledge_delay=self.config.auto_acknowledge_delay, auto_unacknowledge_delay=self.config.auto_unacknowledge_delay, ) # Create remotes and add callbacks. for remote_info in rule.remote_info_list: remote = self.remotes.get(remote_info.key, None) if remote is None: remote = salobj.Remote( domain=self.domain, name=remote_info.name, index=remote_info.index, readonly=True, include=[], start=False, ) self.remotes[remote_info.key] = remote wrapper = base.RemoteWrapper(remote=remote, topic_names=remote_info.topic_names) setattr(rule, wrapper.attr_name, wrapper) for topic_name in remote_info.callback_names: topic = getattr(remote, topic_name, None) if topic is None: raise RuntimeError( f"Bug: could not get topic {topic_name} from remote " "after constructing the remote wrapper") if topic.callback is None: topic.callback = base.TopicCallback(topic=topic, rule=rule, model=self) else: topic.callback.add_rule(rule) # Add the rule. self.rules[rule.name] = rule
def __init__(self, script_log_level, **kwargs): super().__init__(name="ScriptQueue", **kwargs) self.script_log_level = script_log_level self.help_dict[ "add"] = f"""type path config options # add a script to the end of the queue: • type = s or std for standard, e or ext for external • config = @yaml_path or keyword1=value1 keyword2=value2 ... where yaml_path is the path to a yaml file; the .yaml suffix is optional • options can be any of the following, in any order (but all option args must follow all config args): -location=int # first=0, last=1, before=2, after=3 -locationSalIndex=int -logLevel=int # error=40, warning=30, info=20, debug=10; default is {script_log_level} -pauseCheckpoint=str # a regex -stopCheckpoint=str # a regex • examples: add s auxtel/slew_telescope_icrs.py ra=10 dec=0 -location=1 add s auxtel/slew_telescope_icrs.py @target -logLevel=10 -location=0 """ # Default options for the add command self.default_add_options = dict( location=Location.LAST, locationSalIndex=0, logLevel=self.script_log_level, pauseCheckpoint="", stopCheckpoint="", ) self.help_dict["showSchema"] = "type path # type=s, std, e, or ext" self.help_dict[ "stopScripts"] = "sal_index1 [sal_index2 [... sal_indexN]] terminate (0 or 1)" self.script_remote = salobj.Remote( domain=self.domain, name="Script", index=0, readonly=True, include=["heartbeat", "logMessage", "state"], ) self.script_remote.evt_logMessage.callback = self.script_log_message self.script_remote.evt_state.callback = self.script_state self.script_remote.evt_heartbeat.callback = self.script_heartbeat # Dict of "type" argument: isStandard self.script_type_dict = dict(s=True, std=True, standard=True, e=False, ext=False, external=False) # SAL index of script whose heartbeat is being monitored; # this should be the currently executing script. self._script_to_monitor = 0 self.script_heartbeat_monitor_task = make_done_future()
def __init__(self, index: int, remote_indices: typing.Iterable[int]) -> None: super().__init__(index, descr="Script with remotes") remotes = [] # use remotes that read history here, to check that # script.start_task waits for the start_task in each remote. for rind in remote_indices: remotes.append( salobj.Remote(domain=self.domain, name="Test", index=rind)) self.remotes = remotes
def __init__( self, index, config_dir=None, initial_state=salobj.State.STANDBY, simulation_mode=0, ): """ Initialize DIMM CSC. Parameters ---------- index : int Index for the DIMM. This enables the control of multiple DIMMs. """ super().__init__( "DIMM", index=index, config_schema=CONFIG_SCHEMA, config_dir=config_dir, initial_state=initial_state, simulation_mode=simulation_mode, ) # A remote to weather station data self.ws_remote = salobj.Remote( self.domain, "WeatherStation", 1, readonly=True, include=[ "windSpeed", "windDirection", "dewPoint", "precipitation", "snowDepth", ], ) self.controller = None self.loop_die_timeout = 5 # how many heartbeats to wait for the loops to die? self.telemetry_loop_running = False self.telemetry_loop_task = None self.seeing_loop_running = False self.seeing_loop_task = None self.csc_running = True self.health_monitor_loop_task = asyncio.create_task( self.health_monitor())
def __init__(self, index: int, scheduler_index: SalIndex) -> None: super().__init__( index=index, descr=f"Resume {scheduler_index.name} Scheduler", ) self.scheduler_remote = salobj.Remote( domain=self.domain, name="Scheduler", index=scheduler_index, include=[], ) self.timeout_start = 30.0