def query_apcaccess(self, p):
        try:
            args = [
                '/usr/sbin/apcaccess', '-f', self._device, '-p', p.key, '-u'
            ]

            output = subprocess.check_output(
                args, universal_newlines=True,
                timeout=self._query_timeout).strip()
            if not output:
                return p.error_value

            if self._last_command_failed:
                log.info(self._log_name,
                         'Restored contact with ' + self._device)
                self._last_command_failed = False

            return p.parse_apcaccess_output(output)
        except Exception as exception:
            print('{} ERROR: failed to query {}: {}' \
                  .format(datetime.datetime.utcnow(), self._device, str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._device)
                self._last_command_failed = True

            return p.error_value
示例#2
0
    def status(self):
        """Return a dictionary of parameter values for this device"""
        try:
            url = 'http://' + self._ip + '/status.xml'
            with urllib.request.urlopen(url, None,
                                        self._query_timeout) as response:
                response = response.read().decode('ascii')
                xml = ET.fromstring(response)

            if self._last_command_failed:
                log.info(self._log_name, 'Restored contact with ' + self._ip)
                self._last_command_failed = False

            # Return a dictionary of values keyed by parameter name
            return {
                k.name: k.parse_status_response(xml)
                for k in self.parameters
            }

        except Exception as exception:
            print('{} ERROR: failed to query {}: {}' \
                  .format(datetime.datetime.utcnow(), self._ip, str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._ip)
                self._last_command_failed = True

            return {k.name: k.error_value for k in self.parameters}
示例#3
0
    def set_parameter(self, parameter_name, value):
        """Sets the value of a named parameter"""
        if parameter_name not in self.parameters_by_name:
            return False

        parameter = self.parameters_by_name[parameter_name]

        try:
            url = 'http://' + self._ip + '/' + parameter.format_set_parameter(
                value)
            with urllib.request.urlopen(url, None,
                                        self._query_timeout) as response:
                response = response.read().decode('ascii')

            if self._last_command_failed:
                log.info(self._log_name, 'Restored contact with ' + self._ip)
                self._last_command_failed = False
        except Exception as exception:
            print('{} ERROR: failed to send HTTP command: {}' \
                  .format(datetime.datetime.utcnow(), str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._ip)
                self._last_command_failed = True

            return False

        return response == 'Success! ' + str(value)
示例#4
0
    def __take_image(self, exposure, delay):
        """Tells the camera to take an exposure.
           if exposure is 0 then it will reset the camera
           configuration and take a bias with the shutter closed
        """
        self._expected_complete = datetime.datetime.utcnow() \
            + datetime.timedelta(seconds=exposure + delay + CONFIG['max_processing_time'])
        try:
            # Need to communicate directly with camera daemon
            # to allow set_exposure_delay and set_exosure
            with self._daemon.connect() as cam:
                if exposure == 0:
                    # .configure will reset all other parameters to their default values
                    cam_config = {}
                    cam_config.update(self._camera_config)
                    cam_config.update({
                        'shutter': False,
                        'exposure': 0
                    })
                    cam.configure(cam_config, quiet=True)
                else:
                    cam.set_exposure_delay(delay, quiet=True)
                    cam.set_exposure(exposure, quiet=True)
                    cam.set_shutter(True, quiet=True)

                cam.start_sequence(1, quiet=True)
        except Pyro4.errors.CommunicationError:
            print('AutoFlat: Failed to communicate with ' + self.name + ' camera daemon')
            log.error(self._log_name, 'Failed to communicate with ' + self.name + ' camera daemon')
            self.state = AutoFlatState.Error
        except Exception:
            print('AutoFlat: Unknown error with ' + self.name + ' camera')
            traceback.print_exc(file=sys.stdout)
            log.error(self._log_name, 'Unknown error with ' + self.name + ' camera')
            self.state = AutoFlatState.Error
示例#5
0
    def status(self):
        """Return a dictionary of parameter values for this device"""
        # Query all OIDs at once for efficiency
        oids = [p.get_oid for p in self.parameters]
        args = ['/usr/bin/snmpget', '-v', '1', '-c', 'public', self._ip] + oids
        try:
            output = subprocess.check_output(args,
                                             universal_newlines=True,
                                             timeout=self._query_timeout)
            lines = output.strip().split('\n')

            if self._last_command_failed:
                log.info(self._log_name, 'Restored contact with ' + self._ip)
                self._last_command_failed = False

            # Return a dictionary of values keyed by parameter name
            return {
                k.name: k.parse_snmpget_output(v)
                for k, v in zip(self.parameters, lines)
            }
        except Exception as exception:
            print('{} ERROR: failed to query {}: {}' \
                  .format(datetime.datetime.utcnow(), self._ip, str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._ip)
                self._last_command_failed = True

            return {k.name: k.error_value for k in self.parameters}
示例#6
0
    def check_timeout(self):
        """Sets error state if an expected frame is more than 30 seconds late"""
        if self.state not in [AutoFlatState.Waiting, AutoFlatState.Saving]:
            return

        if datetime.datetime.utcnow() > self._expected_complete:
            print('AutoFlat: ' + self.name + ' camera exposure timed out')
            log.error(self._log_name, 'AutoFlat: ' + self.name + ' camera exposure timed out')
            self.state = AutoFlatState.Error
 def __run_thread_wrapper(self):
     """Wrapper that catches exceptions thrown in run_thread implementations
        and sets the error status
     """
     try:
         self.run_thread()
     except:
         print('error: exception in action run thread:')
         traceback.print_exc(file=sys.stdout)
         log.error(self.log_name, 'Exception in action run thread')
         self.status = TelescopeActionStatus.Error
示例#8
0
def stop_camera(log_name, daemon):
    """Aborts any active exposure sequences"""
    try:
        with daemon.connect() as camd:
            return camd.stop_sequence() == CamCommandStatus.Succeeded
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with camera daemon')
        log.error(log_name, 'Failed to communicate with camera daemon')
        return False
    except Exception:
        print('Unknown error while stopping camera')
        traceback.print_exc(file=sys.stdout)
示例#9
0
    def __initialize_telescope(self):
        """Initializes and homes the telescope"""
        try:
            self.set_task('Initializing Telescope')

            with daemons.rasa_telescope.connect(timeout=HOME_TIMEOUT) as teld:
                status = teld.initialize()
                if status not in [TelCommandStatus.Succeeded,
                                  TelCommandStatus.TelescopeNotDisabled]:
                    print('Failed to initialize telescope')
                    log.error(self.log_name, 'Failed to initialize telescope')
                    return False

            self.set_task('Slewing to park position')
            with daemons.rasa_telescope.connect(timeout=STOW_TIMEOUT) as teld:
                status = teld.slew_altaz(STOW_ALTAZ[0], STOW_ALTAZ[1])
                if status != TelCommandStatus.Succeeded:
                    print('Failed to park telescope')
                    log.error(self.log_name, 'Failed to park telescope')
                    return False
        except Pyro4.errors.CommunicationError:
            print('Failed to communicate with telescope daemon')
            log.error(self.log_name, 'Failed to communicate with telescope daemon')
            return False
        except Exception as e:
            print('Unknown error with telescope')
            print(e)
            log.error(self.log_name, 'Unknown error with telescope')
            return False
        return True
示例#10
0
def get_camera_status(log_name, daemon):
    """Returns the status dictionary for the camera"""
    try:
        with daemon.connect() as camd:
            return camd.report_status()
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with camera daemon')
        log.error(log_name, 'Failed to communicate with camera daemon')
        return None
    except Exception:
        print('Unknown error while querying camera status')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error with camera')
        return None
示例#11
0
    def __initialize_camera(self, name, daemon):
        """Initializes a given camera and enables cooling"""
        try:
            self.set_task('Initializing Cameras')
            with daemon.connect(timeout=CAM_INIT_TIMEOUT) as cam:
                status = cam.initialize()
                if status not in [CamCommandStatus.Succeeded,
                                  CamCommandStatus.CameraNotUninitialized]:
                    print('Failed to initialize ' + name + ' camera')
                    log.error(self.log_name, 'Failed to initialize ' + name + ' camera')
                    return False

                # Calling configure with an empty dictionary resets everything to defaults
                if cam.configure({}, quiet=True) != CamCommandStatus.Succeeded:
                    print('Failed to reset ' + name + ' camera to defaults')
                    log.error(self.log_name, 'Failed to reset ' + name + ' camera to defaults')
                    return False

        except Pyro4.errors.CommunicationError:
            print('Failed to communicate with ' + name + ' camera daemon')
            log.error(self.log_name, 'Failed to communicate with ' + name + ' camera daemon')
            return False
        except Exception as e:
            print('Unknown error with ' + name + ' camera')
            print(e)
            log.error(self.log_name, 'Unknown error with ' + name + ' camera')
            return False
        return True
示例#12
0
def tel_status(log_name):
    """Returns the telescope status dict or None on error"""
    try:
        with daemons.rasa_telescope.connect() as teld:
            return teld.report_status()
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with telescope daemon')
        log.error(log_name, 'Failed to communicate with telescope daemon')
        return None
    except Exception:
        print('Unknown error while querying telescope status')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while querying telescope status')
        return None
示例#13
0
def stop_focus(log_name, channel):
    """Stop the focuser movement"""
    try:
        with daemons.rasa_focus.connect() as focusd:
            focusd.stop_channel(channel)
        return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with focuser daemon')
        log.error(log_name, 'Failed to communicate with focuser daemon')
        return False
    except Exception:
        print('Unknown error while stopping focuser')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while stopping focuser')
        return False
示例#14
0
def tel_stop(log_name):
    """Stop the telescope tracking or movement"""
    try:
        with daemons.rasa_telescope.connect() as teld:
            teld.stop()
        return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with telescope daemon')
        log.error(log_name, 'Failed to communicate with telescope daemon')
        return False
    except Exception:
        print('Unknown error while stopping telescope')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while stopping telescope')
        return False
示例#15
0
    def run(self):
        """Main run loop"""
        while True:
            # Initial setup
            try:
                self._port = serial.Serial(self._port_name, 9600, timeout=3)
                print('Connected to', self._port_name)
                log.info(self._log_name, 'Connected to battery voltmeter')
                self._port_connected = True
            except Exception as exception:
                if self._port_connected:
                    log.error(self._log_name,
                              'Lost connection to battery voltmeter')

                print('error: failed to connect to battery voltmeter')
                print(exception)
                print('Will retry in 10 seconds...')
                time.sleep(10.)
                continue

            try:
                # Flush any stale state
                self._port.flushInput()
                self._port.flushOutput()

                # Main run loop
                while True:
                    data = self._port.readline()
                    match = self._regex.match(data)

                    if match:
                        self._voltage = float(match.group('voltage'))
                        self._voltage_sum += self._voltage
                        self._voltage_history.append(self._voltage)
                        if len(self._voltage_history) > 10:
                            self._voltage_sum -= self._voltage_history.pop(0)

            except Exception as exception:
                self._port.close()
                if self._port_connected:
                    log.error(self._log_name,
                              'Lost connection to battery voltmeter')
                    print('error: lost connection to battery voltmeter')
                    print(exception)

                self._port_connected = False
                print('Will retry in 10 seconds...')
                time.sleep(10.)
示例#16
0
    def set_parameter(self, parameter_name, value):
        """Sets the value of a named parameter"""
        if parameter_name not in self.parameters_by_name:
            return False

        parameter = self.parameters_by_name[parameter_name]

        # Read only parameter
        if not parameter.set_oid:
            return False

        try:
            args = [
                '/usr/bin/snmpset', '-v', '1', '-c', 'private', self._ip,
                parameter.set_oid, 'i',
                parameter.format_set_value(value)
            ]

            output = subprocess.check_output(args,
                                             universal_newlines=True,
                                             timeout=self._query_timeout)
            if self._last_command_failed:
                log.info(self._log_name, 'Restored contact with ' + self._ip)
                self._last_command_failed = False
        except Exception as exception:
            print('{} ERROR: failed to send SNMP command: {}' \
                  .format(datetime.datetime.utcnow(), str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._ip)
                self._last_command_failed = True

            return False

        try:
            return parameter.parse_snmpset_output(output) == value
        except Exception as exception:
            print('{} ERROR: failed to parse SNMP response: {}' \
                  .format(datetime.datetime.utcnow(), str(exception)))

            if not self._last_command_failed:
                log.error(
                    self._log_name, 'Invalid response from ' + self._ip +
                    ': ' + str(exception))

            return False
示例#17
0
def take_images(log_name, daemon, count=1, config=None, quiet=False):
    """Start an exposure sequence with count images

       If config is non-None it is assumed to contain
       a dictionary of camera configuration that has been
       validated by the camera schema, which is applied
       before starting the sequence.
    """
    try:
        with daemon.connect() as cam:
            if config:
                status = cam.configure(config, quiet=quiet)

            if not config or status == CamCommandStatus.Succeeded:
                status = cam.start_sequence(count, quiet=quiet)

            if status != CamCommandStatus.Succeeded:
                print('Failed to start exposure sequence')
                log.error(log_name, 'Failed to start exposure sequence')
                return False
            return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with camera daemon')
        log.error(log_name, 'Failed to communicate with camera daemon')
        return False
    except Exception:
        print('Unknown error with camera')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error with camera')
        return False
    def set_parameter(self, parameter_name, value):
        """Sets the value of a named parameter"""
        if parameter_name != self._parameter_name:
            return False

        try:
            with self._daemon.connect(timeout=self._query_timeout) as daemon:
                success = daemon.set_relay(value)

            if self._last_command_failed:
                log.info(self._log_name,
                         'Restored contact with ' + self._daemon.name)
                self._last_command_failed = False

            return success
        except Exception:
            if not self._last_command_failed:
                log.error(self._log_name,
                          'Lost contact with ' + self._daemon.name)
                self._last_command_failed = True

            return False
    def get_parameter(self, parameter_name):
        """Returns the value of a named parameter"""
        if parameter_name != self._parameter_name:
            return False

        try:
            with self._daemon.connect(timeout=self._query_timeout) as daemon:
                enabled = daemon.get_relay()

            if self._last_command_failed:
                log.info(self._log_name,
                         'Restored contact with ' + self._daemon.name)
                self._last_command_failed = False

            return SwitchStatus.On if enabled else SwitchStatus.Off

        except Exception:
            if not self._last_command_failed:
                log.error(self._log_name,
                          'Lost contact with ' + self._daemon.name)
                self._last_command_failed = True

            return SwitchStatus.Unknown
示例#20
0
    def get_parameter(self, parameter_name):
        """Returns the value of a named parameter"""
        if parameter_name not in self.parameters_by_name:
            return False

        parameter = self.parameters_by_name[parameter_name]
        try:
            args = [
                '/usr/bin/snmpget', '-v', '1', '-c', 'public', self._ip,
                parameter.get_oid
            ]
            output = subprocess.check_output(args,
                                             universal_newlines=True,
                                             timeout=self._query_timeout)
            return parameter.parse_snmpget_output(output)
        except Exception as exception:
            print('{} ERROR: failed to query {}: {}' \
                  .format(datetime.datetime.utcnow(), self._ip, str(exception)))

            if not self._last_command_failed:
                log.error(self._log_name, 'Lost contact with ' + self._ip)
                self._last_command_failed = True

            return parameter.error_value
示例#21
0
def tel_offset_radec(log_name, ra, dec, timeout):
    """Offset the telescope by a given RA, Dec"""
    try:
        with daemons.rasa_telescope.connect(timeout=timeout) as teld:
            status = teld.offset_radec(ra, dec)
            if status != TelCommandStatus.Succeeded:
                print('Failed to offset telescope position')
                log.error(log_name, 'Failed to offset telescope position')
                return False
            return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with telescope daemon')
        log.error(log_name, 'Failed to communicate with telescope daemon')
        return False
    except Exception:
        print('Unknown error while offsetting telescope')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while offsetting telescope')
        return False
示例#22
0
def set_focus(log_name, channel, position, timeout):
    """Set the given focuser channel to the given position"""
    try:
        with daemons.rasa_focus.connect(timeout=timeout) as focusd:
            print('moving focus {} to {}'.format(channel, position))
            status = focusd.set_focus(channel, position)
            if status != FocCommandStatus.Succeeded:
                print('Failed to set focuser position')
                log.error(log_name, 'Failed to set focuser position')
                return False
            return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with focuser daemon')
        log.error(log_name, 'Failed to communicate with focuser daemon')
        return False
    except Exception:
        print('Unknown error while configuring focuser')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while configuring focuser')
        return False
示例#23
0
def tel_slew_altaz(log_name, alt, az, tracking, timeout):
    """Slew the telescope to a given Alt, Az"""
    try:
        with daemons.rasa_telescope.connect(timeout=timeout) as teld:
            if tracking:
                status = teld.track_altaz(alt, az)
            else:
                status = teld.slew_altaz(alt, az)

            if status != TelCommandStatus.Succeeded:
                print('Failed to slew telescope')
                log.error(log_name, 'Failed to slew telescope')
                return False
            return True
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with telescope daemon')
        log.error(log_name, 'Failed to communicate with telescope daemon')
        return False
    except Exception:
        print('Unknown error while slewing telescope')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while slewing telescope')
        return False
示例#24
0
def get_focus(log_name, channel):
    """Returns the requested focuser position or None on error
       Requires focuser to be idle
    """
    try:
        with daemons.rasa_focus.connect() as focusd:
            # TODO: Hacking channel id to channel array index is bogus
            # Maybe push this into focusd itself
            status = focusd.report_status()['channels'][channel-1]
            if status['status'] != FocuserStatus.Idle:
                print('Focuser status is not idle')
                log.error(log_name, 'Focuser status is not idle')
                return None
            return status['current_steps']
    except Pyro4.errors.CommunicationError:
        print('Failed to communicate with focuser daemon')
        log.error(log_name, 'Failed to communicate with focuser daemon')
        return None
    except Exception:
        print('Unknown error while querying focuser position')
        traceback.print_exc(file=sys.stdout)
        log.error(log_name, 'Unknown error while querying focuser position')
        return None
示例#25
0
    def __wait_for_temperature_lock(self):
        """Waits until both cameras have reached their target temperature
           Returns True on success, False on error
        """
        # Wait for cameras to cool if required
        self.set_task('Cooling cameras')
        locked = {k: False for k in self._camera_daemons}

        while not self.aborted:
            try:
                for arm in self._camera_daemons:
                    with self._camera_daemons[arm].connect() as cam:
                        status = cam.report_status()
                        if 'temperature_locked' not in status:
                            print('Failed to check tempearture on ' + arm + ' camera')
                            log.error(self.log_name, 'Failed to check temperature on ' + arm +
                                      ' camera')
                            return False

                        locked[arm] = status['temperature_locked']
            except Pyro4.errors.CommunicationError:
                print('Failed to communicate with ' + arm + ' camera daemon')
                log.error(self.log_name, 'Failed to communicate with ' + arm + ' camera daemon')
                return False
            except Exception as e:
                print('Unknown error with ' + arm + ' camera')
                print(e)
                log.error(self.log_name, 'Unknown error with ' + arm + ' camera')
                return False

            if all([locked[k] for k in locked]):
                break

            with self._cooling_condition:
                self._cooling_condition.wait(CAMERA_CHECK_INTERVAL)
        return not self.aborted
示例#26
0
    def run_thread(self):
        """Thread that runs the hardware actions"""
        # Query site location from the telescope
        ts = tel_status(self.log_name)
        if not ts:
            self.status = TelescopeActionStatus.Error
            return

        # pylint: disable=no-member
        location = EarthLocation(lat=ts['site_latitude']*u.rad,
                                 lon=ts['site_longitude']*u.rad,
                                 height=ts['site_elevation']*u.m)
        # pylint: enable=no-member

        while not self.aborted:
            waiting_for = []
            sun_altitude = sun_position(location)[0]
            if self.config['evening']:
                if sun_altitude < CONFIG['min_sun_altitude']:
                    print('AutoFlat: Sun already below minimum altitude')
                    log.info(self.log_name, 'AutoFlat: Sun already below minimum altitude')
                    self.status = TelescopeActionStatus.Complete
                    return

                if sun_altitude < CONFIG['max_sun_altitude'] and self.dome_is_open:
                    break

                if not self.dome_is_open:
                    waiting_for.append('Dome')

                if sun_altitude >= CONFIG['max_sun_altitude']:
                    waiting_for.append('Sun < {:.1f} deg'.format(CONFIG['max_sun_altitude']))

                print('AutoFlat: {:.1f} > {:.1f}; dome {} - keep waiting'.format(
                    sun_altitude, CONFIG['max_sun_altitude'], self.dome_is_open))
            else:
                if sun_altitude > CONFIG['max_sun_altitude']:
                    print('AutoFlat: Sun already above maximum altitude')
                    log.info(self.log_name, 'AutoFlat: Sun already above maximum altitude')
                    self.status = TelescopeActionStatus.Complete
                    return

                if sun_altitude > CONFIG['min_sun_altitude'] and self.dome_is_open:
                    break

                if not self.dome_is_open:
                    waiting_for.append('Dome')

                if sun_altitude < CONFIG['min_sun_altitude']:
                    waiting_for.append('Sun > {:.1f} deg'.format(CONFIG['min_sun_altitude']))

                print('AutoFlat: {:.1f} < {:.1f}; dome {} - keep waiting'.format(
                    sun_altitude, CONFIG['min_sun_altitude'], self.dome_is_open))

            self.set_task('Waiting for ' + ', '.join(waiting_for))
            with self._wait_condition:
                self._wait_condition.wait(CONFIG['sun_altitude_check_interval'])

        self.set_task('Slewing to antisolar point')

        # The anti-solar point is opposite the sun at 75 degrees
        sun_altaz = sun_position(location)
        print('AutoFlat: Sun position is', sun_altaz)

        if not tel_slew_altaz(self.log_name,
                              math.radians(75),
                              math.radians(sun_altaz[1] + 180),
                              False, SLEW_TIMEOUT):
            if not self.aborted:
                print('AutoFlat: Failed to slew telescope')
                log.error(self.log_name, 'AutoFlat: Failed to slew telescope')
                self.status = TelescopeActionStatus.Error
                return

        # Last chance to bail out before starting the main logic
        if self.aborted:
            self.status = TelescopeActionStatus.Complete
            return

        # Configure pipeline and camera for flats
        # Archiving will be enabled when the brightness is inside the required range
        pipeline_config = {}
        pipeline_config.update(self.config['pipeline'])
        pipeline_config.update({
            'intstats': True,
            'type': 'FLAT',
        })

        if not configure_pipeline(self.log_name, pipeline_config):
            self.status = TelescopeActionStatus.Error
            return

        # Take an initial bias frame for calibration
        # This starts the autoflat logic, which is run
        # in the received_frame callbacks
        for arm in self._instrument_arms.values():
            arm.start()

        # Wait until complete
        while True:
            with self._wait_condition:
                self._wait_condition.wait(5)

            codes = ''
            for arm in self._instrument_arms.values():
                arm.check_timeout()
                codes += AutoFlatState.Codes[arm.state]

            self.set_task('Acquiring (' + ''.join(codes) + ')')
            if self.aborted:
                break

            if not self.dome_is_open:
                for arm in self._instrument_arms.values():
                    arm.abort()

                print('AutoFlat: Dome has closed')
                log.error(self.log_name, 'AutoFlat: Dome has closed')
                break

            # We are done once all arms are either complete or have errored
            if all([arm.state >= AutoFlatState.Complete for arm in self._instrument_arms.values()]):
                break

        success = self.dome_is_open and all([arm.state == AutoFlatState.Complete
                                             for arm in self._instrument_arms.values()])

        if self.aborted or success:
            self.status = TelescopeActionStatus.Complete
        else:
            self.status = TelescopeActionStatus.Error
示例#27
0
    def __run(self):
        """Main run loop"""
        while True:
            # Initial setup
            try:
                self._port = serial.Serial(self._port_path,
                                           self._port_baud,
                                           timeout=0.1)
                print('Connected to GPS timer on ', self._port_path)
                prefix = 'Restored' if self._port_error else 'Established'
                log.info(self._log_table,
                         prefix + ' serial connection to GPS timer')
                self._port_error = False
            except Exception as exception:
                print(exception)
                print('Will retry in 10 seconds...')
                if not self._port_error:
                    log.error(self._log_table,
                              'Failed to connect to GPS timer')

                self._port_error = True

                time.sleep(10.)
                continue
            try:
                # Flush any stale state
                self._port.reset_input_buffer()
                self._port.reset_output_buffer()
                self.__send_config()

                # Main run loop
                buf = b''
                while True:
                    # Read as much data as we can or block until the timeout expires
                    buf += self._port.read(max(1, self._port.in_waiting))

                    # Sync on start of UBX packet
                    start = 0
                    for start in range(len(buf) - 2):
                        if buf[start] == 0xb5 and buf[start + 1] == 0x62:
                            break

                    if start > 0:
                        print('timer: discarding {} junk bytes'.format(start))
                        buf = buf[start:]

                    # We need at least 4 bytes to determine packet type
                    if len(buf) < 4:
                        continue

                    (msg_cls, msg_id, msg_len) = struct.unpack_from('<BBh',
                                                                    buf,
                                                                    offset=2)
                    if len(buf) < 8 + msg_len:
                        continue

                    # Verify message checksum
                    chka = chkb = 0
                    for i in range(2, msg_len + 6):
                        chka += buf[i]
                        chkb += chka
                    chka = chka & 0xFF
                    chkb = chkb & 0xFF

                    if chka == buf[msg_len + 6] and chkb == buf[msg_len + 7]:
                        self.__parse_message(msg_cls, msg_id, buf)
                    else:
                        print('timer: discarding packet: checksum mismatch')

                    buf = buf[8 + msg_len:]

            except Exception as exception:
                self._port.close()
                print(exception)
                print('Will retry in 10 seconds...')
                if not self._port_error:
                    log.error(self._log_table,
                              'Lost serial connection to GPS timer')
                self._port_error = True
                time.sleep(10.)
示例#28
0
    def run(self):
        """Main run loop"""
        while True:
            # Initial setup
            try:
                self._port = serial.Serial(self._port_name, 9600, timeout=3)
                print('Connected to', self._port_name)

                first_query = self._enabled_date == datetime.datetime.min
                prefix = 'Established' if first_query else 'Restored'
                log.info(self._log_name,
                         prefix + ' contact with Dehumidifier switch')
                self._port_connected = True
            except Exception as exception:
                if self._port_connected:
                    log.error(self._log_name,
                              'Lost contact with Dehumidifier switch')

                print('error: failed to connect to Dehumidifier switch')
                print(exception)
                print('Will retry in 10 seconds...')
                time.sleep(10.)
                continue

            try:
                # Flush any stale state
                self._port.flushInput()
                self._port.flushOutput()

                # Main run loop
                while True:
                    data = ord(self._port.read(1))

                    # First bit is set when the relay to the dehumidifier is enabled
                    enabled = data & 0x01

                    if enabled != self._enabled:
                        with self._updated_condition:
                            self._enabled = enabled
                            self._enabled_date = datetime.datetime.utcnow()

                            # Wake up any threads that requested the change so it can return
                            self._updated_condition.notify_all()

                    # Second bit is set for one update cycle when the button is pressed
                    if data & 0x02:
                        light_enabled = self._power_daemon.value('light')
                        if not self._power_daemon.switch_internal(
                                'light', not light_enabled):
                            log.error(self._log_name,
                                      'Failed to toggle dome lights')

                    if self._desired_enabled != self._enabled:
                        command = (0x01 if self._desired_enabled else 0)
                        self._port.write(chr(command).encode('ascii'))

            except Exception as exception:
                self._port.close()
                if self._port_connected:
                    log.error(self._log_name,
                              'Lost contact with dehumidifier switch')
                    print('error: failed to connect to Dehumidifier switch')
                    print(exception)

                self._port_connected = False
                print('Will retry in 10 seconds...')
                time.sleep(10.)
示例#29
0
    def __loop(self):
        """Thread that controls dome opening/closing to match requested state"""
        while True:
            # Handle requests from the user to change between manual and automatic mode
            # Manual intervention is required to clear errors and return to automatic mode.
            # If an error does occur the dome heartbeat will timeout, and it will close itself.

            # Copy public facing variables to avoid race conditions
            with self._lock:
                requested_mode = self._requested_mode

                current_date = datetime.datetime.utcnow()
                requested_open = self._requested_open_date is not None and \
                    self._requested_close_date is not None and \
                    current_date > self._requested_open_date and \
                    current_date < self._requested_close_date and \
                    self._environment_safe and \
                    self._environment_safe_date > self._requested_open_date

                requested_status = DomeStatus.Open if requested_open else DomeStatus.Closed
                environment_safe_age = (current_date - self._environment_safe_date).total_seconds()

            auto_failure = self._mode == OperationsMode.Error and \
                requested_mode == OperationsMode.Automatic

            if requested_mode != self._mode and not auto_failure:
                print('dome: changing mode from ' + OperationsMode.Names[self._mode] + \
                    ' to ' + OperationsMode.Names[requested_mode])

                try:
                    with self._daemon.connect() as dome:
                        if requested_mode == OperationsMode.Automatic:
                            ret = dome.set_heartbeat_timer(self._heartbeat_timeout)
                            if ret == DomeCommandStatus.Succeeded:
                                self.__set_mode(OperationsMode.Automatic)
                                log.info(self._log_name, 'Dome switched to Automatic mode')
                            else:
                                print('error: failed to switch dome to auto with ' \
                                    + DomeCommandStatus.message(ret))
                                self.__set_mode(OperationsMode.Error)
                                log.info(self._log_name, 'Failed to switch dome to Automatic mode')
                        else:
                            # Switch from auto or error state back to manual
                            ret = dome.set_heartbeat_timer(0)

                            if ret == DomeCommandStatus.Succeeded:
                                self.__set_mode(OperationsMode.Manual)
                                log.info(self._log_name, 'Dome switched to Manual mode')
                            else:
                                print('error: failed to switch dome to manual with ' \
                                    + DomeCommandStatus.message(ret))
                                self.__set_mode(OperationsMode.Error)
                                log.error(self._log_name, 'Failed to switch dome to Manual mode')
                    if self._daemon_error:
                        log.info(self._log_name, 'Restored contact with Dome daemon')
                except Exception as e:
                    if not self._daemon_error:
                        log.error(self._log_name, 'Lost contact with Dome daemon')
                        self._daemon_error = True

                    print('error: failed to communicate with the dome daemon: ', e)
                    self.__set_mode(OperationsMode.Error)

            if self._mode == OperationsMode.Automatic:
                try:
                    with self._daemon.connect() as dome:
                        status = DomeStatus.parse(dome.status())
                        self.__set_status(status)

                    print('dome: is ' +  DomeStatus.Names[status] + ' and wants to be ' + \
                        DomeStatus.Names[requested_status])

                    if status == DomeStatus.Timeout:
                        print('dome: detected heartbeat timeout!')
                        self.__set_mode(OperationsMode.Error)
                    elif requested_status == DomeStatus.Closed and status == DomeStatus.Open:
                        print('dome: sending heartbeat ping before closing')
                        with self._daemon.connect() as dome:
                            dome.set_heartbeat_timer(self._heartbeat_timeout)
                        print('dome: closing')
                        self.__set_status(DomeStatus.Moving)
                        with self._daemon.connect(timeout=self._open_close_timeout) as dome:
                            ret = dome.close_shutters(east=True, west=True)
                        if ret == DomeCommandStatus.Succeeded:
                            self.__set_status(DomeStatus.Closed)
                        else:
                            self.__set_mode(OperationsMode.Error)
                    elif requested_status == DomeStatus.Open and status == DomeStatus.Closed:
                        print('dome: sending heartbeat ping before opening')
                        with self._daemon.connect() as dome:
                            dome.set_heartbeat_timer(self._heartbeat_timeout)
                        print('dome: opening')
                        self.__set_status(DomeStatus.Moving)
                        with self._daemon.connect(timeout=self._open_close_timeout) as dome:
                            ret = dome.open_shutters(east=True, west=True)
                        if ret == DomeCommandStatus.Succeeded:
                            self.__set_status(DomeStatus.Open)
                        else:
                            self.__set_mode(OperationsMode.Error)
                    elif requested_status == status and environment_safe_age < 30:
                        print('dome: sending heartbeat ping')
                        with self._daemon.connect() as dome:
                            dome.set_heartbeat_timer(self._heartbeat_timeout)
                except Exception as e:
                    if not self._daemon_error:
                        log.error(self._log_name, 'Lost contact with Dome daemon')
                        self._daemon_error = True

                    print('error: failed to communicate with the dome daemon: ', e)
                    self.__set_mode(OperationsMode.Error)

            # Clear the schedule if we have passed the close date
            if self._requested_close_date and current_date > self._requested_close_date:
                self.clear_open_window()

            # Wait for the next loop period, unless woken up early by __shortcut_loop_wait
            with self._wait_condition:
                self._wait_condition.wait(self._loop_delay)
示例#30
0
    def measure_current_hfd(self, exposures=1):
        """ Takes a set of exposures and returns the smallest MEDHFD value
            Returns -1 on error
        """
        print('AutoFocus: Sampling HFD')
        log.info(self.log_name, 'AutoFocus: Sampling HFD')

        requested = exposures
        failed = 0

        cam_config = {}
        cam_config.update(self.config.get('rasa', {}))
        cam_config.update({
            'shutter': True,
            'window': [3065, 5112, 2043, 4090]
        })

        # Handle exposures individually
        # This adds a few seconds of overhead when we want to take
        # multiple samples, but this is the simpler/safer option for nows
        samples = []
        while True:
            if len(samples) == requested:
                print('hfd values:', samples)
                return np.min(samples)

            if failed > 5:
                log.error(self.log_name, 'AutoFocus: Aborting because 5 HFD samples failed')
                print(failed, 'AutoFocus: Aborting because 5 HFD samples failed')
                return -1

            if not take_images(self.log_name, self._camera, 1, cam_config, quiet=True):
                return -1

            delay = self.config['rasa']['exposure'] + MAX_PROCESSING_TIME
            expected_complete = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)

            while True:
                if not self.dome_is_open:
                    log.error(self.log_name, 'AutoFocus: Aborting because dome is not open')
                    print(failed, 'AutoFocus: Aborting because dome is not open')
                    return -1

                if self.aborted:
                    log.error(self.log_name, 'AutoFocus: Aborted by user')
                    print('AutoFocus: Aborted by user')
                    return -1

                measurement = self._focus_measurement
                if measurement:
                    self._focus_measurement = None
                    if measurement[1] > MINIMUM_OBJECT_COUNT and measurement[0] > MINIMUM_HFD:
                        samples.append(measurement[0])
                    else:
                        warning = 'AutoFocus: Discarding frame with {} samples ({} HFD)'.format(
                            measurement[1], measurement[0])
                        print(warning)
                        log.warning(self.log_name, warning)
                        failed += 1
                    break

                if datetime.datetime.utcnow() > expected_complete:
                    print('AutoFocus: Exposure timed out - retrying')
                    log.warning(self.log_name, 'AutoFocus: Exposure timed out - retrying')
                    failed += 1
                    break

                with self._wait_condition:
                    self._wait_condition.wait(10)
示例#31
0
    def run_thread(self):
        """Thread that runs the hardware actions"""
        if self.config['onsky'] and not self.dome_is_open:
            print('Aborting: dome is not open')
            log.error(self.log_name, 'Aborting: dome is not open')
            self.status = TelescopeActionStatus.Error
            return

        self.set_task('Preparing camera')

        if not configure_pipeline(self.log_name, self.config['pipeline']):
            log.error(self.log_name, 'Aborting action')
            print('Aborting action')
            self.status = TelescopeActionStatus.Error
            return

        if not take_images(self.log_name, self._camera, self.config['count'], self.config['rasa']):
            log.error(self.log_name, 'Aborting action')
            print('Aborting action')
            self.status = TelescopeActionStatus.Error
            return

        while True:
            self.set_task('Acquiring image {} / {}'.format(self._acquired_images + 1,
                                                           self.config['count']))

            # The wait period rate limits the camera status check
            # The frame received callback will wake this up immedately
            # TODO: This needs to be rewritten in terms of a timeout
            # otherwise it may check while the pipeline is processing and fail
            with self._wait_condition:
                self._wait_condition.wait(60)

            if self._acquired_images == self.config['count'] or self.aborted:
                break

            if self.config['onsky'] and not self.dome_is_open:
                print('Aborting: dome is not open')
                log.error(self.log_name, 'Dome is not open')
                break

            # Check camera for error status
            status = get_camera_status(self.log_name, self._camera)
            if not status:
                print('Failed to query camera status')
                log.error(self.log_name, 'Failed to query camera status')
                break

            if status['state'] not in VALID_CAMERA_STATES:
                message = 'Camera is in unexpected state', CameraStatus.label(status['state'])
                print(message)
                log.error(self.log_name, message)

                if status['state'] == CameraStatus.Idle:
                    remaining = self.config['count'] - self._acquired_images
                    message = 'Restarting remaining {} exposures'.format(remaining)
                    print(message)
                    log.info(self.log_name, message)

                    if not take_images(self.log_name, self._camera, remaining, self.config['rasa']):
                        print('Aborting action')
                        log.error(self.log_name, 'Aborting action')

                        self.status = TelescopeActionStatus.Error
                        return

                    continue
                break

        if self.aborted or self._acquired_images == self.config['count']:
            self.status = TelescopeActionStatus.Complete
        else:
            self.status = TelescopeActionStatus.Error