async def health_monitor_loop(self) -> None:
        """Monitor the state of the hardware."""

        start_tai = utils.current_tai()

        while self.summary_state == salobj.State.ENABLED:
            try:
                controller_status = await self.model.get_status()
                await self.evt_status.set_write(status=controller_status)
                if controller_status == Status.FAULT:
                    await self.fault(
                        code=ErrorCode.HARDWARE_ERROR,
                        report="Hardware controller reported FAULT.",
                        traceback="",
                    )
                    return
                curr_tai = utils.current_tai()
                await self.tel_timestamp.set_write(timestamp=curr_tai)
                await self.tel_loopTime.set_write(loopTime=curr_tai - start_tai
                                                  )
                await asyncio.sleep(self.heartbeat_interval)
            except Exception:
                await self.fault(
                    code=ErrorCode.MISC,
                    report="Health monitor loop unexpectedly died.",
                    traceback=traceback.format_exc(),
                )
                return
Example #2
0
 async def _script_state_callback(self, state):
     self.script_state = state.state
     self.state_delay = current_tai() - state.private_sndStamp
     if self.script_state == ScriptState.UNCONFIGURED and self.config_task is None:
         self.timestamp_configure_start = current_tai()
         self.config_task = asyncio.create_task(self._configure())
         self.config_task.add_done_callback(self._trigger_callback)
         self.start_task.set_result(None)
     await self._run_callback()
Example #3
0
    async def do_expose(self, data):
        """Take an exposure with the connected spectrograph.

        Parameters
        ----------
        data : `DataType`
            Command data

        Raises
        ------
        asyncio.CancelledError
            Raised if the exposure is cancelled before it completes.
        lsst.ts.salobj.ExpectedError
            Raised if an error occurs while taking the exposure.
        """
        self.assert_enabled()
        msg = self.device.check_expose_ok(data.duration)
        if msg is not None:
            raise salobj.ExpectedError(msg)
        try:
            date_begin = utils.astropy_time_from_tai_unix(utils.current_tai())
            task = asyncio.create_task(self.device.expose(data.duration))
            await self.evt_exposureState.set_write(status=ExposureState.INTEGRATING)
            wavelength, spectrum = await task
            date_end = utils.astropy_time_from_tai_unix(utils.current_tai())
            await self.evt_exposureState.set_write(status=ExposureState.DONE)
            temperature = self.tel_temperature.data.temperature * u.deg_C
            setpoint = self.tel_temperature.data.setpoint * u.deg_C
            n_pixels = self.evt_deviceInfo.data.npixels
            spec_data = dataManager.SpectrographData(
                wavelength=wavelength,
                spectrum=spectrum,
                duration=data.duration,
                date_begin=date_begin,
                date_end=date_end,
                type=data.type,
                source=data.source,
                temperature=temperature,
                temperature_setpoint=setpoint,
                n_pixels=n_pixels,
            )
        except asyncio.TimeoutError as e:
            await self.evt_exposureState.set_write(status=ExposureState.TIMEDOUT)
            msg = f"Timeout waiting for exposure: {repr(e)}"
            await self.fault(code=20, report=msg)
            raise salobj.ExpectedError(msg)
        except asyncio.CancelledError:
            await self.evt_exposureState.set_write(status=ExposureState.CANCELLED)
            raise
        except Exception as e:
            await self.evt_exposureState.set_write(status=ExposureState.FAILED)
            msg = f"Failed to take exposure with fiber spectrograph: {repr(e)}"
            await self.fault(code=20, report=msg)
            raise salobj.ExpectedError(msg)

        await self.save_data(spec_data)
Example #4
0
    def _basic_write(self) -> None:
        """Put self.data after setting the private_x fields."""
        self.data.private_sndStamp = utils.current_tai()
        self.data.private_revCode = self.rev_code
        self.data.private_origin = self.salinfo.domain.origin
        self.data.private_identity = self.salinfo.identity
        if self._seq_num_generator is not None:
            self.data.private_seqNum = next(self._seq_num_generator)
        # when index is 0 use the default of 0 and give senders a chance
        # to override it.
        if self.salinfo.index != 0:
            self.data.salIndex = self.salinfo.index

        try:
            self._writer.write(self.data)
        except struct.error as e:
            raise ValueError(
                f"{self.name} write({self.data}) failed: one or more fields invalid"
            ) from e
        except TypeError as e:
            raise ValueError(
                f"{self.name} write({self.data}) failed: "
                f"perhaps one or more array fields has been set to a scalar"
            ) from e
        except IndexError as e:
            raise ValueError(
                f"{self.name} write({self.data}) failed: "
                f"probably at least one array field is too short"
            ) from e
Example #5
0
    async def telemetry_loop(self):
        try:
            while True:
                tai = utils.current_tai()
                azimuth_target = self.azimuth_actuator.target.at(tai)
                azimuth_actual = self.azimuth_actuator.path.at(tai)
                await self.tel_azimuth.set_write(
                    positionActual=azimuth_actual.position,
                    positionCommanded=azimuth_target.position,
                    velocityActual=azimuth_actual.velocity,
                    velocityCommanded=azimuth_target.velocity,
                )

                await self.tel_lightWindScreen.set_write(
                    positionActual=self.elevation_actuator.position(tai),
                    positionCommanded=self.elevation_actuator.end_position,
                    velocityActual=self.elevation_actuator.velocity(tai),
                    velocityCommanded=0,
                )
                await asyncio.sleep(self.telemetry_interval)
        except asyncio.CancelledError:
            raise
        except Exception:
            self.log.exception("Telemetry loop failed")
            raise
Example #6
0
    def telemetry_callback(self, client):
        """Called when the TCP/IP controller outputs telemetry.

        Parameters
        ----------
        client : `CommandTelemetryClient`
            TCP/IP client.
        """
        # Strangely telemetry.state, offline_substate and enabled_substate
        # are all floats from the controller. But they should only have
        # integer value, so I output them as integers.
        self.evt_controllerState.set_put(
            controllerState=int(client.telemetry.state),
            offlineSubstate=int(client.telemetry.offline_substate),
            enabledSubstate=int(client.telemetry.enabled_substate),
        )
        self.evt_commandableByDDS.set_put(
            state=bool(client.telemetry.application_status
                       & ApplicationStatus.DDS_COMMAND_SOURCE))

        self.tel_rotation.set_put(
            demandPosition=client.telemetry.cmd_position,
            actualPosition=client.telemetry.curr_position,
            timestamp=utils.current_tai(),
        )
Example #7
0
    async def end_readout(self, data: salobj.type_hints.BaseMsgType) -> None:
        """Wait `self.readout_time` and send endReadout event."""
        self.log.debug(f"end_readout started: sleep {self.readout_time}")
        await asyncio.sleep(self.readout_time)

        self.atcam_image_started = False
        self.nimages += 1
        if hasattr(data, "expTime"):
            self.exptime_list.append(data.expTime)
        else:
            self.exptime_list.append(utils.current_tai() -
                                     self.atcam_image_started)

        date_id = astropy.time.Time.now().tai.isot.split("T")[0].replace(
            "-", "")
        image_name = f"test_latiss_{date_id}_{next(index_gen)}"
        self.log.debug(f"sending endReadout: {image_name} :: {data}")

        additional_keys, additional_values = list(
            zip(*[
                key_value.strip().split(":", maxsplit=1)
                for key_value in data.keyValueMap.split(",")
            ]))

        await self.atcam.evt_endReadout.set_write(
            imageName=image_name,
            additionalKeys=":".join([key.strip() for key in additional_keys]),
            additionalValues=":".join(
                [value.strip() for value in additional_values]),
        )
        self.log.debug("sending LFOA")
        await self.atheaderservice.evt_largeFileObjectAvailable.write()
        self.log.debug("end_readout done")
Example #8
0
 def do_setInstrumentPort(self, data):
     self.assert_enabled("setInstrumentPort")
     if self._tracking_enabled:
         raise salobj.ExpectedError(
             "Cannot setInstrumentPort while tracking is enabled"
         )
     port = data.port
     try:
         m3_port_positions_ind = self._port_info_dict[port][0]
     except IndexError:
         raise salobj.ExpectedError(f"Invalid port={port}")
     try:
         m3_port_positions = self.m3_port_positions[m3_port_positions_ind]
     except IndexError:
         raise RuntimeError(
             f"Bug! invalid m3_port_positions_ind={m3_port_positions_ind} for port={port}"
         )
     self.evt_m3PortSelected.set_put(selected=port)
     m3actuator = self.actuators[Axis.M3]
     if (
         m3actuator.target.position == m3_port_positions
         and self.evt_m3InPosition.data.inPosition
     ):
         # already there; don't do anything
         return
     self.actuators[Axis.M3].set_target(
         tai=utils.current_tai(), position=m3_port_positions, velocity=0
     )
     self._axis_enabled[Axis.NA1] = False
     self._axis_enabled[Axis.NA2] = False
     self.update_events()
Example #9
0
 async def closeShutter(self) -> None:
     """Close the shutter."""
     self.log.debug("Received command 'closeShutter'")
     self.command_time_tai = utils.current_tai()
     self.status = LlcMotionState.CLOSED
     # Both positions are expressed in percentage.
     self.position_actual = np.zeros(_NUM_SHUTTERS, dtype=float)
     self.position_commanded = 0.0
Example #10
0
 async def openShutter(self) -> None:
     """Open the shutter."""
     self.log.debug("Received command 'openShutter'")
     self.command_time_tai = utils.current_tai()
     self.status = LlcMotionState.OPEN
     # Both positions are expressed in percentage.
     self.position_actual = np.full(_NUM_SHUTTERS, 100.0, dtype=float)
     self.position_commanded = 100.0
Example #11
0
 async def _finish_stop_tracking(self):
     """Wait for the main axes to stop."""
     end_times = [self.actuators[axis].path[-1].tai for axis in MainAxes]
     max_end_time = max(end_times)
     dt = 0.1 + max_end_time - utils.current_tai()
     if dt > 0:
         await asyncio.sleep(dt)
     asyncio.ensure_future(self._run_update_events())
Example #12
0
    async def test_late_track_target(self):
        # Use a short tracking interval so the test runs quickly.
        max_tracking_interval = 0.2
        async with self.make_csc(initial_state=salobj.State.ENABLED):
            # Get the initial summary state, so `fault_to_enabled` sees FAULT.
            await self.assert_next_summary_state(salobj.State.ENABLED)

            self.csc.configure(
                max_tracking_interval=max_tracking_interval,
                max_velocity=(100,) * 5,
                max_acceleration=(200,) * 5,
            )
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGDISABLED
            )

            await self.assert_next_sample(
                self.remote.evt_m3State, state=M3State.NASMYTH1
            )

            await self.remote.cmd_startTracking.start(timeout=STD_TIMEOUT)
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGENABLED
            )

            # wait too long for trackTarget
            await self.assert_next_sample(
                self.remote.evt_atMountState,
                state=AtMountState.TRACKINGDISABLED,
                timeout=max_tracking_interval + 0.2,
            )
            await self.fault_to_enabled()

            # try again, and this time send a trackTarget command
            # before waiting too long
            await self.remote.cmd_startTracking.start(timeout=STD_TIMEOUT)
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGENABLED
            )

            await self.remote.cmd_trackTarget.set_start(
                elevation=10, taiTime=utils.current_tai(), trackId=20, timeout=1
            )

            # wait too long for trackTarget
            await self.assert_next_sample(
                self.remote.evt_atMountState,
                state=AtMountState.STOPPING,
                timeout=max_tracking_interval + 0.2,
            )

            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGDISABLED
            )
Example #13
0
    def _cleanup(self, returncode=None):
        """Clean up when the Script subprocess exits.

        Set the timestamp_process_end, cancel the config task,
        delete the remote, run the callback and delete the callback.
        """
        self.timestamp_process_end = current_tai()
        if self.config_task is not None:
            self.config_task.cancel()
        self._cancel_set_clear_group_id()
        asyncio.create_task(self._finish_cleanup())
Example #14
0
 async def _finish_disable_all_drives(self):
     """Wait for the main axes to stop."""
     end_times = [actuator.path[-1].tai for actuator in self.actuators]
     max_end_time = max(end_times)
     # give a bit of margin to be sure the axes are stopped
     dt = 0.1 + max_end_time - utils.current_tai()
     if dt > 0:
         await asyncio.sleep(dt)
     for axis in Axis:
         self._axis_enabled[axis] = False
     asyncio.ensure_future(self._run_update_events())
 def get_dome_target_azimuth(self):
     """Get the current dome azimuth target."""
     target = self.dome_remote.evt_azTarget.get()
     if target is None:
         return None
     if math.isnan(target.position):
         return None
     return simactuators.path.PathSegment(
         position=target.position,
         velocity=target.velocity,
         tai=utils.current_tai(),
     )
Example #16
0
    async def test_stop_tracking_while_slewing(self):
        """Call stopTracking while tracking, before a slew is done."""
        async with self.make_csc(initial_state=salobj.State.ENABLED):
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGDISABLED
            )

            await self.assert_next_sample(
                self.remote.evt_m3State, state=M3State.NASMYTH1
            )

            await self.remote.cmd_startTracking.start(timeout=STD_TIMEOUT)
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGENABLED
            )

            start_tai = utils.current_tai()
            path_dict = dict(
                elevation=simactuators.path.PathSegment(tai=start_tai, position=45),
                azimuth=simactuators.path.PathSegment(tai=start_tai, position=100),
                nasmyth1RotatorAngle=simactuators.path.PathSegment(
                    tai=start_tai, position=90
                ),
            )
            trackId = 35  # arbitary

            tai = start_tai + 0.1  # offset is arbitrary but reasonable
            target_kwargs = self.compute_track_target_kwargs(
                tai=tai, path_dict=path_dict, trackId=trackId
            )
            await self.remote.cmd_trackTarget.set_start(**target_kwargs, timeout=1)

            await asyncio.sleep(0.2)

            await self.remote.cmd_stopTracking.start(timeout=1)
            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.STOPPING
            )

            await asyncio.sleep(0.2)  # Give events time to arrive.

            for event in self.in_position_events:
                desired_in_position = event is self.remote.evt_m3InPosition
                await self.assert_next_sample(event, inPosition=desired_in_position)

            await self.assert_next_sample(
                self.remote.evt_atMountState, state=AtMountState.TRACKINGDISABLED
            )

            for actuator in self.csc.actuators:
                self.assertEqual(actuator.kind(), actuator.Kind.Stopped)
 async def current_target_status(self):
     while self.run_current_target_status_loop:
         time = utils.current_tai()
         self.model.update_state(time)
         await self.ptg.tel_currentTargetStatus.set_write(
             timestamp=time,
             demandAz=self.model.current_state.telaz,
             demandEl=self.model.current_state.telalt,
             demandRot=self.model.current_state.telrot,
             demandRa=self.model.current_state.ra,
             demandDec=self.model.current_state.dec,
             parAngle=self.model.current_state.pa,
         )
         await asyncio.sleep(self.telemetry_sleep_time)
Example #18
0
    async def setTemperature(self, temperature: float) -> None:
        """Set the preferred temperature in the dome. It should mock cooling
        down or warming up but it doesn't.

        Parameters
        ----------
        temperature: `float`
            The preferred temperature (degrees Celsius). In reality this should
            be a realistic temperature in the range of about -30 C to +40 C but
            the provided temperature is not checked against this range.
        """
        self.command_time_tai = utils.current_tai()
        self.status = LlcMotionState.OPEN
        self.temperature[:] = temperature
Example #19
0
    def setUp(self):

        self.raw_telemetry = dict()

        self.raw_telemetry["timeHandler"] = None
        self.raw_telemetry["scheduled_targets"] = []
        self.raw_telemetry["observing_queue"] = []
        self.raw_telemetry["observatoryState"] = None
        self.raw_telemetry["bulkCloud"] = 0.0
        self.raw_telemetry["seeing"] = 1.0

        self.models = dict()

        self.models["location"] = ObservatoryLocation()
        self.models["location"].for_lsst()

        self.models["observatory_model"] = ObservatoryModel(self.models["location"])
        self.models["observatory_model"].configure_from_module()

        self.models["observatory_model"].update_state(current_tai())

        self.models["observatory_state"] = ObservatoryState()
        self.models["observatory_state"].set(
            self.models["observatory_model"].current_state
        )

        self.models["sky"] = AstronomicalSkyModel(self.models["location"])
        self.models["seeing"] = SeeingModel()
        self.models["cloud"] = CloudModel()
        self.models["downtime"] = DowntimeModel()

        self.driver = SequentialScheduler(
            models=self.models,
            raw_telemetry=self.raw_telemetry,
        )

        self.config = types.SimpleNamespace(
            driver_configuration=dict(
                observing_list=pathlib.Path(__file__)
                .parents[1]
                .joinpath("tests", "data", "test_observing_list.yaml"),
                general_propos=["Test"],
                default_observing_script_name="standard_visit.py",
                default_observing_script_is_standard=True,
                stop_tracking_observing_script_name="stop_tracking.py",
                stop_tracking_observing_script_is_standard=True,
            )
        )

        self.files_to_delete = []
Example #20
0
    async def start_loading(self, fullpath):
        """Start the script process and start a task that will configure
        the script when it is ready.

        Parameters
        ----------
        fullpath : `str`, `bytes` or `os.PathLike`
            Full path to the script.

        Notes
        -----
        If loading is canceled the script process is terminated.
        If loading fails the script is marked as terminated.
        """
        if self.create_process_task is not None:
            raise RuntimeError("Already started loading")
        if self._terminated:
            # this can happen if the user stops a script with stopScript
            # while the script is being added
            return
        initialpath = os.environ["PATH"]
        try:
            scriptdir, scriptname = os.path.split(fullpath)
            os.environ["PATH"] = scriptdir + ":" + initialpath
            # save task so process creation can be cancelled if it hangs
            self.create_process_task = asyncio.create_task(
                asyncio.create_subprocess_exec(scriptname, str(self.index)))
            self.process = await self.create_process_task
            if self._terminated:
                # Terminated before fully loaded
                return
            self.process_task = asyncio.create_task(self.process.wait())
            self.timestamp_process_start = current_tai()
            await self._run_callback()
            # note: process_task may already be done if the script cannot
            # be run, in which case the callback will be called immediately
            self.process_task.add_done_callback(self._cleanup)
        except asyncio.CancelledError:
            self.log.info("Loading cancelled")
            self._terminated = True
            raise
        except Exception:
            self.log.exception("Loading failed")
            self._terminated = True
            raise
        finally:
            os.environ["PATH"] = initialpath
            if not self.timestamp_process_start == 0:
                await self._run_callback()
Example #21
0
    def run(self):
        """Start the script running.

        Raises
        ------
        RuntimeError
            If the script cannot be run, e.g. because:

            - The script has not yet been configured.
            - `run` was already called.
            - The script process is done.
        """
        if not self.runnable:
            raise RuntimeError("Script is not runnable")
        asyncio.create_task(self.remote.cmd_run.set_start(ScriptID=self.index))
        self.timestamp_run_start = current_tai()
Example #22
0
    async def publish_m1m3_topic_samples(self) -> None:
        """Publish m1m3 topic samples."""

        self.log.debug("Publishing m1m3 topic samples.")

        # Load data without blocking the event loop.

        loop = asyncio.get_running_loop()

        m1m3_topic_samples = await loop.run_in_executor(
            None, get_m1m3_topic_samples_data)

        for topic_sample in m1m3_topic_samples:
            if "timestamp" in m1m3_topic_samples[topic_sample]:
                m1m3_topic_samples[topic_sample]["timestamp"] = current_tai()

            # Cleanup sample data in case of schema evolution
            cleanup_topic_attr = []

            for topic_attr in m1m3_topic_samples[topic_sample]:
                if not hasattr(
                        getattr(self.controllers.mtm1m3,
                                f"evt_{topic_sample}").data,
                        topic_attr,
                ):
                    self.log.debug(
                        f"M1M3 topic {topic_sample} does not have attribute {topic_attr}."
                    )
                    cleanup_topic_attr.append(topic_attr)

            if len(cleanup_topic_attr) > 0:
                self.log.warning(
                    f"The following topic attributes are not available in {topic_sample}: "
                    f"{cleanup_topic_attr}. Need to update topic samples.")
            for topic_attr in cleanup_topic_attr:
                self.log.debug(
                    f"Removing topic {topic_attr} from {topic_sample} sample.")
                m1m3_topic_samples[topic_sample].pop(topic_attr)

            try:
                await getattr(self.controllers.mtm1m3,
                              f"evt_{topic_sample}").set_write(
                                  **m1m3_topic_samples[topic_sample])
            except Exception:
                self.log.exception(f"Error publishing topic {topic_sample}")

        self.log.debug("Finished publishing m1m3 topic samples.")
Example #23
0
 async def handle_summary_state(self):
     if self.summary_state == salobj.State.ENABLED:
         axes_to_enable = set((Axis.Elevation, Axis.Azimuth))
         tai = utils.current_tai()
         rot_axis = self.m3_port_rot(tai)[1]
         if rot_axis is not None:
             axes_to_enable.add(rot_axis)
         for axis in Axis:
             self._axis_enabled[axis] = axis in axes_to_enable
     else:
         self.disable_all_drives()
     if self.summary_state in (salobj.State.DISABLED, salobj.State.ENABLED):
         if self._events_and_telemetry_task.done():
             self._events_and_telemetry_task = asyncio.ensure_future(
                 self.events_and_telemetry_loop()
             )
     else:
         self._events_and_telemetry_task.cancel()
Example #24
0
 def disable_all_drives(self):
     """Stop all drives, disable them and put on brakes."""
     self._tracking_enabled = False
     already_stopped = True
     tai = utils.current_tai()
     for axis in Axis:
         actuator = self.actuators[axis]
         if actuator.kind(tai) == actuator.Kind.Stopped:
             self._axis_enabled[axis] = False
         else:
             already_stopped = False
             actuator.stop()
     self._disable_all_drives_task.cancel()
     if not already_stopped:
         self._disable_all_drives_task = asyncio.ensure_future(
             self._finish_disable_all_drives()
         )
     self.update_events()
Example #25
0
    async def report_azimuth_done(self, in_position, motion_state):
        """Wait for azimuth to stop moving and report evt_azMotion.

        Parameters
        ----------
        in_position : `bool`
            Is the axis in position at the end of the move?
        motion_state : `lsst.ts.idl.MotionState`
            Motion state at end of move.
        """
        end_tai = self.azimuth_actuator.path.segments[-1].tai
        duration = end_tai - utils.current_tai()
        if duration > 0:
            await asyncio.sleep(duration)
        await self.evt_azMotion.set_write(
            state=motion_state,
            inPosition=in_position,
        )
    def test_calculate_parallactic_angle(self) -> None:
        # TODO: Implement test (DM-21336)
        radec_icrs = ICRS(Angle(0.0, unit=u.hourangle), Angle(-80.0,
                                                              unit=u.deg))

        location = EarthLocation.from_geodetic(lon=-70.747698 * u.deg,
                                               lat=-30.244728 * u.deg,
                                               height=2663.0 * u.m)

        current_time = astropy_time_from_tai_unix(current_tai())

        current_time.location = location

        par_angle = calculate_parallactic_angle(
            location,
            current_time.sidereal_time("mean"),
            radec_icrs,
        )

        assert par_angle is not None
Example #27
0
    async def setLouvers(self, position: typing.List[float]) -> None:
        """Set the position of the louver with the given louver_id.

        Parameters
        ----------
        position: array of float
            An array with the positions (percentage) to set the louvers to. 0
            means closed, 180 means wide open, -1 means do not move. These
            limits are not checked.
        """
        self.command_time_tai = utils.current_tai()
        pos: float = 0
        for louver_id, pos in enumerate(position):
            if pos >= 0:
                if pos > 0:
                    self.status[louver_id] = LlcMotionState.OPEN.name
                else:
                    self.status[louver_id] = LlcMotionState.CLOSED.name
                self.position_actual[louver_id] = pos
                self.position_commanded[louver_id] = pos
Example #28
0
    async def set_state(
        self,
        state: typing.Union[ScriptState, int, None] = None,
        reason: typing.Optional[str] = None,
        keep_old_reason: bool = False,
        force_output: bool = False,
    ) -> None:
        """Set the script state.

        Parameters
        ----------
        state : `ScriptState` or `int`, optional
            New state, or None if no change
        reason : `str`, optional
            Reason for state change. `None` for no new reason.
        keep_old_reason : `bool`
            If true, keep old reason; append the ``reason`` argument after ";"
            if it is is a non-empty string.
            If false replace with ``reason``, or "" if ``reason`` is `None`.
        force_output : `bool`, optional
            If true the output even if not changed.

        Notes
        -----
        The lastCheckpoint field is set from self.last_checkpoint,
        and the numCheckpoints field is set from self.num_checkpoints.
        """
        if state is not None:
            state = ScriptState(state)
            self.timestamps[state] = utils.current_tai()
        if keep_old_reason and reason is not None:
            sepstr = "; " if self.evt_state.data.reason else ""  # type: ignore
            reason = self.evt_state.data.reason + sepstr + reason  # type: ignore
        await self.evt_state.set_write(  # type: ignore
            state=state,
            reason=reason,
            lastCheckpoint=self.last_checkpoint,
            numCheckpoints=self.num_checkpoints,
            force_output=force_output,
        )
Example #29
0
 async def do_moveAz(self, data):
     self.assert_enabled()
     if not self.azimuth_done_task.done():
         raise salobj.ExpectedError("Azimuth slew not done.")
     self.azimuth_actuator.set_target(
         position=data.position,
         velocity=data.velocity,
         tai=utils.current_tai(),
     )
     await self.evt_azTarget.set_write(
         position=data.position, velocity=data.velocity, force_output=True
     )
     await self.evt_azMotion.set_write(
         state=MotionState.MOVING,
         inPosition=False,
     )
     end_motion_state = (
         MotionState.CRAWLING if data.velocity != 0 else MotionState.STOPPED
     )
     self.azimuth_done_task = asyncio.create_task(
         self.report_azimuth_done(in_position=True, motion_state=end_motion_state)
     )
Example #30
0
    async def _configure(self):
        """Configure the script.

        If configuration fails or is cancelled then terminate the script.

        Raises
        ------
        RuntimeError
            If the script state is not ScriptState.UNCONFIGURED
        """
        try:
            if self.script_state != ScriptState.UNCONFIGURED:
                raise RuntimeError(
                    f"Cannot configure script {self.index} "
                    f"because it is in state {self.script_state} "
                    f"instead of {ScriptState.UNCONFIGURED!r}")

            await self.remote.cmd_configure.set_start(
                ScriptID=self.index,
                config=self.config,
                logLevel=self.log_level,
                pauseCheckpoint=self.pause_checkpoint,
                stopCheckpoint=self.stop_checkpoint,
                timeout=_CONFIGURE_TIMEOUT,
            )
        except asyncio.CancelledError:
            self.log.info("Configuration cancelled")
            asyncio.create_task(self.terminate())
            raise
        except Exception:
            # terminate the script but first let the configure_task fail
            self.log.exception("Configuration failed")
            asyncio.create_task(self.terminate())
            raise
        finally:
            self.timestamp_configure_end = current_tai()