Beispiel #1
0
    def task2(self, session, params=None):
        """
        TASK (non-blocking)

        In a non-blocking implementation of a Task or Process
        function, we may:

        - use twisted functions freely, as we are running in the
          reactor thread.
        - make blocking i/o requests by wrapping them in
          deferToThread.

        We may not:

        - cause the function to block or sleep using non-twisted
          methods.
        
        Within twisted, asynchronous routines that perform blocking
        operations (without blocking the reactor thread) will return a
        Deferred object, immediately, instead of the result of the
        request you have made.  To suspend the function until the
        actual result is ready, you should:
        - decorate the function with @inlineCallbacks.
        - use the idiom "x = yield Function(...)" idiom.

        This latter idiom is used on the dsleep function; not because
        we care what the function returned but because we definitely
        want to wait until that function has completed before
        proceeding with the next step in our operation.
        """
        for step in range(5):
            session.add_message('task2-nonblocking step %i' % step)
            yield dsleep(1)

        return True, 'task-2 nonblocking complete.'
Beispiel #2
0
    def _run_script(self, script, args, log, session):
        """
        Runs a pysmurf control script. Can only run from the reactor.

        Arguments
        ----------
        script: string
            path to the script you wish to run
        args: list, optional
            List of command line arguments to pass to the script.
            Defaults to [].
        log: string/bool, optional
            Determines if and how the process's stdout should be logged.
            You can pass the path to a logfile, True to use the agent's log,
            or False to not log at all.
        """

        with self.protocol_lock.acquire_timeout(0, job=script) as acquired:
            if not acquired:
                return False, "The requested script cannot be run because " \
                              "script {} is already running".format(self.protocol_lock.job)

            self.current_session = session
            try:
                # IO  is not really safe from the reactor thread, so we possibly
                # need to find another way to do this if people use it and it
                # causes problems...
                logger = None
                if isinstance(log, str):
                    self.log.info("Logging output to file {}".format(log))
                    log_file = yield threads.deferToThread(open, log, 'a')
                    logger = Logger(
                        observer=FileLogObserver(log_file, log_formatter))
                elif log:
                    # If log==True, use agent's logger
                    logger = self.log

                self.prot = PysmurfScriptProtocol(script, log=logger)
                self.prot.deferred = Deferred()
                python_exec = sys.executable

                cmd = [python_exec, '-u', script] + list(map(str, args))

                self.log.info("{exec}, {cmd}", exec=python_exec, cmd=cmd)

                reactor.spawnProcess(self.prot,
                                     python_exec,
                                     cmd,
                                     env=os.environ)

                rc = yield self.prot.deferred

                return (rc == 0
                        ), "Script has finished with exit code {}".format(rc)

            finally:
                # Sleep to allow any remaining messages to be put into the
                # session var
                yield dsleep(1.0)
                self.current_session = None
Beispiel #3
0
 def dsleep(self):
     """
     Sleeps in a non-blocking way by returning the deferred created by
     twisted's sleep method.
     """
     now = time.time()
     if now < self.next_sample:
         yield dsleep(self.next_sample - now)
     self._set_next_sample()
Beispiel #4
0
    def start_acq(self, session, params=None):
        """start_acq(params=None)

        OCS Process for fetching values from the M1000 via SNMP.

        The session.data object stores each unique OID with its latest status,
        decoded value, and the last time the value was retrived. This will look like
        the example here::

            >>> session.data
            {"mbgLtNgRefclockLeapSecondDate_1":
                {"status": "not announced",
                 "lastGet":1598626144.5365012},
             "mbgLtNgPtpPortState_1":
                {"status": 3,
                 "description": "disabled",
                 "lastGet": 1598543397.689727},
             "mbgLtNgNtpCurrentState_0":
                {"status": 1,
                 "description": "not synchronized",
                 "lastGet": 1598543363.289597},
             "mbgLtNgRefclockState_1":
                {"status": 2,
                 "description": "not synchronized",
                 "lastGet": 1598543359.6326838},
             "mbgLtNgSysPsStatus_1":
                {"status": 2,
                 "description": "up",
                 "lastGet": 1598543359.6326838},
             "mbgLtNgSysPsStatus_2":
                {"status": 2,
                 "description": "up",
                 "lastGet": 1598543359.6326838},
             "mbgLtNgEthPortLinkState_1":
                {"status": 1,
                 "description": "up",
                 "lastGet": 1598543359.6326838}}

        Note that session.data is populated within the self.meinberg.run_snmp_get() call.

        """
        if params is None:
            params = {}

        self.is_streaming = True

        while self.is_streaming:
            yield self.meinberg.run_snmp_get(session)
            self.log.debug("{data}", data=session.data)
            yield dsleep(0.1)

        return True, "Finished Recording"
Beispiel #5
0
    def stop_and_clear(self, session, params=None):
        """TASK stop_and_clear

        Changes the azimuth and elevation modes to Stop and clears
        points uploaded to the stack.

        """
        ok, msg = self.try_set_job('control')
        if not ok:
            self.set_job_done('control')
            yield dsleep(0.1)
            self.try_set_job('control')
        self.log.info('try_set_job ok')
        yield self.acu.stop()
        self.log.info('Stop called')
        yield dsleep(5)
        yield self.acu.http.Command('DataSets.CmdTimePositionTransfer',
                                    'Clear Stack')
        yield dsleep(0.1)
        self.log.info('Cleared stack.')
        self.set_job_done('control')
        return True, 'Job completed'
Beispiel #6
0
    def set_boresight(self, session, params=None):
        """TASK set_boresight

        Moves the telescope to a particular third-axis angle.

        Params:
            b (float): destination angle for boresight rotation
        """
        ok, msg = self.try_set_job('control')
        if not ok:
            return ok, msg
        bs_destination = params.get('b')
        yield self.acu.stop()
        yield dsleep(5)
        yield self.acu.go_3rd_axis(bs_destination)
        current_position = self.data['status']['summary']\
            ['Boresight_current_position']
        while current_position != bs_destination:
            yield dsleep(1)
            current_position = self.data['status']['summary']\
                ['Boresight_current_position']
        yield self.acu.stop()
        self.set_job_done('control')
        return True, 'Moved to new 3rd axis position'
Beispiel #7
0
    def delay_task(self, session, params):
        """delay_task(delay=5, succeed=True)

        **Task** - Sleep (delay) for the requested number of seconds.

        This can run simultaneously with the acq Process.  This Task
        should run in the reactor thread.

        Args:
            delay (float, optional): Time to wait before returning, in seconds.
                Defaults to 5.
            succeed (bool, optional): Whether to return success or not.
                Defaults to True.

        Notes:
            The session data will be updated with the requested delay as
            well as the time elapsed so far, for example::

                >>> response.session['data']
                {'requested_delay': 5.,
                 'delay_so_far': 1.2}

        """
        delay = params['delay']
        succeed = params['succeed'] is True

        session.data = {'requested_delay': delay, 'delay_so_far': 0}
        session.set_status('running')
        t0 = time.time()
        while True:
            session.data['delay_so_far'] = time.time() - t0
            sleep_time = min(0.5, delay - session.data['delay_so_far'])
            if sleep_time < 0:
                break
            yield dsleep(sleep_time)
        return succeed, 'Exited after %.1f seconds' % session.data[
            'delay_so_far']
Beispiel #8
0
    def acq(self, session, params=None):
        """acq()

        **Process** - Fetch values from the M1000 via SNMP.

        Notes:
            The session.data object stores each unique OID with its latest status,
            decoded value, and the last time the value was retrived. This will look like
            the example here::

                >>> response.session['data']
                {"mbgLtNgRefclockLeapSecondDate_1":
                    {"status": "not announced",
                     "lastGet":1598626144.5365012},
                 "mbgLtNgPtpPortState_1":
                    {"status": 3,
                     "description": "disabled",
                     "lastGet": 1598543397.689727},
                 "mbgLtNgNtpCurrentState_0":
                    {"status": 1,
                     "description": "not synchronized",
                     "lastGet": 1598543363.289597},
                 "mbgLtNgRefclockState_1":
                    {"status": 2,
                     "description": "not synchronized",
                     "lastGet": 1598543359.6326838},
                 "mbgLtNgSysPsStatus_1":
                    {"status": 2,
                     "description": "up",
                     "lastGet": 1598543359.6326838},
                 "mbgLtNgSysPsStatus_2":
                    {"status": 2,
                     "description": "up",
                     "lastGet": 1598543359.6326838},
                 "mbgLtNgEthPortLinkState_1":
                    {"status": 1,
                     "description": "up",
                     "lastGet": 1598543359.6326838}
                 "m1000_connection":
                    {"last_attempt": 1598543359.6326838,
                     "connected": True}}

            Note that session.data is populated within the :func:`MeinbergSNMP.run_snmp_get` call.

        """
        if params is None:
            params = {}

        # Make an initial attempt at connection.
        # Allows us to fail early if misconfigured.
        yield self.meinberg.run_snmp_get(session)
        if not self.meinberg.oid_cache['m1000_connection'].get('connected', False):
            self.log.error('No initial SNMP response.')
            self.log.error('Either there is a network connection issue, ' +
                           'or maybe you are using the wrong SNMP ' +
                           'version. Either way, we are exiting.')

            reactor.callFromThread(reactor.stop)
            return False, 'acq process failed - No connection to M1000'

        self.is_streaming = True

        while self.is_streaming:
            yield self.meinberg.run_snmp_get(session)
            self.log.debug("{data}", data=session.data)
            yield dsleep(1)

        return True, "Finished Recording"
Beispiel #9
0
    def main(self, session: ocs_agent.OpSession, params):
        """main(test_mode=False)

        **Process** - Main run process for the Registry agent. This will loop
        and keep track of which agents have expired. It will keep track of
        current active agents in the session.data variable so it can be seen by
        clients.

        Parameters:
            test_mode (bool, optional): Run the main Process loop only once.
                This is meant only for testing. Default is False.

        Notes:
            The session data object for this process will be a dictionary containing
            the encoded RegisteredAgent objects for each agent observed during the
            lifetime of the Registry. For instance, this might look like::

                >>> response.session['data']
                {'observatory.aggregator':
                    {'expired': False,
                     'last_updated': 1583179794.5175,
                     'time_expired': None},
                 'observatory.faker1':
                    {'expired': False,
                     'last_updated': 1583179795.072248,
                     'time_expired': None},
                 'observatory.faker2':
                    {'expired': True,
                     'last_updated': 1583179777.0211036,
                     'time_expired': 1583179795.3862052}}

        """

        session.set_status('starting')
        self._run = True

        session.set_status('running')
        while self._run:
            yield dsleep(1)

            for k, agent in self.registered_agents.items():
                if time.time() - agent.last_updated > self.agent_timeout:
                    agent.expire()

            session.data = {
                k: agent.encoded()
                for k, agent in self.registered_agents.items()
            }

            for addr, agent in self.registered_agents.items():
                msg = {
                    'block_name': addr,
                    'timestamp': time.time(),
                    'data': {}
                }
                for op_name, op_code in agent.op_codes.items():
                    field = f'{addr}_{op_name}'
                    field = field.replace('.', '_')
                    field = field.replace('-', '_')
                    field = Feed.enforce_field_name_rules(field)
                    try:
                        Feed.verify_data_field_string(field)
                    except ValueError as e:
                        self.log.warn(f"Improper field name: {field}\n{e}")
                        continue
                    msg['data'][field] = op_code
                if msg['data']:
                    self.agent.publish_to_feed('agent_operations', msg)

            if params['test_mode']:
                break

        return True, "Stopped registry main process"
Beispiel #10
0
    def generate_scan(self, session, params=None):
        """
        Scan generator, currently only works for constant-velocity az scans
        with fixed elevation.

        Args:
            scantype (str): type of scan you are generating. For dev, preset to
                'linear'.
            stop_iter (float): how many times the generator should generate a
                new set of points before forced to stop
            az_endpoint1 (float): first endpoint of a linear azimuth scan
            az_endpoint2 (float): second endpoint of a linear azimuth scan
            az_speed (float): azimuth speed for constant-velocity scan
            acc (float): turnaround acceleration for a constant-velocity scan
            el_endpoint1 (float): first endpoint of elevation motion
            el_endpoint2 (float): second endpoint of elevation motion. For dev,
                currently both el endpoints should be equal
            el_speed (float): speed of motion for a scan with changing
                elevation. For dev, currently set to 0.0

        """
        ok, msg = self.try_set_job('control')
        if not ok:
            return ok, msg
        self.log.info('try_set_job ok')
        #        scantype = params.get('scantype')
        scantype = 'linear'
        stop_iter = params.get('stop_iter')
        az_endpoint1 = params.get('az_endpoint1')
        az_endpoint2 = params.get('az_endpoint2')
        az_speed = params.get('az_speed')
        acc = params.get('acc')
        el_endpoint1 = params.get('el_endpoint1')
        el_endpoint2 = params.get('el_endpoint2')
        el_speed = params.get('el_speed')

        self.log.info('scantype is ' + str(scantype))

        yield self.acu.stop()
        if scantype != 'linear':
            self.log.warn('Scan type not supported')
            return False
        g = sh.generate(stop_iter, az_endpoint1, az_endpoint2, az_speed, acc,
                        el_endpoint1, el_endpoint2, el_speed)
        self.acu.mode('ProgramTrack')
        while True:
            lines = next(g)
            current_lines = lines
            group_size = 250
            while len(current_lines):
                upload_lines = current_lines[:group_size]
                text = ''.join(upload_lines)
                current_lines = current_lines[group_size:]
                free_positions = self.data['status']['summary']\
                    ['Qty_of_free_program_track_stack_positions']
                while free_positions < 5099:
                    yield dsleep(0.1)
                    free_positions = self.data['status']['summary']\
                        ['Qty_of_free_program_track_stack_positions']
                yield self.acu.http.UploadPtStack(text)
        yield self.acu.stop()
        self.set_job_done('control')
        return True, 'Track generation ended cleanly'
Beispiel #11
0
    def run_specified_scan(self, session, params=None):
        """TASK run_specifid_scan

        Upload and execute a scan pattern. The pattern may be specified by a
        numpy file, parameters for a linear scan in one direction, or a linear
        scan with a turnaround.

        Params:
            scantype (str): the type of scan information you are uploading.
                            Options are 'from_file', 'linear_1dir', or
                            'linear_turnaround'.
        Optional params:
            filename (str): full path to desired numpy file. File contains an
                            array of three lists ([list(times), list(azimuths),
                            list(elevations)]). Times begin from 0.0. Applies
                            to scantype 'from_file'.
            azpts (tuple): spatial endpoints of the azimuth scan. Applies to
                           scantype 'linear_1dir' (2 values) and
                           'linear_turnaround' (3 values).
            el (float): elevation for a linear velocity azimuth scan. Applies
                        to scantype 'linear_1dir' and 'linear_turnaround'.
            azvel (float): velocity of the azimuth axis in a linear velocity
                           azimuth scan. Applies to scantype 'linear_1dir' and
                           'linear_turnaround'.
            acc (float): acceleration of the turnaround for a linear velocity
                         scan with a turnaround. Applies to scantype
                         'linear_turnaround'.
            ntimes (int): number of times the platform traverses between
                          azimuth endpoints for a 'linear_turnaround' scan.
        """
        ok, msg = self.try_set_job('control')
        if not ok:
            return ok, msg
        self.log.info('try_set_job ok')
        scantype = params.get('scantype')
        if scantype == 'from_file':
            filename = params.get('filename')
            times, azs, els, vas, ves, azflags, elflags =\
                sh.from_file(filename)
        elif scantype == 'linear_1dir':
            azpts = params.get('azpts')
            el = params.get('el')
            azvel = params.get('azvel')
            total_time = (azpts[1] - azpts[0]) / azvel
            azs = np.linspace(azpts[0], azpts[1], total_time * 10)
            els = np.linspace(el, el, total_time * 10)
            times = np.linspace(0.0, total_time, total_time * 10)
        elif scantype == 'linear_turnaround_sameends':
            # from parameters, generate the full set of scan points
            self.log.info('scantype is' + str(scantype))
            azpts = params.get('azpts')
            el = params.get('el')
            azvel = params.get('azvel')
            acc = params.get('acc')
            ntimes = params.get('ntimes')
            times, azs, els, vas, ves, azflags, elflags =\
                sh.linear_turnaround_scanpoints(azpts, el, azvel, acc, ntimes)

        # Switch to Stop mode and clear the stack
        yield self.acu.stop()
        self.log.info('Stop called')
        yield dsleep(5)
        yield self.acu.http.Command('DataSets.CmdTimePositionTransfer',
                                    'Clear Stack')
        yield dsleep(0.1)
        self.log.info('Cleared stack.')

        # Move to the starting position for the scan and then switch to Stop
        # mode
        start_az = azs[0]
        start_el = els[0]

        upload_publish_dict = {
            'Start_Azimuth': start_az,
            'Start_Elevation': start_el,
            'Start_Boresight': 0,
            'Upload_Type': 2,
            'Preset_Azimuth': 0,
            'Preset_Elevation': 0,
            'Upload_Lines': []
        }

        # Follow the scan in ProgramTrack mode, then switch to Stop mode
        if scantype == 'linear_turnaround_sameends':
            all_lines = sh.write_lines(times, azs, els, vas, ves, azflags,
                                       elflags)
        elif scantype == 'from_file':
            all_lines = sh.write_lines(times, azs, els, vas, ves, azflags,
                                       elflags)
        # Other scan types not yet implemented, so break
        else:
            return False, 'Not enough information to scan'
        self.log.info('all_lines generated')
        yield self.acu.mode('ProgramTrack')
        self.log.info('mode is now ProgramTrack')
        group_size = 120
        while len(all_lines):
            upload_lines = all_lines[:group_size]
            text = ''.join(upload_lines)
            all_lines = all_lines[group_size:]
            free_positions = self.data['status']['summary']\
                ['Qty_of_free_program_track_stack_positions']
            while free_positions < 9899:
                free_positions = self.data['status']['summary']\
                    ['Qty_of_free_program_track_stack_positions']
                yield dsleep(0.1)
            yield self.acu.http.UploadPtStack(text)
            upload_publish_dict['Upload_Lines'] = upload_lines
            acu_upload = {
                'timestamp': self.data['broadcast']['Time'],
                'block_name': 'ACU_upload',
                'data': upload_publish_dict
            }
            self.agent.publish_to_feed('acu_upload', acu_upload)
            self.log.info('Uploaded a group')
        self.log.info('No more lines to upload')
        current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4)
        current_el = round(self.data['broadcast']['Elevation_Corrected'], 4)
        while current_az != azs[-1] or current_el != els[-1]:
            yield dsleep(0.1)
            modes = (self.data['status']['summary']['Azimuth_mode'],
                     self.data['status']['summary']['Elevation_mode'])
            if modes != ('ProgramTrack', 'ProgramTrack'):
                return False, 'Fault triggered (not ProgramTrack)!'
            current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4)
            current_el = round(self.data['broadcast']['Elevation_Corrected'],
                               4)
        yield dsleep(self.sleeptime)
        yield self.acu.stop()
        self.set_job_done('control')
        return True, 'Track completed.'
Beispiel #12
0
    def go_to(self, session, params=None):
        """ TASK "go_to"

        Moves the telescope to a particular point (azimuth, elevation)
        in Preset mode. When motion has ended and the telescope reaches
        the preset point, it returns to Stop mode and ends.

        Params:
            az (float): destination angle for the azimuthal axis
            el (float): destination angle for the elevation axis
            wait (float): amount of time to wait for motion to end
        """
        ok, msg = self.try_set_job('control')
        if not ok:
            return ok, msg
        az = params.get('az')
        el = params.get('el')
        wait_for_motion = params.get('wait', 1)
        current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4)
        current_el = round(self.data['broadcast']['Elevation_Corrected'], 4)
        publish_dict = {
            'Start_Azimuth': current_az,
            'Start_Elevation': current_el,
            'Start_Boresight': 0,
            'Upload_Type': 1,
            'Preset_Azimuth': az,
            'Preset_Elevation': el,
            'Upload_Lines': []
        }
        acu_upload = {
            'timestamp': self.data['broadcast']['Time'],
            'block_name': 'ACU_upload',
            'data': publish_dict
        }
        self.agent.publish_to_feed('acu_upload', acu_upload)
        # Check whether the telescope is already at the point
        self.log.info('Checking current position')
        if current_az == az and current_el == el:
            self.log.info('Already positioned at %.2f, %.2f' %
                          (current_az, current_el))
            self.set_job_done('control')
            return True, 'Pointing completed'
        yield self.acu.stop()
        self.log.info('Stopped')
        yield dsleep(0.1)
        yield self.acu.go_to(az, el)
        mdata = self.data['status']['summary']
        # Wait for telescope to start moving
        self.log.info('Moving to commanded position')
        while mdata['Azimuth_current_velocity'] == 0.0 and\
                mdata['Elevation_current_velocity'] == 0.0:
            yield dsleep(wait_for_motion)
            mdata = self.data['status']['summary']
        moving = True
        while moving:
            mdata = self.data['status']['summary']
            ve = round(mdata['Elevation_current_velocity'], 2)
            va = round(mdata['Azimuth_current_velocity'], 2)
            if (ve != 0.0) or (va != 0.0):
                moving = True
                yield dsleep(wait_for_motion)
            else:
                moving = False
                mdata = self.data['status']['summary']
                pe = round(mdata['Elevation_current_position'], 2)
                pa = round(mdata['Azimuth_current_position'], 2)
                if pe != el or pa != az:
                    yield self.acu.stop()
                    self.log.warn('Stopped before reaching commanded point!')
                    return False, 'Something went wrong!'
                modes = (mdata['Azimuth_mode'], mdata['Elevation_mode'])
                if modes != ('Preset', 'Preset'):
                    return False, 'Fault triggered!'

        yield self.acu.stop()
        self.set_job_done('control')
        return True, 'Pointing completed'
Beispiel #13
0
    def start_udp_monitor(self, session, params=None):
        """PROCESS broadcast

        This process reads UDP data from the port specified by self.acu_config,
        decodes it, and publishes to an HK feed.

        """
        ok, msg = self.try_set_job('broadcast')
        if not ok:
            return ok, msg
        session.set_status('running')
        FMT = '<iddddd'
        FMT_LEN = struct.calcsize(FMT)
        UDP_PORT = self.acu_config['PositionBroadcast_target'].split(':')[1]
        udp_data = []

        class MonitorUDP(protocol.DatagramProtocol):
            def datagramReceived(self, data, src_addr):
                host, port = src_addr
                offset = 0
                while len(data) - offset >= FMT_LEN:
                    d = struct.unpack(FMT, data[offset:offset + FMT_LEN])
                    udp_data.append(d)
                    offset += FMT_LEN

        handler = reactor.listenUDP(int(UDP_PORT), MonitorUDP())
        while self.jobs['broadcast'] == 'run':
            if udp_data:
                self.health_check['broadcast'] = True
                process_data = udp_data[:200]
                udp_data = udp_data[200:]
                year = datetime.datetime.now().year
                gyear = calendar.timegm(time.strptime(str(year), '%Y'))
                sample_rate = (len(process_data) / (
                    (process_data[-1][0] - process_data[0][0]) * 86400 +
                    process_data[-1][1] - process_data[0][1]))
                latest_az = process_data[2]
                latest_el = process_data[3]
                latest_az_raw = process_data[4]
                latest_el_raw = process_data[5]
                session.data = {
                    'sample_rate': sample_rate,
                    'latest_az': latest_az,
                    'latest_el': latest_el,
                    'latest_az_raw': latest_az_raw,
                    'latest_el_raw': latest_el_raw
                }
                pd0 = process_data[0]
                pd0_gday = (pd0[0] - 1) * 86400
                pd0_sec = pd0[1]
                pd0_data_ctime = gyear + pd0_gday + pd0_sec
                pd0_azimuth_corrected = pd0[2]
                pd0_azimuth_raw = pd0[4]
                pd0_elevation_corrected = pd0[3]
                pd0_elevation_raw = pd0[5]
                bcast_first = {
                    'Time': pd0_data_ctime,
                    'Azimuth_Corrected': pd0_azimuth_corrected,
                    'Azimuth_Raw': pd0_azimuth_raw,
                    'Elevation_Corrected': pd0_elevation_corrected,
                    'Elevation_Raw': pd0_elevation_raw,
                }
                acu_broadcast_influx = {
                    'timestamp': bcast_first['Time'],
                    'block_name': 'ACU_position',
                    'data': bcast_first,
                }
                self.agent.publish_to_feed('acu_broadcast_influx',
                                           acu_broadcast_influx)
                for d in process_data:
                    gday = (d[0] - 1) * 86400
                    sec = d[1]
                    data_ctime = gyear + gday + sec
                    azimuth_corrected = d[2]
                    azimuth_raw = d[4]
                    elevation_corrected = d[3]
                    elevation_raw = d[5]
                    self.data['broadcast'] = {
                        'Time': data_ctime,
                        'Azimuth_Corrected': azimuth_corrected,
                        'Azimuth_Raw': azimuth_raw,
                        'Elevation_Corrected': elevation_corrected,
                        'Elevation_Raw': elevation_raw,
                    }
                    acu_udp_stream = {
                        'timestamp': self.data['broadcast']['Time'],
                        'block_name': 'ACU_position',
                        'data': self.data['broadcast']
                    }
                    self.agent.publish_to_feed('acu_udp_stream',
                                               acu_udp_stream)
            else:
                yield dsleep(1)
            yield dsleep(0.005)

        handler.stopListening()
        self.set_job_done('broadcast')
        return True, 'Acquisition exited cleanly.'
Beispiel #14
0
    def start_monitor(self, session, params=None):
        """PROCESS "monitor".

        This process refreshes the cache of SATP ACU status information,
        and reports it on HK feeds 'acu_status_summary' and 'acu_status_full'.

        Summary parameters are ACU-provided time code, Azimuth mode,
        Azimuth position, Azimuth velocity, Elevation mode, Elevation position,
        Elevation velocity, Boresight mode, and Boresight position.

        """
        ok, msg = self.try_set_job('monitor')
        if not ok:
            return ok, msg

        session.set_status('running')

        report_t = time.time()
        report_period = 10
        n_ok = 0
        min_query_period = 0.05  # Seconds
        query_t = 0
        summary_params = [
            'Time',
            'Azimuth mode',
            'Azimuth current position',
            'Azimuth current velocity',
            'Elevation mode',
            'Elevation current position',
            'Elevation current velocity',
            # 'Boresight mode',
            # 'Boresight current position',
            'Qty of free program track stack positions',
        ]
        mode_key = {
            'Stop': 0,
            'Preset': 1,
            'ProgramTrack': 2,
            'Stow': 3,
            'SurvivalMode': 4,
        }
        tfn_key = {'None': 0, 'False': 0, 'True': 1}
        char_replace = [' ', '-', ':', '(', ')', '+', ',', '/']
        while self.jobs['monitor'] == 'run':
            now = time.time()

            if now > report_t + report_period:
                self.log.info('Responses ok at %.3f Hz' % (n_ok /
                                                           (now - report_t)))
                self.health_check['status'] = True
                report_t = now
                n_ok = 0

            if now - query_t < min_query_period:
                yield dsleep(now - query_t)

            query_t = now
            try:
                # j = yield self.acu.http.Values('DataSets.StatusSATPDetailed8100')
                j = yield self.acu.http.Values(
                    'DataSets.StatusCCATDetailed8100')
                n_ok += 1
                session.data = j
            except Exception as e:
                # Need more error handling here...
                errormsg = {'aculib_error_message': str(e)}
                self.log.error(errormsg)
                acu_error = {
                    'timestamp': time.time(),
                    'block_name': 'ACU_error',
                    'data': errormsg
                }
                self.agent.publish_to_feed('acu_error', acu_error)
                yield dsleep(1)

            for (key, value) in session.data.items():
                ocs_key = key
                for char in char_replace:
                    ocs_key = ocs_key.replace(char, '_')
                ocs_key = ocs_key.replace('24V', 'V24')
                if key in summary_params:
                    self.data['status']['summary'][ocs_key] = value
                    if key == 'Azimuth mode':
                        self.data['status']['summary']['Azimuth_mode_num'] =\
                            mode_key[value]
                    elif key == 'Elevation mode':
                        self.data['status']['summary']['Elevation_mode_num'] =\
                            mode_key[value]
                else:
                    self.data['status']['full_status'][ocs_key] = str(value)
            influx_status = {}
            for v in self.data['status']['full_status']:
                try:
                    influx_status[str(v) + '_influx'] =\
                        float(self.data['status']['full_status'][v])
                except ValueError:
                    influx_status[str(v) + '_influx'] =\
                        tfn_key[self.data['status']['full_status'][v]]
            self.data['status']['summary']['ctime'] =\
                timecode(self.data['status']['summary']['Time'])
            acustatus_summary = {
                'timestamp': self.data['status']['summary']['ctime'],
                'block_name': 'ACU_summary_output',
                'data': self.data['status']['summary']
            }
            acustatus_full = {
                'timestamp': self.data['status']['summary']['ctime'],
                'block_name': 'ACU_fullstatus_output',
                'data': self.data['status']['full_status']
            }
            acustatus_influx = {
                'timestamp': self.data['status']['summary']['ctime'],
                'block_name': 'ACU_fullstatus_ints',
                'data': influx_status
            }
            self.agent.publish_to_feed('acu_status_summary', acustatus_summary)
            self.agent.publish_to_feed('acu_status_full', acustatus_full)
            self.agent.publish_to_feed('acu_status_influx', acustatus_influx)
        self.set_job_done('monitor')
        return True, 'Acquisition exited cleanly.'
Beispiel #15
0
    def master(self, session, params):
        """master(**kwargs)

        **Process** - The "master" Process maintains a list of child Agents for
        which it is responsible.  In response to requests from a client, the
        Process will launch or terminate child Agents.

        If an Agent process exits unexpectedly, it will be relaunched
        within a few seconds.

        When the master Process receives a stop request, it will terminate all
        child agents before moving to the 'done' state.

        Parameters:
            **kwargs: Passed directly to
                ``_update_target_states(params=kwargs)``; see
                :func:`HostMaster._update_target_states`.

        """
        self.running = True
        session.set_status('running')
        self._update_target_states(session, params)

        session.data = {}

        dying_words = ['down', 'kill', 'wait_dead']  #allowed during shutdown

        any_jobs = False
        while self.running or any_jobs:

            sleep_time = 1.
            any_jobs = False

            for key, db in self.database.items():
                # State machine.
                prot = db['prot']

                # If Process exit is requested, force all targets to down.
                if not self.running:
                    db['target_state'] = 'down'

                # The uninterruptible transition state(s) are most easily handled
                # in the same way regardless of target state.

                # Transitional: wait_start, which bridges from start -> up.
                if db['next_action'] == 'wait_start':
                    if prot is not None:
                        session.add_message(
                            'Launched {full_name}'.format(**db))
                        db['next_action'] = 'up'
                    else:
                        if time.time() >= db['at']:
                            session.add_message(
                                'Launch not detected for '
                                '{full_name}!  Will retry.'.format(**db))
                            db['next_action'] = 'start_at'
                            db['at'] = time.time() + 5.

                # Transitional: wait_dead, which bridges from kill -> idle.
                elif db['next_action'] == 'wait_dead':
                    if prot is None:
                        stat, t = 0, None
                    else:
                        stat, t = prot.status
                    if stat is not None:
                        db['next_action'] = 'down'
                    elif time.time() >= db['at']:
                        if stat is None:
                            session.add_message('Agent instance {full_name} '
                                                'refused to die.'.format(**db))
                            db['next_action'] = 'down'
                    else:
                        sleep_time = min(sleep_time, db['at'] - time.time())

                # State handling when target is to be 'up'.
                elif db['target_state'] == 'up':
                    if db['next_action'] == 'start_at':
                        if time.time() >= db['at']:
                            db['next_action'] = 'start'
                        else:
                            sleep_time = min(sleep_time,
                                             db['at'] - time.time())
                    elif db['next_action'] == 'start':
                        # Launch.
                        if db['agent_script'] is None:
                            session.add_message(
                                'No Agent script registered for '
                                'class: {class_name}'.format(**db))
                            db['next_action'] = 'down'
                        else:
                            session.add_message(
                                'Requested launch for {full_name}'.format(
                                    **db))
                            db['prot'] = None
                            reactor.callFromThread(self._launch_instance, key,
                                                   db['agent_script'],
                                                   db['instance_id'])
                            db['next_action'] = 'wait_start'
                            db['at'] = time.time() + 1.
                    elif db['next_action'] == 'up':
                        stat, t = prot.status
                        if stat is not None:
                            # Right here would be a great place to check
                            # the stat return code, and include a traceback from stderr
                            session.add_message('Detected exit of {full_name} '
                                                'with code {stat}.'.format(
                                                    stat=stat, **db))
                            db['next_action'] = 'start_at'
                            db['at'] = time.time() + 3
                    else:  # 'down'
                        db['next_action'] = 'start'

                # State handling when target is to be 'down'.
                elif db['target_state'] == 'down':
                    if db['next_action'] == 'down':
                        pass
                    elif db['next_action'] == 'up':
                        session.add_message('Requesting termination of '
                                            '{full_name}'.format(**db))
                        self._terminate_instance(key)
                        db['next_action'] = 'wait_dead'
                        db['at'] = time.time() + 5
                    else:  # 'start_at', 'start'
                        session.add_message(
                            'Modifying state of {full_name} from '
                            '{next_action} to idle'.format(**db))
                        db['next_action'] = 'down'

                # Should not get here.
                else:
                    session.add_message(
                        'State machine failure: state={next_action}, target_state'
                        '={target_state}'.format(**db))

                any_jobs = (any_jobs or (db['next_action'] != 'down'))

            # Clean up retired items.
            self.database = {
                k: v
                for k, v in self.database.items()
                if not v.get('retired') or v['next_action'] != 'down'
            }

            # Update session info.
            child_states = []
            for k, v in self.database.items():
                child_states.append({
                    _k: v[_k]
                    for _k in [
                        'next_action', 'target_state', 'class_name',
                        'instance_id'
                    ]
                })
            session.data = child_states

            yield dsleep(max(sleep_time, .001))
        return True, 'Exited.'