def __init__( self, config_dir=None, initial_state=salobj.State.STANDBY, simulation_mode=0, mock_port=None, ): self.reader = None self.writer = None self.move_code = 0 self.mock_ctrl = None # mock controller, or None of not constructed self.status_interval = 0.2 # delay between short status commands (sec) # Amount to add to "tolerance" reported by the low-level controller # to set az_tolarance (deg). self.az_tolerance_margin = 0.5 # Tolerance for "in position" (deg). # Set the initial value here, then update from the # "Tolerance" reported in long status. self.az_tolerance = 1.5 self.homing = False # Task for sleeping in the status loop; cancel this to trigger # an immediate status update. Warning: do not cancel status_task # because that may be waiting for TCP/IP communication. self.status_sleep_task = salobj.make_done_future() # Task for the status loop. To trigger new status cancel # status_sleep_task, not status_task. self.status_task = salobj.make_done_future() # Task that waits while connecting to the TCP/IP controller. self.connect_task = salobj.make_done_future() # Task that waits while shutter doors move self.shutter_task = salobj.make_done_future() # The conditions that self.shutter_task is waiting for. # Must be one of: ShutterDoorState.OPENED, # ShutterDoorState.CLOSED or None (for don't care) self.desired_main_shutter_state = None self.desired_dropout_shutter_state = None self.cmd_lock = asyncio.Lock() self.config = None self.mock_port = mock_port self.status_event = asyncio.Event() super().__init__( "ATDome", index=0, config_schema=CONFIG_SCHEMA, config_dir=config_dir, initial_state=initial_state, simulation_mode=simulation_mode, ) # Homing can be slow; rather than queue up these commands # (which is unlikely to be what the user wants) allow multiple # command to run, so the CSC can reject the overlapping commands. self.cmd_homeAzimuth.allow_multiple_callbacks = True # Set but don't put azimuthState.homed=False: only homed is maintained # locally; we have no idea of the rest of the state. self.evt_azimuthState.set(homed=False)
def __init__( self, config_dir=None, initial_state=salobj.State.STANDBY, simulation_mode=0, mock_port=None, ): schema_path = ( pathlib.Path(__file__) .resolve() .parents[4] .joinpath("schema", "ATDome.yaml") ) self.reader = None self.writer = None self.move_code = 0 self.mock_ctrl = None # mock controller, or None of not constructed self.status_interval = 0.2 # delay between short status commands (sec) # Amount to add to "tolerance" reported by the low-level controller # to set az_tolarance (deg). self.az_tolerance_margin = 0.5 # Tolerance for "in position" (deg). # Set the initial value here, then update from the # "Tolerance" reported in long status. self.az_tolerance = 1.5 # Task for sleeping in the status loop; cancel this to trigger # an immediate status update. Warning: do not cancel status_task # because that may be waiting for TCP/IP communication. self.status_sleep_task = salobj.make_done_future() # Task for the status loop. To trigger new status cancel # status_sleep_task, not status_task. self.status_task = salobj.make_done_future() # Task that waits while connecting to the TCP/IP controller. self.connect_task = salobj.make_done_future() # Task that waits while shutter doors move self.shutter_task = salobj.make_done_future() # The conditions that self.shutter_task is waiting for. # Must be one of: ShutterDoorState.OPENED, # ShutterDoorState.CLOSED or None (for don't care) self.desired_main_shutter_state = None self.desired_dropout_shutter_state = None self.cmd_lock = asyncio.Lock() self.config = None self.mock_port = mock_port super().__init__( "ATDome", index=0, schema_path=schema_path, config_dir=config_dir, initial_state=initial_state, simulation_mode=simulation_mode, )
def __init__(self, name): self.name = name self.callback = None self.auto_acknowledge_delay = 0 self.auto_unacknowledge_delay = 0 self.escalate_to = "" self.escalate_delay = 0 self.auto_acknowledge_task = salobj.make_done_future() self.auto_unacknowledge_task = salobj.make_done_future() self.escalate_task = salobj.make_done_future() self.unmute_task = salobj.make_done_future() self.reset()
def __init__(self, config): super().__init__( config=config, name=f"test.ConfiguredSeverities.{config.name}", remote_info_list=[], ) self.run_timer = salobj.make_done_future()
def __init__(self, title, cache, SAMPLE_TIME, toolBar, channels=[]): super().__init__(title) self.setObjectName(title) self.SAMPLE_TIME = SAMPLE_TIME self.toolBar = toolBar self.chart = AbstractChart() self.updateTask = make_done_future() self.update_after = 0 self.cache = cache self.chartView = VMSChartView(self.chart, QtCharts.QLineSeries) self.chartView.updateMaxSensor(self.cache.sensors()) self.chartView.axisChanged.connect(self.axisChanged) self.chartView.unitChanged.connect(self.unitChanged) for channel in channels: self.chartView.addSerie(str(channel[0]) + " " + channel[1]) self.coefficient = 1 self.unit = units[0] self.setupAxes = True self._setupAxes() self.setWidget(self.chartView)
def __init__( self, kafka_config, log_level=logging.INFO, ): self.kafka_config = kafka_config self.log = logging.getLogger() if not self.log.hasHandlers(): self.log.addHandler(logging.StreamHandler()) self.log.setLevel(log_level) semaphore_filename = "SALKAFKA_PRODUCER_RUNNING" suffix = os.environ.get("SALKAFKA_SEMAPHORE_SUFFIX") if suffix is not None: semaphore_filename += f"_{suffix.upper()}" self.semaphore_file = pathlib.Path("/tmp", semaphore_filename) if self.semaphore_file.exists(): self.semaphore_file.unlink() # A collection of ComponentProducers. # Set by run_producers but not run_distributed_producer. self.producers = [] # A collection of producer subprocess tasks. # Set by run_distributed_producer but not run_producers. self.producer_tasks = [] self.start_task = asyncio.Future() # Internal task to monitor startup of producer(s) # This is different than start_task in order to avoid a race condition # when code (typically a unit test) creates a ComponentProducerSet # and immediately starts waiting for start_task to be done. self._interruptable_start_task = salobj.make_done_future() self._run_producer_subprocess_task = salobj.make_done_future() # A task that ends when the service is interrupted. self._wait_forever_task = asyncio.Future()
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()
def __init__( self, index, initial_state=salobj.State.STANDBY, simulation_mode=0, ): """ Initialize DSM CSC. Parameters ---------- index : `int` Index for the DSM. This enables the control of multiple DSMs. initial_state : `lsst.ts.salobj.State`, optional State to place CSC in after initialization. simulation_mode : `int`, optional Flag to determine mode of operation. """ self.telemetry_directory = None self.telemetry_loop_task = salobj.make_done_future() self.telemetry_watcher = aionotify.Watcher() self.simulated_telemetry_ui_config_written = False self.simulated_telemetry_loop_task = salobj.make_done_future() self.simulation_loop_time = None super().__init__( "DSM", index, initial_state=initial_state, simulation_mode=simulation_mode, ) ch = logging.StreamHandler() self.log.addHandler(ch) self.finish_csc_setup()
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=initial_state) print(f"Creating for worker for: {name}") print(f"Running {salobj.__version__}") self.atcam = salobj.Remote(domain=self.domain, name="ATCamera", index=0) self.atcam.evt_startIntegration.callback = self.startIntegration_callback self.atcam.evt_endOfImageTelemetry.callback = self.endOfImageTelemetry_callback self.endOfImage_timeout_task = salobj.make_done_future()
def __init__(self, items, maxItems=50 * 30, updateInterval=0.1): super().__init__() self.maxItems = maxItems self.timeAxis = None self._next_update = 0 self.updateInterval = updateInterval self.updateTask = make_done_future() self._caches = [] for axis in items.items(): data = [("timestamp", "f8")] for d in axis[1]: if d is None: self._caches.append(TimeCache(maxItems, data)) data = [("timestamp", "f8")] else: data.append((d, "f8")) self._addSerie(d, axis[0]) self._caches.append(TimeCache(maxItems, data)) # Caveat emptor, the order here is important. Hard to find, but the order in # which chart, axis and series are constructed and attached should always be: # - construct Axis, Chart, Serie # - addAxis to chart # - attach series to axis # Changing the order will result in undetermined behaviour, most # likely the axis or even graph not shown. It's irrelevant when you # fill series with data. See QtChart::createDefaultAxes in QtChart # source code for details. self.timeAxis = QtCharts.QDateTimeAxis() self.timeAxis.setReverse(True) self.timeAxis.setTickCount(5) self.timeAxis.setFormat("h:mm:ss.zzz") self.timeAxis.setTitleText("Time (TAI)") self.timeAxis.setGridLineVisible(True) self.addAxis(self.timeAxis, Qt.AlignBottom) for serie in self.series(): serie.attachAxis(self.timeAxis)
def __init__( self, simulation_mode=0, initial_state: salobj.State = salobj.State.STANDBY, config_dir=None, ): super().__init__( name="CBP", index=0, config_dir=config_dir, initial_state=initial_state, simulation_mode=simulation_mode, config_schema=CONFIG_SCHEMA, ) self.component = component.CBPComponent(self) self.simulator = None self.telemetry_task = salobj.make_done_future() self.telemetry_interval = 0.5 self.in_position_timeout = 20 self.log.info("CBP CSC initialized")
def __init__( self, index, simulation_mode=0, initial_state=salobj.State.STANDBY, config_dir=None, settings_to_apply="", ): super().__init__( name="PMD", index=index, config_dir=config_dir, initial_state=initial_state, simulation_mode=simulation_mode, config_schema=CONFIG_SCHEMA, settings_to_apply=settings_to_apply, ) self.telemetry_task = salobj.make_done_future() self.telemetry_interval = 1 self.index = index self.component = None
def __init__(self, config_dir=None, initial_state=salobj.State.OFFLINE, simulation_mode=0): self.server = None self.mock_ctrl = None # Set this to 2 when trackStart is called, then decrement # when telemetry is received. If > 0 or enabled_substate is # SLEWING_OR_TRACKING then allow the track command. # This solves the problem of allowing the track command # immediately after the trackStart, before telemetry is received. self._tracking_started_telemetry_counter = 0 # The sequential number of times the camera cable wrap following error # has been too large. self._num_ccw_following_errors = 0 self._check_ccw_following_error_task = salobj.make_done_future() self._reported_ccw_following_error_issue = False self._faulting = False self._prev_flags_tracking_success = False self._prev_flags_tracking_lost = False super().__init__( name="MTRotator", index=0, port=constants.TELEMETRY_PORT, sync_pattern=constants.ROTATOR_SYNC_PATTERN, CommandCode=enums.CommandCode, ConfigClass=structs.Config, TelemetryClass=structs.Telemetry, config_dir=config_dir, config_schema=CONFIG_SCHEMA, initial_state=initial_state, simulation_mode=simulation_mode, ) self.mtmount_remote = salobj.Remote(domain=self.domain, name="MTMount")
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)
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())
def __init__( self, port, door_time=1, az_vel=6, home_az=10, home_az_overshoot=1, home_az_vel=1, ): # If port == 0 then this will be updated to the actual port # in `start`, right after the TCP/IP server is created. self.port = port self.door_time = door_time self.az_vel = az_vel self.high_speed = 5 self.coast = 0.5 self.tolerance = 1.0 self.home_az = home_az self.home_az_overshoot = home_az_overshoot self.home_az_vel = home_az_vel self.encoder_counts_per_360 = 4018143232 self.az_actuator = simactuators.CircularPointToPointActuator( speed=az_vel) self.az_move_timeout = 120 self.watchdog_reset_time = 600 self.dropout_timer = 5 self.reverse_delay = 4 self.main_door_encoder_closed = 118449181478 self.main_door_encoder_opened = 8287616388 self.dropout_door_encoder_closed = 5669776578 self.dropout_door_encoder_opened = 5710996184 self.door_move_timeout = 360 self.door_actuators = { enum: simactuators.PointToPointActuator( min_position=0, max_position=100, start_position=0, speed=100 / door_time, ) for enum in Door } self._homing_task = salobj.make_done_future() self.rain_sensor_enabled = True self.rain_detected = False self.cloud_sensor_enabled = True self.clouds_detected = False self.scb_link_ok = True self.auto_shutdown_enabled = False self.estop_active = False # Name of a command to report as failed once, the next time it is seen, # or None if no failures. Used to test CSC handling of failed commands. self.fail_command = None self.last_rot_right = None self.log = logging.getLogger("MockDomeController") self.server = None # Dict of command: (has_argument, function). # The function is called with: # * No arguments, if `has_argument` False. # * The argument as a string, if `has_argument` is True. self.dispatch_dict = { "?": (False, self.do_short_status), "+": (False, self.do_full_status), "ST": (False, self.do_stop), "CL": (False, functools.partial(self.do_close_doors, Door.Main)), "OP": (False, functools.partial(self.do_open_doors, Door.Main)), "UP": (False, functools.partial(self.do_close_doors, Door.Dropout)), "DN": (False, functools.partial(self.do_open_doors, Door.Dropout)), "SC": ( False, functools.partial(self.do_close_doors, Door.Main | Door.Dropout), ), "SO": ( False, functools.partial(self.do_open_doors, Door.Main | Door.Dropout), ), "HM": (False, self.do_home), "MV": (True, self.do_set_cmd_az), }
def __init__( self, index, config_dir=None, initial_state=salobj.State.OFFLINE, settings_to_apply="", simulation_mode=0, ): index = enums.SalIndex(index) controller_constants = constants.IndexControllerConstants[index] # The maximum position limits configured in the low-level controller. # The limits specified by the configureLimits command must be # within these limits. self.max_pos_limits = constants.MAX_POSITION_LIMITS[index] # Current position limits; initialize to max limits, # but update from configuration reported by the low-level controller. self.current_pos_limits = copy.copy(self.max_pos_limits) # The move command that is currently running, or None. self.move_command = None # Task for the current move, if one is being commanded. self.move_task = salobj.make_done_future() # Record missing compensation inputs we have warned about, # to avoid duplicate warnings. self.missing_inputs_str = "" # Interval between compensation updates. # Set in `configure`, but we need an initial value. self.compensation_interval = 0.2 # Maximum time for the longest possible move (sec) with some buffer. # Set in `config_callback` but we need an initial value. # 61.74 is the value for the mock controller as of 2021-04-15. self.max_move_duration = 65 # The part of the compensation loop that is always safe to cancel. self.compensation_wait_task = salobj.make_done_future() # The whole compensation loop; to safely cancel this use:: # # async with self.write_lock: # self.compensation_loop_task.cancel() self.compensation_loop_task = salobj.make_done_future() # Event set when a telemetry message is received from # the low-level controller, after it has been parsed. self.telemetry_event = asyncio.Event() # Count of number of telemetry samples read # since issuing a low-level command. # Maxes out at MAX_N_TELEMETRY. self.n_telemetry = 0 structs.Config.FRAME_ID = controller_constants.config_frame_id structs.Telemetry.FRAME_ID = controller_constants.telemetry_frame_id super().__init__( name="MTHexapod", index=index, port=controller_constants.port, sync_pattern=controller_constants.sync_pattern, CommandCode=enums.CommandCode, ConfigClass=structs.Config, TelemetryClass=structs.Telemetry, config_schema=CONFIG_SCHEMA, config_dir=config_dir, initial_state=initial_state, settings_to_apply=settings_to_apply, simulation_mode=simulation_mode, ) # TODO DM-28005: add a suitable Remote from which to get temperature; # perhaps something like: # self.eas = salobj.Remote(domain=self.domain, name="EAS", include=[?]) self.mtmount = salobj.Remote( domain=self.domain, name="MTMount", include=["target"] ) self.mtrotator = salobj.Remote( domain=self.domain, name="MTRotator", include=["target"] )
def __init__( self, log, host=hexrotcomm.LOCAL_HOST, command_port=constants.TELEMETRY_PORT + 1, telemetry_port=constants.TELEMETRY_PORT, initial_state=ControllerState.OFFLINE, ): self.encoder_resolution = 200_000 # counts/deg; arbitrary # Amplitude of jitter in measured position (deg), # to simulate encoder jitter. self.position_jitter = 0.000003 self.tracking_timed_out = False config = structs.Config() config.velocity_limit = 3 # deg/sec config.accel_limit = 1 # deg/sec^2 config.pos_error_threshold = 0.1 # deg config.lower_pos_limit = -90 # deg config.upper_pos_limit = 90 # deg config.following_error_threshold = 0.1 # deg config.track_success_pos_threshold = 0.01 # deg config.tracking_lost_timeout = 5 # sec telemetry = structs.Telemetry() telemetry.set_pos = math.nan self.tracking_timer_task = salobj.make_done_future() self.rotator = simactuators.TrackingActuator( min_position=config.lower_pos_limit, max_position=config.upper_pos_limit, max_velocity=config.velocity_limit, max_acceleration=config.accel_limit, dtmax_track=0.25, ) # Set True when the first TRACK_VEL_CMD command is received # and False when not tracking. Used to delay setting # telemetry.flags_slew_complete to 1 until we know # where we are going. self.track_vel_cmd_seen = False # Dict of command key: command extra_commands = { ( enums.CommandCode.SET_ENABLED_SUBSTATE, enums.SetEnabledSubstateParam.MOVE_POINT_TO_POINT, ): self.do_move_point_to_point, ( enums.CommandCode.SET_ENABLED_SUBSTATE, enums.SetEnabledSubstateParam.TRACK, ): self.do_track, ( enums.CommandCode.SET_ENABLED_SUBSTATE, enums.SetEnabledSubstateParam.STOP, ): self.do_stop, ( enums.CommandCode.SET_ENABLED_SUBSTATE, enums.SetEnabledSubstateParam.CONSTANT_VELOCITY, ): self.do_constant_velocity, enums.CommandCode.FAULT: self.do_fault, enums.CommandCode.POSITION_SET: self.do_position_set, enums.CommandCode.SET_CONSTANT_VEL: self.do_set_constant_vel, enums.CommandCode.CONFIG_VEL: self.do_config_vel, enums.CommandCode.CONFIG_ACCEL: self.do_config_accel, enums.CommandCode.TRACK_VEL_CMD: self.do_track_vel_cmd, } super().__init__( log=log, CommandCode=enums.CommandCode, extra_commands=extra_commands, config=config, telemetry=telemetry, host=host, command_port=command_port, telemetry_port=telemetry_port, initial_state=initial_state, )