from __future__ import absolute_import from celery.task import Task from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q import observation_manager import observation_manager.tasks import scheduler import scheduler.tasks as sched_task import utils.Logger as L import monitoring.tasks import alert_manager.tasks from common.models import * from dashboard.views import get_sunelev from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault from django.shortcuts import get_object_or_404 from devices.CameraNIR import NIRCameraController from devices.CameraVIS import VISCameraController from devices.Dome import DomeController from devices.PLC import PLCController from devices.Telescope import TelescopeController from majordome.MajordomeDecorators import * from utils.JDManipulator import * import time from django.conf import settings as djangosettings from threading import Thread DEBUG_FILE = False log = L.setupLogger("MajordomeTaskLogger", "Majordome") ''' Task to handle the execution of the program check the environment status in database check the devices status (telescope / cameras) check if the last schedule made has to be planned launch schedule's sequences ''' class Majordome(Task): # (EP) do this so that Majordome can be run from a thread, and called with thread.start(): #class Majordome(Task, Thread): loop_speed = 1 julian_div = 86400 executing_sequence = None next_sequence = None status_tel = "" status_nir = "" status_vis = "" status_dom = "" site_status = "OK" weather_status = "OK" timers = {} functions = {} schedule = None available_status = [] alarm_list = [] # New variables from TP: config = None plc_status = None current_state = "NONE" # OCS-RESTART, OCS-SHUTDOWN, or NONE (default) closing_mode = "NONE" NEED_TO_CLOSE = False PLC_IS_SAFE = False ''' OLD //// Function called by celery task Behavior: Init telescope / cameras set night limits check the software version launch the majordome loop ///// OLD Main loop of Majordome startup everything if needed Loop over Majordome state Change behavior with the pyros states Take suitable decision thanks to states in database ''' def run(self): #self.createTask() #self.updateSoftware() #self.setContext() #self.setTime() /// OLD Majordome #self.setTasks() #self.loop() print("DB3 used is:", djangosettings.DATABASES['default']['NAME']) while True: # SETUP try : self.config = get_object_or_404(Config, id=1) # By default, set mode to SCHEDULER (False = REMOTE, which should never be the default) self.config.global_mode = True self.config.save() #self.config = Config.objects.get(pk=1) #self.config = Config.objects.get()[0] #print("maj config id is", self.config.id) #except Config.ObjectDoesNotExist: except Exception as e: print("Config read (or write) exception", str(e)) return -1 #self.config.ntc = False # Set STATE to STARTING self.changeState("STARTING") ''' if not self.config.majordome_restarted: #in order to start other agents etc.... self.setup() # Set STATE to PASSIVE (NO PLC) self.changeState("Passive No PLC") print("Waiting for PLC connection") # Wait until PCL is connected while not self.plc_is_connected(): pass print("PLC is connected") # Set STATE to PASSIVE (with PLC) self.changeState("Passive") self.config.majordome_state = "RUNNING" # MAIN LOOP: iterate on current state #while self.plc_is_connected() != False and self.closing_mode == "RUNNING" : while self.plc_is_connected() and not self.is_shuttingdown_or_restarting(): # Call behavior for the current state and set new current state (can stay the same if no state change) #self.behavior[self.current_state](self) self.current_state = self.behavior[self.current_state](self) time.sleep(2) ''' while True: self.reload_config() if self.config.majordome_state == "STOP": break if self.config.majordome_state in ("OCS-RESTART", "OCS-SHUTDOWN"): if self.config.majordome_state == "OCS-SHUTDOWN": self.NEED_TO_CLOSE=True self.closing_mode = self.config.majordome_state self.config.majordome_state = "RUNNING" self.config.save() if self.current_state in ['STARTING', 'PASSIVE_NO_PLC', 'PASSIVE']: if self.is_shuttingdown_or_restarting() and not self.NEED_TO_CLOSE: break ''' if self.current_state not in ['STARTING', 'PASSIVE_NO_PLC']: if self.plc_is_not_auto(): self.changeState("PASSIVE") ''' self.current_state = self.do_behavior_for_current_state() # Shutdown options change by the main program if self.config.majordome_state == "STOP" or self.closing_mode == "OCS-SHUTDOWN": if self.config.majordome_state == "STOP": print("STOP Majordome agent from DB") self.config.majordome_state = "RUNNING" self.changeState("KILLED") #self.config.save() #self.shutdown() #self.closeBehaviour(self) #self.sub_behavior_closing() break if self.closing_mode == "OCS-RESTART": self.config.majordome_restarted = True self.config.save() self.closing_mode = "NONE" #TODO: self.kill_all_agents() #self.send_alarm_if_not_closed() #self.run() ''' elif not self.plc_is_connected(): #self.config.majordome_restarted = True #self.config.save() self.changeState("Passive No PLC") self.send_alarm_if_not_closed() #self.run() ''' self.shutdown() def shutdown(self): #TODO: write shutdown code (kill agents...) print("OCS SHUTDOWN") def is_restarting(self): return self.closing_mode == "OCS-RESTART" def is_shuttingdown(self): return self.closing_mode == "OCS-SHUTDOWN" def is_shuttingdown_or_restarting(self): return self.closing_mode != "NONE" ''' Function called to change and save current state of pyros. ''' def changeState(self, state): # (local variable) idem self.config.pyros_state Log.objects.create(message="Go from " + self.current_state + " to " + state, agent="Majordome") self.current_state = state # (in DB) PASSIVE, STANDBY, REMOTE, OPERATING self.config.pyros_state = state self.config.save() def _plc_is_not_auto(self): if not self.plc_is_connected(): return True; # now, self.plc_status has been updated, so check it: return self.plc_status.plc_mode != "AUTO" def plc_is_auto(self): return not self._plc_is_not_auto() def plc_is_safe(self): if not self.plc_is_connected(): return False; # now, self.plc_status has been updated, so check it: return self.plc_status.is_safe def is_night(self): return get_sunelev() < -10 ''' Each function with behavior describes the behavior of a state, they are all contained in a dictionary called in run() ''' def behavior_starting(self): #print("STARTING") #if self.is_shuttingdown_or_restarting(): return if not self.config.majordome_restarted: #TODO: Do setup things... (start agents...) time.sleep(2) ''' Cette variable sert à indiquer l'état du majordome dans la DB. Elle va aussi être changée par le panneau Simulator pour passer à l'état SHUTDOWN ou RESTART. Elle est récupérée à chaque itération par la fonction plcConnection() ''' self.config.majordome_state = "RUNNING" #self.config.majordome_state = "NONE" self.changeState("PASSIVE_NO_PLC") #return self.current_state def behavior_passive_no_plc(self): #print("PASSIVE_NO_PLC") #if self.is_shuttingdown_or_restarting(): return #self.changeState("Passive No PLC") print("Waiting for PLC connection") # Wait until PCL is connected while not self.plc_is_connected(): time.sleep(3) ''' if self.is_restarting(): self.changeState("STARTING") return ''' self.reload_config() if self.config.majordome_state == "STOP" or self.is_shuttingdown_or_restarting(): return print("PLC is connected") # Set STATE to PASSIVE (with PLC) self.changeState("PASSIVE") #return self.current_state def behavior_passive(self): #print("Passive") #if self.is_shuttingdown_or_restarting(): return #if self.plc_status.plc_mode == "AUTO": if not self.plc_is_connected(): self.changeState("PASSIVE_NO_PLC") return if self.plc_is_auto(): #if not self.config.majordome_restarted: self.config.ntc = True if not self.config.majordome_restarted: self.NEED_TO_CLOSE=True self.config.majordome_restarted = False self.changeState("Standby") #return self.current_state def behavior_standby(self): #print("Standby") #if self.config.ntc: if self.is_shuttingdown() and not self.NEED_TO_CLOSE: self.NEED_TO_CLOSE = True if self.NEED_TO_CLOSE: self.changeState("Closing") #self.sub_behavior_closing() self.do_behavior_for_current_state() self.changeState("Standby") #elif self.plc_status.plc_mode != "AUTO" or self.is_shuttingdown_or_restarting(): if not self.plc_is_auto() or self.is_shuttingdown_or_restarting(): self.changeState("PASSIVE") elif not self.config.global_mode and not self.config.lock: self.changeState("Remote") elif self.is_night() and self.plc_is_safe() and self.config.ack and not self.config.lock : self.changeState("Startup") #return self.current_state def behavior_remote(self): #print("Remote") if self.config.global_mode or self.is_shuttingdown_or_restarting() or not self.plc_is_auto() or self.config.lock: if not self.plc_is_auto() or self.config.lock or self.is_shuttingdown(): self.NEED_TO_CLOSE=True self.changeState("Standby") return #TODO: get shutter state from db status (then wait until close/open) if not self.plc_is_safe() and self.PLC_IS_SAFE: PLC_IS_SAFE = False response = TelescopeRemoteControlDefault("DOME SHUTTER CLOSE", expert_mode=True).exec_command() if self.plc_is_safe() and not self.PLC_IS_SAFE: PLC_IS_SAFE = True response = TelescopeRemoteControlDefault("DOME SHUTTER OPEN", expert_mode=True).exec_command() #return self.current_state def behavior_startup(self): #print("Startup") if self.config.lock or not self.plc_is_auto(): self.NEED_TO_CLOSE=True self.changeState("Standby") return #TODO: get shutter and telescope from db status time.sleep(5) ''' if not (dome_shutter == Open and telescope == Ready) TelescopeRemoteControlDefault("DO DOME SHUTTER OPEN", expert_mode=True).exec_command() TelescopeRemoteControlDefault("DO START", expert_mode=True).exec_command() ''' self.changeState("Scheduler") #return self.current_state def behavior_scheduler(self): #print("Scheduler") if not self.is_night() or not self.plc_status.is_safe or self.config.lock or not self.plc_is_auto() or not self.config.global_mode or self.is_shuttingdown_or_restarting(): if not ( not self.config.global_mode or self.is_restarting() ): self.NEED_TO_CLOSE = True self.changeState("Standby") #return self.current_state def sub_behavior_closing(self): #print("CURRENT OCS (MAJORDOME) STATE: "+ self.current_state) #print("Closing") #self.config.save() if not self.plc_is_auto(): # PLC not AUTO => we can do nothing, so only send email if dome not closed... self.send_alarm_if_not_closed() else: # These commands should do nothing if instruments are already closed/parked... response = TelescopeRemoteControlDefault("DO DOME SHUTTER CLOSE", expert_mode=True).exec_command() response = TelescopeRemoteControlDefault("DO DOME PARK", expert_mode=True).exec_command() response = TelescopeRemoteControlDefault("DO TELESCOPE PARK", expert_mode=True).exec_command() self.NEED_TO_CLOSE = False #self.changeState("Standby") #return self.current_state behavior = { "STARTING": behavior_starting, "PASSIVE_NO_PLC": behavior_passive_no_plc, "PASSIVE": behavior_passive, "Standby": behavior_standby, "Remote": behavior_remote, "Startup": behavior_startup, "Scheduler": behavior_scheduler, #"Closing": sub_behavior_closing, } def do_behavior_for_current_state(self) -> str: print("CURRENT OCS (MAJORDOME) STATE: " + self.current_state) time.sleep(2) # EXIT if PLC not connected #if not self.plc_is_connected(): return # EXIT if closing or restarting #if self.is_shuttingdown_or_restarting(): return # run behavior for this state #self.behavior[current_state](self) if self.current_state == "Closing": self.sub_behavior_closing() else: self.behavior[self.current_state](self) return self.current_state ''' Function called to send alarms is the site isn't closed (Empty for the moment) ''' def send_alarm_if_not_closed(self): pass ''' Function called by run to wait for PLC connection before and during the loop (also gathering needed info) ''' def plc_is_connected(self): try : self.config = Config.objects.get(pk=1) self.plc_status = PlcDeviceStatus.objects.exclude(plc_mode=None).latest('created') timeout = (datetime.datetime.now() - self.plc_status.created).total_seconds() if timeout >= self.config.plc_timeout_seconds: return (False) return (True) except Config.DoesNotExist or PlcDeviceStatus.DoesNotExist: return (False) def reload_config(self): self.config = Config.objects.get(pk=1) ''' try : self.config = Config.objects.get(pk=1) except Config.DoesNotExist: return (False) return self.config ''' """ OLD MAJORDOME CODE ''' Check if the instrument status is valid ''' def isValidStatus(self, status): # TODO REMOVE COMMENT AND CHANGE WHEN DEFINED # if (status == "" or status == "ERROR" or status == "FAILED" or status == "NOT_SET"): # return (False) return (True) def setContext(self): self.tel = TelescopeController() self.vis_camera = VISCameraController() self.nir_camera = NIRCameraController() self.plc = PLCController() self.dom = DomeController() return (0) def createTask(self): try: # (EP) NO find() method available from Django ORM !!!! #TaskId.objects.find(task="majordome").delete() TaskId.objects.filter(task="majordome").delete() except Exception as e: log.info(str(e)) return 1 TaskId.objects.create(task_id=self.request.id, task="majordome") return 0 def setTasks(self): try: self.monitoring_task = TaskId.objects.get(task="monitoring") self.alert_task = TaskId.objects.get(task="alert_manager") except Exception as e: self.monitoring_task = None self.alert_task = None return 0 ''' Reads the softwares versions in the settings.py, store them in the DB and send them to the IC. ''' def updateSoftware(self): versions = settings.MODULES_VERSIONS for module, version in versions.items(): same_module_versions = Version.objects.filter(module_name=module) if same_module_versions.count() == 0: Version.objects.create(module_name=module, version=version) elif same_module_versions.order_by("-created")[0].version != version: Version.objects.create(module_name=module, version=version) return (0) ''' Loop to wait for the device to be idle with the starting configurations. ''' def waitDevices(self): while self.status_vis == "" and self.status_tel == "" and self.status_nir == "" and self.status_dom == "": self.status_vis = self.vis_camera.getStatus() self.status_nir = self.nir_camera.getStatus() self.status_tel = self.tel.getStatus() self.status_dom = self.dom.getStatus() return (0) ''' Computes the beginning and the end of the following (or current) night set the timers -> maybe put timers in a config file ? ''' def setTime(self): self.night_start = getNightStart() self.night_end = getNightEnd() self.night_start_jd = secondsToJulianDate(getNightStart()) self.night_end_jd = secondsToJulianDate(getNightEnd()) self.timer_night_start = self.night_start - getCurrentTime() self.timer_night_end = self.night_end - getCurrentTime() self.timer_status = 5 self.tasks_timer = 5 self.timer_plc = 2 self.alert_timer = 1 self.timer_schedule = 1 self.timer_sequence = 1 if (self.night_start - 120 > getCurrentTime()): self.timer_night_start = self.night_start - 120 - getCurrentTime() self.timer_night_end = self.night_end - getCurrentTime() if (getCurrentTime() > self.night_start): self.adaptTimers() self.timers = { "status": self.timer_status, "environment": self.timer_plc, "night_start": self.timer_night_start, "night_end": self.timer_night_end, "schedule": self.timer_schedule, "sequence": self.timer_sequence, "tasks": self.tasks_timer } if (settings.DEBUG and DEBUG_FILE): log.info("Majordome started with timers : " + str(self.timers)) # Functions called during the loop self.functions = { "status": self.handleStatusTimer, "environment": self.handleEnvironmentTimer, "night_start": self.handleNightStartTimer, "night_end": self.handleNightEndTimer, "schedule": self.handleScheduleTimer, "sequence": self.handleSequenceTimer, "tasks": self.handleTasks } return (0) ''' Function called by the main loop to handle the task event (check monitoring and alert_manager) ''' def handleTasks(self): if not settings.USE_CELERY: return 0 self.timers["tasks"] = self.tasks_timer if self.monitoring_task is None: try: self.monitoring_task = TaskId.objects.get(task="monitoring") except Exception as e: monitoring.tasks.Monitoring.apply_async() if settings.DEBUG and DEBUG_FILE: log.info(str(e)) if self.alert_task is None: try: self.alert_task = TaskId.objects.get(task="alert_manager") except Exception as e: alert_manager.tasks.AlertListener.apply_async() if settings.DEBUG and DEBUG_FILE: log.info(str(e)) return 0 # TODO adapt timers if the majordome is started during the night or not ? def adaptTimers(self): pass def logDB(self, message: str): Log.objects.create(agent="Majordome", message=message) ''' Infinite loop according to the majordome behavior ''' def loop(self): while (self.closing_mode != "OCS-SHUTDOWN"): print("(MAJOR): start new iteration") minimal_timer = min(self.timers, key=self.timers.get) if (self.timers[minimal_timer] > 0): time.sleep(self.timers[minimal_timer]) self.timers = {key: value - self.timers[minimal_timer] for key, value in self.timers.items()} for timer_name, timer_value in self.timers.items(): if (timer_value <= 0): if timer_name in self.functions: self.logDB("Executing timer " + str(timer_name)) self.functions[timer_name]() else: if (settings.DEBUG and DEBUG_FILE): log.info("Timer : " + str(timer_name) + "is not known by the Majordome") self.logDB("Timer " + str(timer_name) + " unknown") if (settings.DEBUG and DEBUG_FILE): log.info("Timer : " + str(timer_name) + " executed") # EP added because loop is too quick (without CELERY) if not settings.USE_CELERY: time.sleep(2) return (0) ''' Function called by the main loop to handle environment event (PLC info) ''' def handleEnvironmentTimer(self): self.timers["environment"] = self.timer_plc self.handlePLC() return (0) ''' Function called by the main loop to handle the devices status ''' def handleStatusTimer(self): self.timers["status"] = self.timer_status self.status_tel = self.tel.getStatus() self.status_nir = self.nir_camera.getStatus() self.status_vis = self.vis_camera.getStatus() self.status_dom = self.dom.getStatus() self.handleStatus() return 0 ''' Function called by the main loop to check if the executing sequence is finished ''' def handleSequenceTimer(self): self.timers["sequence"] = self.timer_sequence if (self.executing_sequence): self.handleSequence(self.executing_sequence[0], self.executing_sequence[1], self.executing_sequence[2]) return (0) ''' Function called by the main loop to check if there is a new schedule and to execute its sequences ''' def handleScheduleTimer(self): self.timers["schedule"] = self.timer_schedule if (self.isValidStatus(self.status_tel)): if (self.schedule is None): try: self.schedule = Schedule.objects.latest('created') except ObjectDoesNotExist: if (settings.DEBUG and DEBUG_FILE): log.info("No schedule found in database") return (1) else: try: schedule = Schedule.objects.latest('created') except ObjectDoesNotExist: if (settings.DEBUG and DEBUG_FILE): log.info("No schedule found in database") return (1) if (schedule.created != self.schedule.created): self.next_sequence = None self.schedule = schedule self.firstSequenceIsAlert() if (self.schedule): shs_list = self.schedule.shs.filter(Q(status=Sequence.PLANNED) | Q(status=Sequence.PENDING)).order_by('tsp') self.executeSchedule(shs_list) else: self.notifyTelescopeStatus("scheduler") return (0) ''' Function called by handleScheduleTimer, the purpose is to kill the executing sequence if the first sequence in the new planning is from an alert ''' @executingSequenceExist def firstSequenceIsAlert(self): shs_list = self.schedule.shs.filter(Q(status=Sequence.PLANNED) | Q(status=Sequence.PENDING)).order_by('tsp') if shs_list and shs_list[0].sequence.is_alert: if shs_list[0].sequence.created.date() >= (datetime.datetime.now() - datetime.timedelta(seconds=10)).date(): self.killExecutingSequence() return 1 return 0 ''' Function called by the main loop to handle the end of a night ''' def handleNightEndTimer(self): self.timers["night_end"] = getNightEnd() if (self.isValidStatus(self.status_tel)): #observation_manager.tasks.night_calibrations.apply_async() if settings.USE_CELERY: print("MJ: call observation_manager WITH CELERY") observation_manager.tasks.night_calibrations.apply_async() else: print("MJ: call observation_manager WITHOUT CELERY") observation_manager.tasks.night_calibrations().run() else: self.notifyTelescopeStatus("night_end") return (0) ''' Function called by the main loop to handle the beginning of a night ''' def handleNightStartTimer(self): self.timers["night_start"] = getNextNightStart() if self.isOutsideOk(): self.dom.open() self.vis_camera.open_shutter() self.nir_camera.open_shutter() #scheduler.tasks.scheduling.apply_async((False, False)) if settings.USE_CELERY: print("MJ: call schedule WITH CELERY") scheduler.tasks.scheduling.apply_async((False, False)) else: print("MJ: call schedule WITHOUT CELERY") scheduler.tasks.scheduling().run((False, False)) return (0) def notifyTelescopeStatus(self, timer_name): return (self.notifyDeviceStatus("telescope", timer_name, self.status_tel)) def notifyDeviceStatus(self, device_name, timer_name, status): Log.objects.create(agent=device_name, created=datetime.datetime.now(), message="The action : " + str(timer_name) + " has been canceled : Telescope status : " + str(status)) # TODO MAYBE reset some variables and do a scheduling return (0) ''' Execute a schedule ''' def executeSchedule(self, shs_list): for shs in shs_list: # shs_list is sorted by tsp if (self.executableSequence(shs.sequence.status) and self.observable(shs.sequence)): if self.next_sequence is None: self.setNextSequence(shs, shs.sequence) if self.isExecutable() and self.executing_sequence is None: if self.isValidTimer(self.next_sequence[0]): if self.executeSequence(self.next_sequence[0], self.next_sequence[1]) == -1: return -1 if self.next_sequence[0] != shs and self.next_sequence[1] != shs.sequence: self.setNextSequence(shs, shs.sequence) else: self.next_sequence = None else: return 0 else: if (settings.DEBUG and DEBUG_FILE): log.info("Sequence cannot be executed : Not observable") self.logDB("Sequence "+shs.sequence.name+" cannot be executed : Not observable") return 0 def observable(self, sequence): if (sequence.jd2 - sequence.duration - secondsToPreciseJulianDate(getPreciseCurrentTime()) <= 0): return 0 return 1 def executableSequence(self, status): if status == Sequence.PLANNED or status == Sequence.PENDING: return 1 return 0 ''' Kill the executing sequence and set its state to cancelled ''' @executingSequenceExist def killExecutingSequence(self): shs = self.executing_sequence[0] sequence = self.executing_sequence[1] executing_plans = self.executing_sequence[2] shs.status = Sequence.CANCELLED sequence.status = Sequence.CANCELLED shs.save() sequence.save() for rev in executing_plans: if (not rev.failed() and not rev.successful()): rev.revoke(terminate=True) self.executing_sequence = None return 0 def reset(self, type): if type == "WEATHER": self.dom.open() #scheduler.tasks.scheduling.delay((False, False)) if settings.USE_CELERY: print("MJ: call schedule WITH CELERY") scheduler.tasks.scheduling.delay((False, False)) else: print("MJ: call schedule WITHOUT CELERY") scheduler.tasks.scheduling().run((False, False)) elif type == "INSIDE": #scheduler.tasks.scheduling.delay((False, False)) if settings.USE_CELERY: print("MJ: call schedule WITH CELERY") scheduler.tasks.scheduling.delay((False, False)) else: print("MJ: call schedule WITHOUT CELERY") scheduler.tasks.scheduling().run((False, False)) ''' Handle a new alarm (called by isInsideOk or isWeatherOk) ''' @SameAlarmCheck def handleAlarm(self, type, pos=-1): if type == "WEATHER": #TODO send email self.dom.close() self.killExecutingSequence() self.vis_camera.park() self.nir_camera.park() elif type == "INSIDE": #TODO send email self.killExecutingSequence() self.vis_camera.park() self.nir_camera.park() elif type == "ENDED": if len(self.alarm_list) > 0 and pos != -1: ended = self.alarm_list[pos] del self.alarm_list[pos] self.reset(ended) return 0 else: return 1 self.alarm_list.append(type) return 0 ''' for now weather_status and site_status contains something different than OK if the status is critical Later we may have other states to handle ''' def isOutsideOk(self) -> bool: self.handlePLC() if self.weather_status == "OK": if "WEATHER" in self.alarm_list: self.handleAlarm("ENDED", self.alarm_list.index("WEATHER")) return True self.handleAlarm("WEATHER") return False ''' Check the telescope inside status ''' def isInsideOk(self) -> bool: self.handlePLC() if self.site_status == "OK": if "INSIDE" in self.alarm_list: self.handleAlarm("ENDED", self.alarm_list.index("INSIDE")) return True self.handleAlarm("INSIDE") return False def isDevicesOk(self) -> bool: if self.isValidStatus(self.status_tel) and self.isValidStatus(self.status_dom)\ and self.isValidStatus(self.status_vis) and self.isValidStatus(self.status_nir): return True return False def isExecutable(self) -> bool: if self.isValidStatus(self.status_tel) and self.isValidStatus(self.status_dom)\ and self.isOutsideOk() and self.isInsideOk(): return True return False ''' check if the sequence timer is valid for execution, also check if there is a scheduling task running ''' def isValidTimer(self, shs) -> bool: current_countdown = self.getCountdown(shs) if (current_countdown <= JulianSeconds(5)): try: task = TaskId.objects.filter(task="scheduling") if not task: return True return False except: return True return False ''' Launch the observation tasks NIR and VIS associated to a sequence ''' def executeSequence(self, shs, sequence): shs.status = Sequence.EXECUTING sequence.status = Sequence.EXECUTING shs.save() sequence.save() log.info("Executing sequence id = " + str(sequence.pk)) self.logDB("Executing sequence") plans_results = [] if sequence.albums.filter(detector__name="Cagire").exists(): if (self.isValidStatus(self.status_nir)): for plan in sequence.albums.get(detector__name="Cagire").plans.all(): #res = observation_manager.tasks.execute_plan_nir.apply_async((plan.id, float(self.getCountdown(shs)))) if settings.USE_CELERY: print("MJ: call observation_manager WITH CELERY") res = observation_manager.tasks.execute_plan_nir.apply_async( (plan.id, float(self.getCountdown(shs)))) else: print("MJ: call observation_manager WITHOUT CELERY") res = observation_manager.tasks.execute_plan_nir().run( (plan.id, float(self.getCountdown(shs)))) # JB TODO : is it still usefull ? # TaskId.objects.create(task_id=res.id, task="execute_plan") plans_results.append(res) else: self.notifyDeviceStatus("Cagire", "Sequence execution", self.status_nir) sequence.status = Sequence.CANCELLED shs.status = Sequence.CANCELLED shs.save() sequence.save() return (1) if sequence.albums.filter(detector__name="Visible camera").exists(): if (self.isValidStatus(self.status_vis)): for plan in sequence.albums.get(detector__name="Visible camera").plans.all(): #res = observation_manager.tasks.execute_plan_vis.apply_async((plan.id, float(self.getCountdown(shs)))) if settings.USE_CELERY: print("MJ: call observation_manager WITH CELERY") res = observation_manager.tasks.execute_plan_vis.apply_async((plan.id, float(self.getCountdown(shs)))) else: print("MJ: call observation_manager WITHOUT CELERY") res = observation_manager.tasks.execute_plan_vis().run((plan.id, float(self.getCountdown(shs)))) plans_results.append(res) else: self.notifyDeviceStatus("Camera visible", "Sequence execution", self.status_vis) sequence.status = Sequence.CANCELLED shs.status = Sequence.CANCELLED shs.save() sequence.save() return (1) self.executing_sequence = [shs, sequence, plans_results] return (0) ''' Set the next sequence ''' def setNextSequence(self, shs, sequence): sequence.status = Sequence.PENDING shs.status = Sequence.PENDING self.next_sequence = [shs, sequence] sequence.save() shs.save() return (0) ''' Check if the current sequence is finished ''' def handleSequence(self, shs, sequence, executing_plans): count = 0 for res in executing_plans: try: if res.successful() or res.failed(): count += 1 except Exception as e: if DEBUG_FILE and settings.DEBUG: log.info(str(e)) shs.status = Sequence.CANCELLED sequence.status = Sequence.CANCELLED shs.save() sequence.save() for rev in executing_plans: if (not rev.failed() and not rev.successful()): rev.revoke(terminate=True) self.executing_sequence = None return (-1) if count >= len(executing_plans): sequence.status = Sequence.EXECUTED shs.status = Sequence.EXECUTED sequence.save() shs.save() message = "Finished sequence " + str(sequence.pk) + " execution" Log.objects.create(agent="Majordome", message=message) self.executing_sequence = None return (0) ''' Function called to do an action with the devices status ''' def handleStatus(self): telescope = Telescope.objects.first() camera_nir = Detector.objects.get(name="Cagire") camera_vis = Detector.objects.get(name="Visible camera") dome = Dome.objects.get(name="Dome") dome.status = self.status_dom telescope.status = self.status_tel camera_nir.status = self.status_nir camera_vis.status = self.status_vis dome.save() telescope.save() camera_nir.save() camera_vis.save() self.logDB("Instrument status : dome = " + str(self.status_dom) + ", telescope = " + str(self.status_tel) + ", camera_nir = " + str(self.status_nir) + ", camera_vis" + str(self.status_vis)) return (0) ''' Put the majordome in pause ''' def systemPause(self, duration, cause: str): self.logDB("System in pause for " + str(duration)) time.sleep(duration) #scheduler.tasks.scheduling.apply_async(first_schedule=False, alert=False) if settings.USE_CELERY: print("MJ: call schedule WITH CELERY") scheduler.tasks.scheduling.apply_async(first_schedule=False, alert=False) else: print("MJ: call schedule WITHOUT CELERY") scheduler.tasks.scheduling().run(first_schedule=False, alert=False) self.setTime() print("system has been paused. Cause : " + cause) return (0) ''' Function called to do an action with the site status and the wheather status ''' def handlePLC(self): try: site_status = SiteWatch.objects.latest('updated') weather_status = WeatherWatch.objects.latest('updated') self.weather_status = weather_status.global_status self.site_status = site_status.global_status except ObjectDoesNotExist as e: if (settings.DEBUG and DEBUG_FILE): log.info("No site_status or weather_status found in database : " + str(e)) # TODO shutdown everything return 1 return 0 ''' Gets the time before the expected start of the execution. ''' def getCountdown(self, shs): # TODO start sequence as soon as possible (a lot of verifications must be done there) current_time = secondsToPreciseJulianDate(getPreciseCurrentTime()) countdown = shs.tsp - current_time return countdown ''' Change observation conditions ''' def changeObsConditions(self): print("change_obs_conditions") pass """