from __future__ import absolute_import import time import datetime from celery.task import Task #from django.core.exceptions import ObjectDoesNotExist #from django.db.models import Q from django.shortcuts import get_object_or_404 from django.conf import settings as djangosettings import utils.Logger as L ''' import observation_manager import observation_manager.tasks import scheduler import scheduler.tasks as sched_task import monitoring.tasks import alert_manager.tasks ''' #from common.models import * from common.models import Config, Log, PlcDeviceStatus from dashboard.views import get_sunelev from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault ''' 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 * ''' 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): FOR_REAL = True 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, FOR_REAL:bool=True): ''' FOR_REAL: set to False if you don't want Majordome to send commands to devices ''' self.FOR_REAL = FOR_REAL #self.createTask() #self.updateSoftware() #self.setContext() #self.setTime() /// OLD Majordome #self.setTasks() #self.loop() print("FOR REAL ?", self.FOR_REAL) 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 if self.FOR_REAL: 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 if self.FOR_REAL: 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: if self.FOR_REAL: # 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 """