diff --git a/pocs/core.py b/pocs/core.py index d6fc2837e..8a8721692 100644 --- a/pocs/core.py +++ b/pocs/core.py @@ -81,7 +81,8 @@ def __init__( self._interrupted = False self.force_reschedule = False - self._retry_attempts = 3 + self._retry_attempts = kwargs.get('retry_attempts', 3) + self._obs_run_retries = self._retry_attempts self.status() @@ -118,8 +119,7 @@ def has_messaging(self, value): @property def should_retry(self): - self._retry_attempts -= 1 - return self._retry_attempts >= 0 + return self._obs_run_retries >= 0 ################################################################################################## @@ -136,6 +136,7 @@ def initialize(self): """ if not self._initialized: + self.logger.info('*' * 80) self.say("Initializing the system! Woohoo!") try: @@ -176,6 +177,8 @@ def say(self, msg): Args: msg(str): Message to be sent """ + if self.has_messaging is False: + self.logger.info('Unit says: {}', msg) self.send_message(msg, channel='PANCHAT') def send_message(self, msg, channel='POCS'): @@ -260,6 +263,10 @@ def power_down(self): self._connected = False self.logger.info("Power down complete") + def reset_observing_run(self): + """Reset an observing run loop. """ + self.logger.debug("Resetting observing run attempts") + self._obs_run_retries = self._retry_attempts ################################################################################################## # Safety Methods @@ -422,7 +429,7 @@ def sleep(self, delay=2.5, with_status=True): time.sleep(delay) def wait_until_safe(self): - """ Waits until weather is safe + """ Waits until weather is safe. This will wait until a True value is returned from the safety check, blocking until then. diff --git a/pocs/observatory.py b/pocs/observatory.py index 26140f9ac..b1ea05e3f 100644 --- a/pocs/observatory.py +++ b/pocs/observatory.py @@ -184,9 +184,17 @@ def get_observation(self, *args, **kwargs): """ self.logger.debug("Getting observation for observatory") + + # If observation list is empty or a reread is requested + if (self.scheduler.has_valid_observations is False or + kwargs.get('reread_fields_file', False)): + self.scheduler.read_field_list() + + # This will set the `current_observation` self.scheduler.get_observation(*args, **kwargs) - if self.scheduler.current_observation is None: + if self.current_observation is None: + self.scheduler.clear_available_observations() raise error.NoObservation("No valid observations found") return self.current_observation diff --git a/pocs/scheduler/dispatch.py b/pocs/scheduler/dispatch.py index 14eef0204..30a3c98d9 100644 --- a/pocs/scheduler/dispatch.py +++ b/pocs/scheduler/dispatch.py @@ -30,16 +30,13 @@ def get_observation(self, time=None, show_all=False, reread_fields_file=False): defaults to time called show_all (bool, optional): Return all valid observations along with merit value, defaults to False to only get top value - reread_fields_file (bool, optional): If targets file should be reread - before getting observation, default False. Returns: tuple or list: A tuple (or list of tuples) with name and score of ranked observations """ if reread_fields_file: self.logger.debug("Rereading fields file") - # The setter method on `fields_file` will force a reread - self.fields_file = self.fields_file + self.read_field_list() if time is None: time = current_time() diff --git a/pocs/scheduler/scheduler.py b/pocs/scheduler/scheduler.py index 22fbf19b6..123b66317 100644 --- a/pocs/scheduler/scheduler.py +++ b/pocs/scheduler/scheduler.py @@ -66,11 +66,15 @@ def observations(self): Note: `read_field_list` is called if list is None """ - if len(self._observations.keys()) == 0: + if self.has_valid_observations is False: self.read_field_list() return self._observations + @property + def has_valid_observations(self): + return len(self._observations.keys()) > 0 + @property def current_observation(self): """The observation that is currently selected by the scheduler @@ -131,10 +135,7 @@ def fields_file(self): @fields_file.setter def fields_file(self, new_file): - # Clear out existing list and observations - self.current_observation = None - self._fields_list = None - self._observations = dict() + self.clear_available_observations() self._fields_file = new_file if new_file is not None: @@ -161,9 +162,7 @@ def fields_list(self): @fields_list.setter def fields_list(self, new_list): - # Clear out existing list and observations - self._fields_file = None - self._observations = dict() + self.clear_available_observations() self._fields_list = new_list self.read_field_list() @@ -172,6 +171,14 @@ def fields_list(self, new_list): # Methods ########################################################################## + def clear_available_observations(self): + """Reset the list of available observations""" + # Clear out existing list and observations + self.current_observation = None + self._fields_file = None + self._fields_list = None + self._observations = dict() + def get_observation(self, time=None, show_all=False): """Get a valid observation @@ -219,6 +226,7 @@ def add_observation(self, field_config): if 'exp_time' in field_config: field_config['exp_time'] = float(field_config['exp_time']) * u.second + self.logger.debug("Adding {} to scheduler", field_config['name']) field = Field(field_config['name'], field_config['position']) try: @@ -256,7 +264,10 @@ def read_field_list(self): if self._fields_list is not None: for field_config in self._fields_list: - self.add_observation(field_config) + try: + self.add_observation(field_config) + except AssertionError: + self.logger.debug("Skipping duplicate field.") ########################################################################## # Utility Methods diff --git a/pocs/state/machine.py b/pocs/state/machine.py index aaa0b7bab..a964df40e 100644 --- a/pocs/state/machine.py +++ b/pocs/state/machine.py @@ -121,6 +121,7 @@ def run(self, exit_when_done=False, run_once=False): # If we are processing the states if self.do_states: + # If sleeping, wait until safe (or interrupt) if self.state == 'sleeping': if self.is_safe() is not True: @@ -129,7 +130,8 @@ def run(self, exit_when_done=False, run_once=False): try: state_changed = self.goto_next_state() except Exception as e: - self.logger.warning("Problem going to next state, exiting loop [{}]".format(e)) + self.logger.warning("Problem going from {} to {}, exiting loop [{!r}]".format( + self.state, self.next_state, e)) self.stop_states() break @@ -144,6 +146,14 @@ def run(self, exit_when_done=False, run_once=False): else: _loop_iteration = 0 + ######################################################## + # Note that `self.state` below has changed from above + ######################################################## + + # If we are in ready state then we are making one attempt + if self.state == 'ready': + self._obs_run_retries -= 1 + if self.state == 'sleeping' and self.run_once: self.stop_states() elif exit_when_done: @@ -170,7 +180,6 @@ def stop_states(self): """ Stops the machine loop on the next iteration """ self.logger.info("Stopping POCS states") self._do_states = False - self._retry_attemps = 0 def status(self): """Computes status, a dict, of whole observatory.""" diff --git a/pocs/state/states/default/parked.py b/pocs/state/states/default/parked.py index e7ee903b0..9824e5c81 100644 --- a/pocs/state/states/default/parked.py +++ b/pocs/state/states/default/parked.py @@ -4,5 +4,26 @@ def on_enter(event_data): pocs = event_data.model pocs.say("I'm parked now. Phew.") - pocs.say("Cleaning up for the night!") - pocs.next_state = 'housekeeping' + has_valid_observations = pocs.observatory.scheduler.has_valid_observations + + if has_valid_observations: + if pocs.is_safe(): + if pocs.should_retry is False or pocs.run_once is True: + pocs.say("Done retrying for this run, going to clean up and shut down!") + pocs.next_state = 'housekeeping' + else: + pocs.say("Things look okay for now. I'm going to try again.") + pocs.next_state = 'ready' + else: + pocs.say("Cleaning up for the night!") + pocs.next_state = 'housekeeping' + else: + if pocs.run_once is False: + pocs.say("No observations found. Going to stay parked for an hour then try again.") + pocs.sleep(delay=3600) # 1 hour = 3600 seconds + + pocs.reset_observing_run() + pocs.next_state = 'ready' + else: + pocs.say("Only wanted to run once so cleaning up!") + pocs.next_state = 'housekeeping' diff --git a/pocs/state/states/default/sleeping.py b/pocs/state/states/default/sleeping.py index 2ba014c4a..78595e5a6 100644 --- a/pocs/state/states/default/sleeping.py +++ b/pocs/state/states/default/sleeping.py @@ -1,14 +1,15 @@ def on_enter(event_data): """ """ pocs = event_data.model - pocs.next_state = 'ready' - # If it is dark and safe we shouldn't be in sleeping state - if pocs.is_dark() and pocs.is_safe(): - if pocs.should_retry is False: - pocs.say("Weather is good and it is dark. Something must have gone wrong. Stopping loop") - pocs.stop_states() - else: - pocs.say("Things look okay for now. I'm going to try again.") + if pocs.should_retry is False: + pocs.say("Weather is good and it is dark. Something must have gone wrong. " + + "Stopping loop.") + pocs.stop_states() else: - pocs.say("Another successful night!") + # Note: Unit will "sleep" before transition until it is safe + # to observe again. + pocs.next_state = 'ready' + pocs.reset_observing_run() + + pocs.say("Another successful night!") diff --git a/pocs/tests/test_dispatch_scheduler.py b/pocs/tests/test_dispatch_scheduler.py index 041a3c23d..9779bd9a6 100644 --- a/pocs/tests/test_dispatch_scheduler.py +++ b/pocs/tests/test_dispatch_scheduler.py @@ -115,14 +115,14 @@ def test_get_observation_reread(field_list, observer, temp_file, constraints): assert isinstance(best[1], float) # Alter the field file - note same target but new name - with open(temp_file, 'w') as f: + with open(temp_file, 'a') as f: f.write(yaml.dump([{ 'name': 'New Name', 'position': '20h00m43.7135s +22d42m39.0645s', - 'priority': 50 + 'priority': 5000 }])) - # Get observation but reread file + # Get observation but reread file first best = scheduler.get_observation(time=time, reread_fields_file=True) assert best[0] != 'HD 189733' assert isinstance(best[1], float) diff --git a/pocs/tests/test_pocs.py b/pocs/tests/test_pocs.py index 82f7109a3..a67554b7d 100644 --- a/pocs/tests/test_pocs.py +++ b/pocs/tests/test_pocs.py @@ -286,8 +286,9 @@ def test_run_no_targets_and_exit(pocs): pocs.state = 'sleeping' pocs.initialize() + pocs.observatory.scheduler.clear_available_observations() assert pocs.is_initialized is True - pocs.run(exit_when_done=True) + pocs.run(exit_when_done=True, run_once=True) assert pocs.state == 'sleeping' @@ -312,41 +313,6 @@ def test_run_complete(pocs): assert pocs.state == 'sleeping' -def test_run_interrupt_with_reschedule_of_target(observatory): - def start_pocs(): - pocs = POCS(observatory, messaging=True) - pocs.logger.info('Before initialize') - pocs.initialize() - pocs.logger.info('POCS initialized, back in test') - pocs.observatory.scheduler.fields_list = [{'name': 'KIC 8462852', - 'position': '20h06m15.4536s +44d27m24.75s', - 'priority': '100', - 'exp_time': 2, - 'min_nexp': 1, - 'exp_set_size': 1, - }] - pocs.run(exit_when_done=True, run_once=True) - pocs.logger.info('run finished, powering down') - pocs.power_down() - - pub = PanMessaging.create_publisher(6500) - sub = PanMessaging.create_subscriber(6511) - - pocs_process = Process(target=start_pocs) - pocs_process.start() - - while True: - msg_type, msg_obj = sub.receive_message() - if msg_type == 'STATUS': - current_state = msg_obj.get('state', {}) - if current_state == 'pointing': - pub.send_message('POCS-CMD', 'shutdown') - break - - pocs_process.join() - assert pocs_process.is_alive() is False - - def test_run_power_down_interrupt(observatory): def start_pocs(): pocs = POCS(observatory, messaging=True)