VERSION = "0.3" #from __future__ import absolute_import import time import datetime import os """TODO: - 1 log par agent + lastlog (30 dernières lignes) - 1 table par agent - table agents_log avec log minimum pour affichage dans dashboard, et ordre chrono intéressant pour suivi activité : nom agent, timestamp, message - table agent__vars : nom variable, value, desc """ # 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 from config.configpyros import ConfigPyros """ 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 common.models import AgentsSurvey 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 Agent: # (EP) do this so that Majordome can be run from a thread, and called with thread.start(): # class Majordome(Task, Thread): name = "Generic Agent" FOR_REAL = True mainloop_waittime = 3 subloop_waittime = 2 status = None mode = None config = None # Statuses STATUS_LAUNCH = "LAUNCHED" STATUS_INIT = "INITIALIZING" STATUS_MAIN_LOOP = "IN_MAIN_LOOP" STATUS_PROCESS_LOOP = "IN_PROCESS_LOOP" STATUS_EXIT = "EXITING" # Modes MODE_ACTIVE = "ACTIVE" MODE_IDLE = "IDLE" DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml" CONFIG_DIR = "config" _agents_survey = None def __init__(self, name:str=None, config_filename:str=None): self.set_mode(self.MODE_IDLE) self.set_status(self.STATUS_LAUNCH) self.name = name if not config_filename: #config_filename = '/PROJECTS/GFT/SOFT/PYROS_SOFT/CURRENT/config/config_unit_simulunit1.xml' config_filename = self.DEFAULT_CONFIG_FILE_NAME #config_file_path, _ = os.path.split(config_filename) if config_filename == os.path.basename(config_filename): config_filename = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename) print("Config file used is", config_filename) #print("current path", os.getcwd()) #print("this file path :", __file__) #print("config file path is", config_filename) # Instantiate an object for configuration #print("config file path is ", config_abs_filename) self.config = ConfigPyros(config_filename) if self.config.get_last_errno() != self.config.NO_ERROR: raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") tmp = AgentsSurvey.objects.filter(name=self.name) if len(tmp) == 0: self._agents_survey = AgentsSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) #self._agents_survey = AgentsSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) #self._agents_survey.save() print("agent is", self._agents_survey) def __str__(self): return "I am agent " + self.name 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.load_config() self.init() ''' print() print(self) print("FOR REAL ?", self.FOR_REAL) print("DB3 used is:", djangosettings.DATABASES["default"]["NAME"]) # 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] except Exception as e: # except Config.ObjectDoesNotExist: print("Config read (or write) exception", str(e)) return -1 ''' # Main loop while True: self.set_status(self.STATUS_MAIN_LOOP) """ A chaque tour de boucle, remplir champ "iamalive" avec timestamp + durée validité (> temps iteration, n minutes par défaut) + nom agent dans table agents_survey (nom agent + mode + status + updated timestamp) """ print() print("Starting main loop iteration...") self.show_mode_and_status() self.load_config() self.update_db_survey() self.read_db_commands() ''' if self.config.majordome_state == "STOP": break if self.config.majordome_state == "RESTART": self.config.majordome_restarted = True self.closing_mode = self.config.majordome_state self.config.majordome_state = "RUNNING" self.config.save() ''' # Sub-level loop (only if ACTIVE) if self.is_active(): self.set_status(self.STATUS_PROCESS_LOOP) self.core_process() self.waitfor(self.mainloop_waittime) print("Ending main loop iteration...") #self.do_log(LOG_DEBUG, "Ending main loop iteration") def waitfor(self, nbsec): print(f"Now, waiting for {nbsec} seconds...") time.sleep(nbsec) def set_status(self, status:str): print(f"Switching from status {self.status} to status {status}") self.status = status def set_mode(self, mode:str): print(f"Switching from mode {self.mode} to mode {mode}") self.mode = mode def is_active(self): return self.mode == self.MODE_ACTIVE def set_active(self): self.set_mode(self.MODE_ACTIVE) def set_idle(self): self.mode = self.MODE_IDLE def show_mode_and_status(self): print(f"CURRENT MODE is {self.mode} (with status {self.status})") def die(self): self.set_status(self.STATUS_EXIT) """ suspend/resume """ def suspend(self): """ TODO: Mode IDLE (doit rester à l'écoute d'un resume, et doit continuer à alimenter les tables pour informer de son état via tables agents_logs, et lire table agents_command pour reprendre via resume, et update la table agents_survey pour donner son status "idle" """ self.set_idle() return True def resume(self): """ Quit suspend() mode """ self.set_active() return True def set_mode_from_config(self,agent_alias): # --- Get the startmode of the AgentX modestr = self.config.get_paramvalue(agent_alias,'general','startmode') if self.config.get_last_errno() != self.config.NO_ERROR: raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") if (modestr == None): return True # --- Set the mode according the startmode value mode = self.MODE_IDLE if modestr.upper() == 'RUN': mode = self.MODE_ACTIVE self.set_mode(mode) return True """ ================================================================= Generic methods that may be specialized (overriden) by subclasses ================================================================= """ def init(self): print("Initializing...") self.set_status(self.STATUS_INIT) def load_config(self): """ TODO: only si date fichier xml changée => en RAM, un objet Config avec méthodes d'accès, appelle le parser de AK (classe Config.py indépendante) """ print("Loading the config file...") #config_filename = 'c:/srv/develop/pyros/config/config_unit_simulunit1.xml' #config.set_configfile(config_filename) self.config.load() if self.config.get_last_errno() != self.config.NO_ERROR: raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") # --- display informations # --- Get all the assembly of this unit[0] (mount + channels) if self.config.is_config_contents_changed(): print("--------- Components of the unit -----------") print("Configuration file is {}".format(self.config.get_configfile())) alias = self.config.get_aliases('unit')[0] namevalue = self.config.get_paramvalue(alias,'unit','name') print("Unit alias is {}. Name is {}".format(alias,namevalue)) unit_subtags = self.config.get_unit_subtags() for unit_subtag in unit_subtags: aliases = self.config.get_aliases(unit_subtag) for alias in aliases: namevalue = self.config.get_paramvalue(alias,unit_subtag,'name') print("Unit {} alias is {}. Name is {}".format(unit_subtag,alias,namevalue)) print("------------------------------------------") #params = self.config.get_params(unit_alias) #for param in params: # print("Unit component is {}".format(param)) """ # self.config = Config.objects.get(pk=1) 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] except Exception as e: # except Config.ObjectDoesNotExist: # except Config.DoesNotExist: print("Config read (or write) exception", str(e)) # return self.config # return -1 return False """ def update_db_survey(self): print("Updating the survey database table...") self._agents_survey = AgentsSurvey.objects.get(name=self.name) self._agents_survey.mode = self.mode self._agents_survey.status = self.status self._agents_survey.save() def read_db_commands(self): print("Looking for new commands from the database ...") def do_log(self): """ log à 2 endroits ou 1 seul - in file - in db """ print("Logging data...") def core_process(self): """ Sublevel Loop (only if ACTIVE) : PLUS TARD, maybe :start_process_thread() dans un thread : ensuite, à chaque tour de boucle il regarde si c'est fini ou pas, et si fini recommence """ assert(self.is_active()) print("Starting the core process subloop...") self.waitfor(self.subloop_waittime) print("Ending core process subloop...") """ =================================== OLD FUNCTIONS TO BE REMOVED =================================== """ 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