예제 #1
0
class Supervisor(ChimeraObject):

    __config__ = {  "site"       : "/Site/0",
                    "telescope"  : "/Telescope/0",
                    "camera"     : "/Camera/0",
                    "dome"       : "/Dome/0",
                    "scheduler"  : None,
                    "domefan"    : None,
                    "weatherstations" : None,
                    "telegram-token": None,          # Telegram bot token
                    "telegram-broascast-ids": None,  # Telegram broadcast ids
                    "telegram-listen-ids": None,     # Telegram listen ids
                    "freq": 0.01  ,                  # Set manager watch frequency in Hz.
                    "max_mins": 10                   # Maximum time, in minutes, data from weather station should have
                 }

    def __init__(self):
        ChimeraObject.__init__(self)

        self._operationStatus = {
                                 "site":            InstrumentOperationFlag.UNSET,
                                 "telescope":       InstrumentOperationFlag.UNSET,
                                 "camera":          InstrumentOperationFlag.UNSET,
                                 "dome":            InstrumentOperationFlag.UNSET,
                                 "scheduler":       InstrumentOperationFlag.UNSET,
                                 "domefan":         InstrumentOperationFlag.UNSET,
                                 "weatherstation":  InstrumentOperationFlag.UNSET,
                                 }

        self._telegramBroadcast = False
        self._telegramSocket = None
        self._testIP = '8.8.8.8' # Use google's dns IP as beacon to network connectivity

        self._log_handler = None

        self.checklist = None
        self.machine = None
        self.bot = None


    def __start__(self):

        self._openLogger()

        # Connect to telegram, if info is given
        self.connectTelegram()

        self.checklist = CheckList(self)
        self.machine = Machine(self.checklist, self)

        # Connect to telescope events
        self._connectTelescopeEvents()

        # Connect to dome events
        self._connectDomeEvents()

        # Connect to scheduler events
        self._connectSchedulerEvents()

        self.setHz(self["freq"])

        self._broadcast_ids = None if self["telegram-broascast-ids"] is None \
            else [int(id) for id in str(self["telegram-broascast-ids"]).split(',')]

        self._listen_ids = None if self["telegram-listen-ids"] is None \
            else [int(id) for id in str(self["telegram-listen-ids"]).split(',')]

        self["weatherstations"] = self["weatherstations"].split(",")

    def __stop__(self):

        self.machine.state(State.SHUTDOWN)
        self.checklist.mustStop.set()

        if self.isTelegramConnected():
            self.disconnectTelegram()

        self._closeLogger()

    def control(self):

        # self.log.debug('[control] current status is "%s"'%(self._operationStatus["site"]))

        if self.machine.state() == State.IDLE:
            self.machine.state(State.START)
            return True
        # else:
        #     self.log.info("[control] current machine state is %s."%self.machine.state())

        if not self.machine.isAlive():
            self.machine.start()

        # Todo: after starting the checker machine, do some basic check of operational status.

        return True

    def wakeup(self):
        if self.machine.state() == State.IDLE:
            self.machine.state(State.START)
            return True
        else:
            return False

    def start(self):
        if self.machine.state() == State.OFF:
            self.machine.state(State.IDLE)
            return True
        else:
            return False

    def stop(self):
        if self.machine.state() != State.OFF:
            self.machine.state(State.STOP)
            return True
        else:
            return False

    def connectTelegram(self):

        if self["telegram-token"] is not None:
            self.bot = telegram.Bot(token=self["telegram-token"])

    def disconnectTelegram(self):
        pass

    def isTelegramConnected(self):
        return self._telegramSocket is not None

    def _openLogger(self):

        if self._log_handler:
            self._closeLogger()

        self._log_handler = logging.FileHandler(os.path.join(SYSTEM_CONFIG_DIRECTORY,
                                                             "supervisor.log"))

        # self._log_handler.setFormatter(logging.Formatter(fmt='%(asctime)s.%(msecs)d %(origin)s %(levelname)s %(name)s %(filename)s:%(lineno)d %(message)s'))
        self._log_handler.setFormatter(logging.Formatter(fmt='%(asctime)s[%(levelname)8s:%(threadName)s]-%(name)s-(%(filename)s:%(lineno)d):: %(message)s'))
        self._log_handler.setLevel(logging.DEBUG)
        self.log.addHandler(self._log_handler)

    def _closeLogger(self):
        if self._log_handler:
            self.log.removeHandler(self._log_handler)
            self._log_handler.close()

    def getLogger(self):
        return self._log_handler

    def broadCast(self,msg):
        if isinstance(msg,Exception):
            self.log.exception(msg)
            msg = repr(msg)
        else:
            self.log.info(msg)

        if self.bot is not None and self["telegram-broascast-ids"] is not None:
            for id in self._broadcast_ids:
                self.bot.sendMessage(chat_id=id,
                                     text=msg)

    def askWatcher(self,question,waittime):

        if self.bot is not None and self["telegram-listen-ids"] is not None:

            updates = self.bot.getUpdates()
            update_id=None
            for update in updates:
                update_id = updates[-1].update_id + 1

            self.log.debug('Asking lister %s.' % question)

            for id in self._listen_ids:
                self.bot.sendMessage(chat_id=id,
                                     text='[waittime: %i s] %s' %
                                          (waittime,
                                           question))

            start_time = time.time()
            while time.time() - start_time < waittime:

                updates = self.bot.getUpdates(offset = update_id)

                for update in updates:

                    if update.message.chat_id in self._listen_ids:
                        answer = update.message.text
                        if answer is not None:
                            return answer
                        update_id = update.update_id+1

            return None

    def site(self):
        return self.getManager().getProxy('/Site/0')

    def getTel(self):
        return self.getManager().getProxy(self["telescope"])

    def getSched(self):
        return self.getManager().getProxy(self["scheduler"])

    def getItems(self):
        return self.checklist.itemsList

    def getResponses(self):
        return self.checklist.responseList

    def getInstrumentList(self):
        return self._operationStatus.keys()

    def setFlag(self, instrument, flag, updatedb= True):
        if updatedb:
            if self.checklist.updateInstrumentStatus(instrument,flag):
                self._operationStatus[instrument] = flag
            else:
                raise StatusUpdateException("Could not update %s status with flag %s"%(instrument,
                                                                                       flag))
        else:
            self._operationStatus[instrument] = flag

    def getFlag(self,instrument):
        return self._operationStatus[instrument]

    def canOpen(self,instrument=None):
        """
        Checks if open operation are allowed in general or for a particular instrument. If none is given will only
        allow if all instruments have the open flag. Otherwise will check the instrument and site.

        :return:
        """

        if instrument is None:
            flag = True
            for inst_ in self._operationStatus.keys():
                flag = flag and (self.getFlag(inst_) == InstrumentOperationFlag.READY)
            return flag
        else:
            return (self.getFlag(instrument) == InstrumentOperationFlag.READY) and \
                   (self.getFlag("site") == InstrumentOperationFlag.READY)


    def lockInstrument(self,instrument,key):

        if self.checklist.updateInstrumentStatus(instrument,
                                                 InstrumentOperationFlag.LOCK,
                                                 key):
            self._operationStatus[instrument] = InstrumentOperationFlag.LOCK
        else:
            self.log.warning("Could not change instrument status.")

    def getInstrumentKey(self,instrument):
        if self.getFlag(instrument) == InstrumentOperationFlag.LOCK:
            return self.checklist.instrumentKey(instrument)
        else:
            return ""

    def unlockInstrument(self,instrument,key):

        if self.getFlag(instrument) != InstrumentOperationFlag.LOCK:
            self.log.debug("Instrument not locked")
            return False

        if self.checklist.updateInstrumentStatus(instrument,
                                                 InstrumentOperationFlag.CLOSE,
                                                 key):
            self._operationStatus[instrument] = InstrumentOperationFlag.CLOSE
            return True
        else:
            raise StatusUpdateException("Unable to unlock %s with provided key"%(instrument))

    def activate(self,item):
        self.checklist.activate(item)

    def deactivate(self,item):
        self.checklist.deactivate(item)

    def _connectTelescopeEvents(self):
        # Todo
        tel = self.getTel()
        if not tel:
            self.log.warning("Couldn't find telescope.")
            return False

        tel.slewBegin += self.getProxy()._watchSlewBegin
        tel.slewComplete += self.getProxy()._watchSlewComplete
        tel.trackingStarted += self.getProxy()._watchTrackingStarted
        tel.trackingStopped += self.getProxy()._watchTrackingStopped
        tel.parkComplete += self.getProxy()._watchTelescopePark
        tel.unparkComplete += self.getProxy()._watchTelescopeUnpark

        return True

    def _connectSchedulerEvents(self):

        sched = self.getSched()
        if not sched:
            self.log.warning("Couldn't find telescope.")
            return False

        sched.programBegin += self.getProxy()._watchProgramBegin
        sched.programComplete += self.getProxy()._watchProgramComplete
        # sched.actionBegin += self.getProxy()._watchActionBegin
        # sched.actionComplete += self.getProxy()._watchActionComplete
        sched.stateChanged += self.getProxy()._watchStateChanged


    def _disconnectTelescopeEvents(self):
        tel = self.getTel()
        if not tel:
            self.log.warning("Couldn't find telescope.")
            return False

        tel.slewBegin -= self.getProxy()._watchSlewBegin
        tel.slewComplete -= self.getProxy()._watchSlewComplete
        tel.trackingStarted -= self.getProxy()._watchTrackingStarted
        tel.trackingStopped -= self.getProxy()._watchTrackingStopped
        tel.parkComplete -= self.getProxy()._watchTelescopePark
        tel.unparkComplete -= self.getProxy()._watchTelescopeUnpark

    def _disconnectSchedulerEvents(self):

        sched = self.getSched()
        if not sched:
            self.log.warning("Couldn't find telescope.")
            return False

        sched.programBegin -= self.getProxy()._watchProgramBegin
        sched.programComplete -= self.getProxy()._watchProgramComplete
        # sched.actionBegin -= self.getProxy()._watchActionBegin
        # sched.actionComplete -= self.getProxy()._watchActionComplete
        sched.stateChanged -= self.getProxy()._watchStateChanged

    def _connectDomeEvents(self):
        # Todo
        pass

    def _disconnectDomeEvents(self):
        # Todo
        pass

    def _watchSlewBegin(self, target):
        self.setFlag("telescope",InstrumentOperationFlag.OPERATING)

    def _watchSlewComplete(self, position, status):
        pass

    def _watchTrackingStarted(self, position):
        # Todo
        pass

    def _watchTrackingStopped(self, position, status):
        self.setFlag("telescope",InstrumentOperationFlag.READY)

    def _watchTelescopePark(self):

        self.broadCast("Telescope parked")
        self.setFlag("telescope",InstrumentOperationFlag.CLOSE)
        self.setFlag("dome",InstrumentOperationFlag.CLOSE)

    def _watchTelescopeUnpark(self):

        self.broadCast("Telescope unparked")
        self.setFlag("telescope",InstrumentOperationFlag.READY)
        self.setFlag("dome",InstrumentOperationFlag.READY)

    def _watchProgramBegin(self,program):
        if self.getFlag("scheduler") != InstrumentOperationFlag.OPERATING:
            self.setFlag("scheduler",InstrumentOperationFlag.OPERATING)

    def _watchProgramComplete(self, program, status, message=None):
        if status == SchedStatus.ERROR:
            msg = "Scheduler in ERROR"
            if message is not None:
                msg += ": %s" % message
            self.broadCast(msg)
            self.setFlag("scheduler",InstrumentOperationFlag.ERROR)
            # should I take any action regarding the telescope or even the scheduler itself?
            # Maybe stop the telescope? park the telescope? Or, I could have an action that, if the scheduler is in
            # error it will ask if it should close the telescope. Then, in the next cycle the action will take effect
        elif status == SchedStatus.ABORTED:
            self.setFlag("scheduler",InstrumentOperationFlag.READY)
            if message is not None:
                self.broadCast('%s' % message)

    def _watchStateChanged(self, newState, oldState):

        if newState == SchedState.BUSY:
            self.setFlag("scheduler",InstrumentOperationFlag.OPERATING)
        else:
            self.setFlag("scheduler",InstrumentOperationFlag.READY)

    @lock
    def status(self,new=None):
        if not new:
            return self._operationStatus
        self._operationStatus = new
        return

    @event
    def statusChanged(self,old,new):
        '''
        Wake all calls for checking their conditions.
        :return:
        '''
        pass

    @event
    def checkBegin(self,item):
        pass

    @event
    def checkComplete(self,item,status):
        pass

    @event
    def itemStatusChanged(self,item,status):
        pass

    @event
    def itemResponseBegin(self,item,response):
        pass


    @event
    def itemResponseComplete(self,item,response,status):
        pass