Agent.py 10.2 KB
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_<agent-name>_vars : nom variable, value, desc
- table agents_commands : agent demandeur, agent dest, deposit_time (timestamp), durée validité (valeur par défaut), commande (format à définir), ack_time (timestamp)
(l'agent destinataire en profite pour supprimer les commandes périmées qui le concernent)

"""


# 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 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"


    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_abs_filename = os.path.abspath(self.CONFIG_DIR + os.sep + config_filename)
        #print("current path", os.getcwd())
        #print("this file path :", __file__)
        #print("config file path is", config_filename)
        # Instantiate an object for configuration
        self.config = ConfigPyros(config_abs_filename)
        if self.config.get_last_errno() != self.config.NO_ERROR:
            raise Exception(f"Bad config file name '{config_abs_filename}', error code {str(self.config.get_last_errno())}")


    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()
        #TODO: lire param start_mode (si ce param, alors changer le mode)

        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


    """
    =================================================================
    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()
        # --- get the parameter value of <mount1/localization/home>
        instantiation = 'mount1'
        section = 'localization'
        key = 'home'
        paramvalue = self.config.get_paramvalue(instantiation, section, key)
        print("paramvalue <{}/{}/{}> = {}".format(instantiation, section, key,paramvalue))

        """
        # 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...")

    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