tasks.py 11.7 KB
from __future__ import absolute_import
from django.conf import settings
from common.models import *

# (EP) OLD task base class
from celery.task import Task
# NEW task base class, but DOES NOT WORK (because celery3 and not 4 ?) !!!
#from celery import Task

from src.monitoring.plc_checker import PlcChecker
from devices.PLC import PLCController
from utils.JDManipulator import *
import json
import utils.Logger as L
import majordome.tasks
from celery import app, task
import alert_manager.tasks
from celery import shared_task
log = L.setupLogger("MonitoringTaskLogger", "Monitoring")

# EP
#DEBUG_FILE = False
DEBUG_FILE = True


'''
    Infinite task created at the program's start.
    Checks the plc status, parse it, analyse it, store it in db
'''


class Monitoring(Task):
    timers = {}
    functions = {}

    state = None
    plcController = None
    majordome = None
    alert_manager = None
    alert_task = None
    majordome_task = None
    timer_status = None
    timer_tasks = None
    status_plc = None
    plc_checker = PlcChecker()

    def run(self):
        print ("AGENT ENV: startup...")
        self.createTask()

        # (TRY TO) Connect to PLC
        # (non blocking if could not connect)
        self.setContext()
        print ("AGENT ENV: config PLC is (ip={}, port={})".format(self.plcController.ip, self.plcController.port))
        
        # (EP) self.setTime()
        self.setTimers()
        print("AGENT ENV: my timers (check env status every {}s, check other agents every {}s)".format(self.timer_status, self.timer_tasks))
        
        self.setTasks()
        print("AGENT ENV: Other Agents id read from DB (majordome={}, alert={})".format(self.majordome_task, self.alert_task))
        
        self.loop()


    def createTask(self):
        try:
            # (EP) NO find() method available from Django ORM, c'est du n'importe quoi ce code (lastminute.com ?) !!!! 
            #TaskId.objects.find(task="monitoring").delete()
            TaskId.objects.filter(task="monitoring").delete()
        except Exception as e:
            log.info(str(e))
            # (EP) return always if no monitoring task to delete, and so monitoring task id is never saved !!!
            #return 1
        TaskId.objects.create(task_id=self.request.id, task="monitoring")
        return 0

    def setTasks(self):
        try:
            self.majordome_task = TaskId.objects.get(task="majordome")
            self.alert_task = TaskId.objects.get(task="alert_manager")
        except Exception as e:
            self.majordome_task = None
            self.alert_task = None
        return 0

    def setContext(self):
        self.plcController = PLCController()
        self.state = "RUNNING"
        return (0)

    def setTimers(self):
        self.timer_status = 2
        self.timer_tasks = 5
        self.timers = {"timer_status": self.timer_status,
                       "timer_tasks": self.timer_tasks}
        # (EP)         "tasks": self.timer_tasks}
        # (EP) self.functions = {"timer_status": self.handleTimerStatus,
        self.functions = {"timer_status": self.handleStatus,
                          "timer_tasks": self.handleTasks}
        return (0)


    def logDB(self, message: str):
        Log.objects.create(agent='Monitoring', message=message)

        '''
            Function called by the main loop to check if the majordome and alert_manager are running
        '''


    def handleTasks(self):
        # Reset timer total duration
        # (EP) self.timers["tasks"] = self.timer_tasks
        self.timers["timer_tasks"] = self.timer_tasks
        # TODO check majordome and alert_manager status
        if self.majordome_task is None:
            try:
                # (EP) bugfix !!!
                #self.monitoring_task = TaskId.objects.get(task="majordome")
                self.majordome_task = TaskId.objects.get(task="majordome")
            except Exception as e:
                if settings.USE_CELERY:
                    try:
                        # Mind this will raise an Exception if RABBITMQ is not started !
                        majordome.tasks.Majordome.apply_async()
                        if settings.DEBUG and DEBUG_FILE:
                            log.info(str(e))
                    except Exception as e2:
                        print("YOU MUST START RABBITMQ before running PyROS !")
                        exit()
                else: print("USE_CELERY is false => do not create Majordome task")
                    
        if self.alert_task is None:
            try:
                self.alert_task = TaskId.objects.get(task="alert_manager")
            except Exception as e:
                # Mind this will raise an Exception if RABBITMQ is not started !
                if settings.USE_CELERY:
                    try:
                        alert_manager.tasks.AlertListener.apply_async()
                        if settings.DEBUG and DEBUG_FILE:
                            log.info(str(e))
                    except Exception as e2:
                        print("YOU MUST START RABBITMQ before running PyROS !")
                        exit()
                else: print("USE_CELERY is false => do not create AlertListener task")
        # EP non car 0 = false
        #return 0
        return True

    '''
        Check if all devices info are consitants
    '''

    def isStatusValid(self):
        return True

    '''
        Extract content from the status dictionary
    '''
    def extractFromDict(self, status):
        synthesis = {}
        devices = status["devices"]
        #print(devices)
        #is_safe_str = status["is_safe"]
        #mode = status["mode"]
        for device in devices:
            if device['valid'] == "yes":
                for value in device["device_values"]:
                    synthesis[value["name"]] = value["value"]
                    synthesis[value["name"] + "_unit"] = value["unit"]
        return synthesis

    # TODO ATTENTION SI DEUX DEVICES ONT LE MEME NOM
    # TODO SAVE SYNTHESIS
    def saveContent(self, content):
        devices = content[0]["devices"]
        print("saveContent")
        for device in devices:
            if device['valid'] == "yes":
                status = PlcDeviceStatus()
                try:
                    database_device = PlcDevice.objects.get(name=device["device_name"])
                except Exception as e:
                   # plc = Plc.objects.first()
                    database_device = PlcDevice.objects.create(name=device["name"])
                status.device = database_device
                for value in device["device_values"]:
                    status.setValue(value["name"], value["value"], value["unit"])
                    if   value["name"] == "OutsideTemp":
                        print(value["name"], value["value"])

                #status.setValue("mode", mode)
                #status.setValue("is_safe", is_safe_str)
                status.save()

    '''
        Parse status returned by plc
    '''
    def parseStatus(self, status_plc):
        try:
            status = {}
            dict = json.loads(status_plc)
            if dict[0]["entity_name"] == "PLC_STATUS":
                if self.isStatusValid():
                    status = self.extractFromDict(dict[0])
                    self.saveContent(dict)
                else:
                    # TODO HANDLE ERROR HERE
                    pass
        except Exception as e:

            if DEBUG_FILE and settings.DEBUG:
                log.info(str(e))
            self.status_plc = {}
            return 1
        self.status_plc = status
        return 0

    '''
        Check if string is ouside
    '''
    def isOutside(self, key):
        if key.find('Outside') != -1 or key.find('Rain') != -1\
                or key.find("Wind") != -1 or key.find("Sky") != -1:
            return True
        elif key == "Pressure":
            return True
        elif key == "DewPoint":
            return True
        return False

    def isInside(self, key):
        if key.find('Inside') != -1 or key.find('Door') != -1:
            return True
        elif key == "status":
            return True
        elif key == "current":
            return True
        return False

    '''
        Save status returned by plc
    '''
    def saveStatus(self):
        outside = WeatherWatch()
        inside = SiteWatch()
        for key, value in self.status_plc.items():
            if self.isOutside(key):
                outside.setAttribute(key, value)
            elif self.isInside(key):
                inside.setAttribute(key, value)
        outside.setGlobalStatus()
        inside.setGlobalStatus()
        outside.save()
        inside.save()
        #return 0

    def parseNewStatus(self,status_plc ):
        # """ PM 20181009 parse new status for config
        if status_plc.find('PLC_STATUS') >= 0:
            self.plc_checker.chk_config(status_plc)

    '''
        Function called by the main loop to handle the plc status
    '''
    def handleStatus(self):
        # Reset timer total duration
        self.timers["timer_status"] = self.timer_status
        status_plc = self.plcController.getStatus()
        # PM 20181009 parse new status for config
        self.parseNewStatus(status_plc)

        # Error while READING status ?
        if (self.plcController.isError(status_plc)):

            if (settings.DEBUG and DEBUG_FILE):
                log.info("Invalid PLC status returned (while reading) : " + str(status_plc))
                Log.objects.create(message="Invalid PLC status returned (while reading) : ", agent="Monitoring")
            # EP Non car 1 = true
            #return (1)

            return False

        # (EP) if parseStatus() = THERE WAS AN ERROR !!!
        # Error while PARSING status ?
        if self.parseStatus(status_plc):
            if (settings.DEBUG and DEBUG_FILE):
                log.info("Invalid PLC status returned (while parsing) : " + str(status_plc))
                Log.objects.create(message="Invalid PLC status returned (while parsing) : ", agent="Monitoring")
            # EP Non car 1 = true
            #return (1)
            return False
        print("Status received from PLC (read and parsed ok):")
        print(status_plc)
        #return self.saveStatus()
        #print(datetime.datetime.now())

        Log.objects.create(message="Status received from PLC (read and parsed ok):", agent="Monitoring")
        self.saveStatus()
        return True

    '''
        Main loop
    '''
    def loop(self):
        i = 0
        while (self.state != "SHUTDOWN"):
            print("-")
            print("-")
            print("-")
            print ("AGENT Monitoring (ENV): iteration "+str(i)+", (my state is " + self.state+") :")
            minimal_timer = min(self.timers, key=self.timers.get)
            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):
                        # Handle the timer function
                        self.functions[timer_name]()
                        if (settings.DEBUG and DEBUG_FILE):
                            log.info("Timer : " + str(timer_name) + " executed by monitoring")
                    else:
                        if (settings.DEBUG and DEBUG_FILE):
                            log.info("Timer : " + str(timer_name) + "is not known by the monitoring")
                        self.logDB("Timer " + str(timer_name) + " unknown")
                # EP : pas au bon endroit !!!
                #if (settings.DEBUG and DEBUG_FILE):
                    #log.info("Timer : " + str(timer_name) + " executed by monitoring")
            i+=1
        return (0)



if (__name__ == "__main__"):
    m = Monitoring()
    m.run()