#!/usr/bin/env python

#    "$Name:  $";
#    "$Header:  $";
#=============================================================================
#
# file :        PyAlarm.py
#
# description : Python source for the PyAlarm and its commands. 
#                The class is derived from Device. It represents the
#                CORBA servant object which will be accessed from the
#                network. All commands which can be executed on the
#                PyAlarm are implemented in this file.
#
# project :     TANGO Device Server
#
# $Author:  srubio at cells$
#
# $Revision:  $
#
# $Log:  $
#
# copyleft :   ALBA Synchrotron Light Facility
#                Barcelona
#                Europe
#
#=============================================================================
#          This file is generated by POGO
#    (Program Obviously used to Generate tango Object)
#
#         (c) - Software Engineering Group - ESRF
#=============================================================================

import sys,os,time,threading,traceback,re,collections,json, urllib

import PyTango
import fandango
import fandango.tango
import fandango.functional as fun
from fandango.functional import *
from fandango.log import except2str,shortstr
from fandango.objects import __lock__, self_locked,Singleton
from fandango.dicts import defaultdict,defaultdict_fromkey
try:
    from fandango.threads import WorkerProcess,getPickable
    PROCESS = True
except: 
    PROCESS = False

# It should work only in PyTango7
# if 'PyUtil' not in dir(PyTango):
    #PyTango.PyDeviceClass = PyTango.DeviceClass
    #PyTango.PyUtil = PyTango.Util

###############################################################################
# Checking Dependencies

import panic 
from panic.properties import *
from panic.engine import init_callbacks

try: 
  __RELEASE__ = panic.__RELEASE__
except Exception,e: __RELEASE__ = '6.?'
print '> PyAlarm %s'%__RELEASE__

try:
    try: import panic.extra.smslib as smslib
    except:
      try: import albasmslib as smslib
      except:
        import smslib
    SMS_ALLOWED=True
    print('Using smslib from %s'%smslib.__file__)
except Exception,e: 
    print('UNABLE TO LOAD SMSLIB ... SMS MESSAGING DISABLED: %s'%e)
    SMS_ALLOWED=False

try:
    from PyTangoArchiving import snap
    SNAP_ALLOWED=True
except Exception,e:
    print('UNABLE TO LOAD SNAP ... SNAP ARCHIVING DISABLED: %s'%e)
    SNAP_ALLOWED=False
#The device is not ready yet for Snapshoting
#SNAP_ALLOWED=False

USE_STATIC_METHODS = getattr(PyTango,'__version_number__',0)<722

#Setting size of tango DB query cache (for FIND macros)
fandango.tango.get_matching_devices.depth = 200 

###############################################################################

class SingletonTangoEval(fandango.tango.TangoEval,Singleton):
    #All processes must use the same TangoEval object
    pass

class AlarmHook(fandango.Object):
    """
    This class identifies an Action to be executed as response to an alarm.
    :param condition:   a boolean function that will return True if this action
                        is appliable to the given argument.

            e.g. (condition=lambda s: '@' in s) will apply for mail addresses
                        
    :param action:      function([tag,receivers,...]) to be executed when the 
                        action is applied.
    """
    def __init__(self,condition=None,action=None):
        self._condition = condition
        self._action = action
        pass
    def match(self,argument):
        """
        This method will apply the given condition to the argument. 
        Overridable in subclasses
        """
        return self._condition(argument)
    def apply(self,tag,receivers):
        """
        This method will apply the given action to the argument. 
        Overridable in subclasses
        """
        return self._action(tag,receivers)


#==================================================================
#   PyAlarm Class Description:
#
#         This device server is used as a alarm logger, it connects to the list of attributes provided and verifies its values.<br />
#         Its focused on notifying Alarms by log files, Mail, SMS and (some day in the future) electronic logbook.<br />
#         It allows to setup alarms based on attribute values, connection status and boolean combinations of them.<br />
#         Independent configuration and mailing lists available for each alarm.<br/>
#         You can acknowledge these alarms by a proper command.<br />
#         <p>This device requires <a href="http://www.tango-controls.org/Documents/tools/fandango/fandango">Fandango module<a> to be available in the PYTHONPATH.</p>
#
#==================================================================
#     Device States Description:
#
#   DevState.ON :     ActiveAlarms list is empty
#   DevState.ALARM :  Size of ActiveAlarms is >=1
#==================================================================


class PyAlarm(PyTango.Device_4Impl, fandango.log.Logger):

    #--------- Add your global variables here --------------------------

    Panic = None
    
    #--------------------------------------------------------------------------
    ##Overriding Quality-based Tango state machine
    def set_state(self,state):
        self._state = state
        PyTango.Device_4Impl.set_state(self,state)
    def get_state(self):
        #@Tango6
        #This have been overriden as it seemed not well managed when connecting devices in a same server
        return self._state
    def dev_state(self):
        #@Tango7
        #This have been overriden to avoid device servers with states managed by qualities
        return self._state
    def State(self):
        """ State redefinition is required to keep independency between 
        attribute configuration (max/min alarms) and the device State """
        #return self.get_state()
        return self._state    
    
    def set_status(self,status):
        self._status = status
        PyTango.Device_4Impl.set_status(self,status)
    def get_status(self):
        return self._status
    def dev_status(self):
        return self._status
    def Status(self):
        return self._status
    
    def StateMachine(self):
        ## STATUS CALCULLATION
        now = time.time()
        self.eval_status = ""
        try:
            self.debug('StateMachine()')
            _state = self.get_state()
            actives = list(reversed([(v.active,k) 
                        for k,v in self.Alarms.items() if v.active]))
            tlimit = time.time()-2*self.PollingPeriod
        
            if [a for a in self.Alarms if a not in self.DisabledAlarms] \
                    and 0<self.last_attribute_check<tlimit:
                self.set_state(PyTango.DevState.FAULT)
                msg = 'Alarm Values not being updated!!!\n\n'
                self.eval_status = msg+self.get_status().replace(msg,'')
                self.set_status(self.eval_status)
                
            elif self.worker and not (self.worker._process.is_alive() 
                                      and self.worker._receiver.is_alive()):
                self.set_state(PyTango.DevState.FAULT)
                self.eval_status = 'Alarm Values not being processed!!!'
                self.set_status(self.eval_status)
            else:
                if self.get_enabled():
                    self.set_state(PyTango.DevState.ALARM if actives 
                                   else PyTango.DevState.ON)
                    status = "There are %d/%d active alarms\n" % (
                            len(actives),len(self.Alarms))

                else:
                    self.set_state(PyTango.DevState.DISABLE)
                    status = ("Device is DISABLED temporarily (Enabled=%s)\n"
                              %self.Enabled)
                for date,tag_name in actives:
                    status+=('%s:%s:\n\t%s\n\tSeverity:%s\n\tSent to:%s\n' 
                             % (time.ctime(date),tag_name,
                                self.Alarms[tag_name].description,
                                self.Alarms[tag_name].severity,
                                self.Alarms[tag_name].receivers))
                if self.FailedAlarms:
                    status+='\n%d alarms couldnt be evaluated:\n%s'%(
                        len(self.FailedAlarms),
                        ','.join(str(t) for t in self.FailedAlarms.items()))
                    if (float(len(self.FailedAlarms))/len(self.Alarms) > 0.1
                        and fandango.isFalse(self.IgnoreExceptions)):
                      self.set_state(PyTango.DevState.FAULT)
                if self.Uncatched:
                    status+='\nUncatched exceptions:\n%s'%self.Uncatched
                    
                self.eval_status = "Last eval was %s at %s" % (
                                self.Eval.getter('TAG'),
                                time2str(self.Eval.getter('now')))                    
                #self.eval_status += 'EvalTimes are: \n %s\n'%(self.EvalTimes)
                self.set_status(status+self.eval_status)

            if self.get_state() != _state:
                self.push_change_event('State')
        except:
            self.warning( traceback.format_exc())
            self.set_state(PyTango.DevState.UNKNOWN) 
        finally:
            self.last_status = now
    
    def adm_poll_command(self,command,period):
        """
        this method cannot be called at init_device()
        """
        U = PyTango.Util.instance()
        admin = U.get_dserver_device()
        print(dir(admin))
        pattrs = self.get_polled_attrs()
        print(pattrs)
        #if att not in pattrs:
            #admin.add_obj_polling([[int(period)],[my_name,'attribute',att]])
        #else:
            #admin.upd_obj_polling_period([[int(period)],[my_name,'attribute',att]])
        
    
    #--------------------------------------------------------------------------
    
    def get_alarm_attribute(self,attr):

        if isString(attr):
            if attr in self.Alarms:
                tag_name = attr
            else:
                l = self.Alarms.get(attr)
                tag_name = l[0].get_attribute() if len(l) else attr

        elif hasattr(attr,'get_attribute'):
            tag_name = attr.get_attribute()

        else:
            tag_name = attr.get_name()

        return tag_name
    
    def alarm_attr_read(self,attr,t=0, push=True):
        """
        This is the method where you control the value assigned
        to each Alarm attributes
        """
        tag_name = self.get_alarm_attribute(attr)
            
        if tag_name not in self.Alarms:
            raise Exception('%s_AlarmDoesntExist' % tag_name)
        value = any(re.match(tag_name.replace('_','.')+'$',a) 
                    for a in self.get_active_alarms())
        self.debug('PyAlarm(%s).read_alarm_attribute(%s) is %s;'\
                    ' Active Alarms: %s' 
                % (self.get_name(),tag_name,value,self.get_active_alarms()))
        
        quality=PyTango.AttrQuality.ATTR_WARNING
        if tag_name in self.FailedAlarms or self.CheckDisabled(tag_name):
            quality=PyTango.AttrQuality.ATTR_INVALID
        elif(self.Alarms[tag_name].severity=='DEBUG'):
            quality=PyTango.AttrQuality.ATTR_VALID
        elif(self.Alarms[tag_name].severity=='WARNING'):
            quality=PyTango.AttrQuality.ATTR_WARNING
        elif(self.Alarms[tag_name].severity=='ALARM' 
             or self.Alarms[tag_name].severity=='ERROR'):
            quality=PyTango.AttrQuality.ATTR_ALARM
        else: quality=PyTango.AttrQuality.ATTR_WARNING

        t = t or time.time()        
        if push:
            try:
                self.info('read_%s(%s,%s): pushing event' % (
                    tag_name,value,quality))
                self.push_change_event(tag_name,value,t,quality)
            except:
                self.warning('read_%s(): unable to push events\n%s' % (
                    tag_name, traceback.format_exc()))
        
        # ORDER MATTERS!! IF THIS IS NOT DONE LAST, THERE's A SEGFAULT!
        if hasattr(attr,'set_value_date_quality'):
            attr.set_value_date_quality(value,t,quality)
    
    if USE_STATIC_METHODS: 
        alarm_attr_read = staticmethod(self_locked(alarm_attr_read))

    def alarm_attr_allowed(self,req_type):
        """
        Alarms should be always readable, the quality of the alarm 
        will change if needed
        """
        return True

    if USE_STATIC_METHODS: 
        alarm_attr_allowed = staticmethod(alarm_attr_allowed)

    def create_alarm_attribute(self,argin):
        aname = self.Alarms[argin].get_attribute()
        if aname in self.DynamicAttributes:
            self.info('PyAlarm(%s): attribute %s already exists' 
                      % (self.get_name(),aname))
        else:
            self.info('PyAlarm(%s): Creating attribute %s for %s alarm' 
                      % (self.get_name(),aname,argin))
            self.add_attribute(PyTango.Attr(aname,
                PyTango.ArgType.DevBoolean,PyTango.AttrWriteType.READ),
                self.alarm_attr_read,
                None, #self.write_new_attribute #(attr)
                self.alarm_attr_allowed
                )
            self.DynamicAttributes.append(aname)
            self.set_change_event(aname.lower(),True,False)
        return aname
        
    def update_locals(self,_locals=None,check=True,update=True):
        """
        This method is used to override _locals variables passed to TangoEval object.
        
        If check is True, It will also check which alarms are enabled and update their values accordingly.
        
        If update is False it will perform a dry run; without updating the cached values.
         
        """
        if _locals is None: _locals = {}
        if not isinstance(self.PhoneBook,dict):
            try:
                self.PhoneBook = self.Alarms.get_phonebook(
                    load = True, value = None)
                # PROPERTY VALUE IS DISCARDED BECAUSE
                # CLASS PROPERTIES ARE NOT RELOADED ON SECOND init() CALLS!
                self.debug('PhoneBook reloaded: [%d]' % len(self.PhoneBook))
            except:
                self.error('Unable to parse PhoneBook!\n' + traceback.format_exc())
        try:
            _locals.update(dict(zip('DOMAIN FAMILY MEMBER'.split(),
                                    self.get_name().split('/'))))
            _locals.update({'DEVICE':self.get_name(),
                            'ALARMS':self.Alarms.keys(),
                            'PHONEBOOK':self.Alarms.get_phonebook(),
                            'PANIC':self.Panic,
                            'SELF':self})
            _locals['t'] = time.time() - (self.TStarted + self.StartupDelay)
            if not check: update = True #If check is True locals will be updated only if necessary or forced
            if check:
                for k,v in self.Alarms.items():
                    val = v.active if not self.CheckDisabled(k) else False
                    if _locals.get(k,None)!=val: update = True
                    _locals[k] = val
            self.debug('In PyAlarm.update_locals(%s)'%update)
            if update:
                if self.worker:
                    try:
                        self.worker.send('update_locals',
                            target='update_locals',
                            args={'dct':dict((k,v) for k,v in _locals.items() if k in self.Panic)},
                            callback=None)
                    except: 
                        self.error('worker.send(update_locals) failed!: %s'%traceback.format_exc())
                        self.info(str(_locals))
                else: 
                    self.Eval.update_locals(_locals)
                    if check and self.Alarms.keys():
                        if self.get_name()+'/'+self.Alarms.keys()[0] not in self.Eval.attributes:
                          self.Eval.attributes.update(dict((str(n).lower(),fandango.tango.CachedAttributeProxy(n,fake=True))
                            for n in (self.get_name()+'/'+k for k in self.Alarms) ))
                        [self.Eval.attributes[self.get_name()+'/'+k].set_cache(_locals[k]) for k in self.Alarms]

        except:
            self.warning(traceback.format_exc())
        return _locals

    def dyn_attr(self):
        ## Dynamic Attributes Creator
        self.debug( '#'*40)
        self.info( 'In PyAlarm(%s).dyn_attr()'%self.get_name())
        alarms = self.Alarms.keys()
        if self.worker:
            # Done here to avoid subprocess triggering exceptions
            self.update_locals(
                dict.fromkeys(self.Panic.keys()),check=False,update=True) 
        
        for alarm in alarms:
            try:
                self.create_alarm_attribute(alarm)
                if self.worker: 
                    self.worker.add(alarm,'eval',
                        {'formula':self.Alarms[alarm].formula,'_raise':True},
                        period=self.PollingPeriod,
                        expire=self.AlarmThreshold*self.PollingPeriod)
            except:
                self.warning( 'Unable to create %s attribute'%alarm)
                self.warning( traceback.format_exc())
        self._initialized = True
        return

    ###########################################################################
    
    def parse_defines(self,argin,tag='$ALARM',message='',date=''):
        """
        Replaces keywords on receivers. Accepted keywords in actions are:
        
            $ALARM/$TAG/$NAME : Alarm name
            $DEVICE : PyAlarm name
            $DESCRIPTION : Description text
            $VALUES : last values evaluated for that alarm
            $SNAP : last values stored for that alarm
            $REPORT : full report sent when the alarm was raised
            $DATE/$DATETIME : current time as YYYYMMDD_hhmm
            $MESSAGE : type of alarm event (RESET,ALARM,REMINDER,...)
            $JSON : all the previous fields in a JSON dictionary
            
        """
        keys = {}
        keys['$ALARM'] = keys['$TAG'] = keys['$NAME'] = tag
        date = date or fandango.time2str().replace('-','').replace(':','').replace(' ','')
        keys['$DATE'] = keys['$DATETIME'] = date
        keys['$DEVICE'] = self.get_name().replace('/','_').replace('-','_').upper()
        keys['$MESSAGE'] = message

        obj = tag in self.Alarms and self.Alarms[tag] #don't use .get() here
        if obj:
          keys['$DESCRIPTION'] = obj.description
          if '$SNAP' in str(argin):
            keys['$SNAP'] = str(self.PastValues.get(obj.tag))          
          if '$VALUES' in str(argin):
            keys['$VALUES'] = str(self.LastValues.get(obj.tag))
          if '$REPORT' in str(argin):
            keys['$REPORT'] = '\n'.join(self.GenerateReport(obj.tag,message=message))
          if '$JSON' in str(argin):
            try:
              import json
              obj.get_active()
              obj.get_acknowledged()
              dct = fandango.obj2dict(obj)
              dct['values'] = self.PastValues.get(obj.tag)
              dct['message'] = message
              keys['$JSON'] = json.dumps(dct)
            except:
              self.error(traceback.format_exc())
        else:
          keys['$DESCRIPTION'] = keys['$VALUES'] = keys['$REPORT'] = ''
            
        def rep(s):
          for k,v in keys.items():
            if v:
              s = s.replace(k,v)
          return s

        argin = map(rep,argin) if isSequence(argin) else rep(argin)
        #for k,v in keys.items():
          #argin = [clsub('\'+k,v,c,lower=0) for c in argin]
        
        return argin
    
    def parse_receivers(self,tag_name='',filtre=False,receivers=None,message=''):
        '''
        Filters the Alarm receivers matching tag_name; also replaces addresses 
        entered in the PhoneBook 
        This method is called from free_alarm and send_alarm.
        '''
        if not receivers:
          if tag_name not in self.Alarms:
            return []
          else:
            receivers = self.Alarms[tag_name].receivers.split('#')[0]
            receivers += ','+self.Alarms.get_global_receivers(tag_name)
        
        if '%' in str(receivers):
          raw_receivers = ','.join(receivers) if isSequence(receivers) else receivers
          receivers = self.Alarms.parse_phonebook(raw_receivers)
          if receivers != raw_receivers: 
            self.debug( 'In parse_receivers: %s replaced by %s' % (raw_receivers,receivers))
          
        if not isSequence(receivers): receivers = receivers.split(',')
        receivers = [r for r in receivers if not filtre or filtre in r]
          
        ##DISABLED; CAUSED A MESS ON parse_receivers(ACTION)
        #if '$' in str(receivers):
          ##Phonebook and Defines must be parsed separately
          #raw_receivers = ','.join(receivers) if isSequence(receivers) else receivers
          #receivers = self.parse_defines(receivers,tag_name,message=message)
          #if receivers != raw_receivers: 
            #self.debug( 'In parse_receivers: %s replaced by %s' % (raw_receivers,receivers))
        #if not isSequence(receivers): receivers = receivers.split(',')
        
        self.debug(('parse_receivers(%s,%s): %s' % (tag_name,filtre,receivers))[:120])
        return receivers
        
    def parse_action_receivers(self,tag_name,message,receivers):
        '''
        Filters the alarm receivers extracting ACTION([message]:...) formulas.
        Extracts arguments from every action and return them as a list.
        '''
        actions = []
        re_arglist = '(?:'+'(?:[^()]*)'+'|'+'(?:[^()]*\([^()]*\)[^()]*)'+')'
        if isSequence(receivers): receivers = ','.join(receivers)
        action_receivers = re.findall('ACTION\('+re_arglist+'\)',receivers)

        for ac in action_receivers:
            t = {'DISABLED':'disable','ACKNOWLEDGED':'acknowledge'}.get(message,message.lower())
            #rm = re.search(t.lower()+':((?:.*?)|(?:.*[\(].*[\)].*))'+'[\;\)]',ac)
            #rm = re.search(t.lower()+':('+re_arglist+')[\;\)]',ac)
            rm = re.search('(?:'+t.lower()+'|[*]):'+'('+re_arglist+')[\;\)]',ac)
            if rm: actions.append(rm.group(1))

        self.debug(('parse_action_receivers(%s,%s): %s'%(tag_name,message,actions))[:180]+'...')
        return actions

    ##@name Thread management methods
    # @{

    def start(self):
        if self.updateThread:
            if self.updateThread.isAlive():
                self.warning( 'Start not allowed, thread still working')
                return
            else: del self.updateThread
        self.info( 'Thread Starting')
        self.kill.clear()
        self.pause.clear()
        self.updateThread = threading.Thread(None,self.updateAlarms,'PyAlarm')
        self.updateThread.setDaemon(True)
        self.updateThread.start()

    def stop(self):
        self.info( 'In PyAlarm.stop() ...')
        self.kill.set()
        self.pause.set()
        self.updateThread.join(self.PollingPeriod)
        if self.updateThread.isAlive():
            self.warning( 'Thread '+self.updateThread.getName()+' doesn''t Stop!')
        else:
            self.warning( 'Thread '+self.updateThread.getName()+' Stop')
        if self.worker and self.worker.isAlive(): self.worker.stop()
        return
    ##@}

    ###########################################################################
    
    def get_enabled(self, force=False):
        e = str(self.Enabled).lower().strip()
        last = self.PastValues.get('Enabled',(0,0))
        # t is time passed since start
        t = int(time.time() - (self.TStarted+self.StartupDelay))
        
        if e in ('true','1','yes','enabled'): r = True
        elif e in ('','false','0','no','none','disabled'): r = None
        elif e=='nan': r = fandango.NaN
        elif fandango.matchCl('^[0-9]+$',e): r = t > int(e)
        elif not force and last[0] and (t-last[0])<self.PollingPeriod:
            # return last cached value
            return last[1]
        else:
            # Evaluating an alarm formula
            try:
                self.FailedAlarms.pop('Enabled',None)
                r = self.EvaluateFormula(self.Enabled,as_string=False,_locals={'t':t})
            except:
                self.FailedAlarms['Enabled'] = traceback.format_exc()
                print(self.FailedAlarms['Enabled'])
                r = False
                
        self.PastValues['Enabled'] = t,r
        return r

        
    
    def get_last_values(self,alarm='',variables=None):
        if self.worker:
            # Getting the values of variables
            previous = self.worker.get('previous')
            try: 
                #self.debug('previous values: %s'%previous.keys())
                if variables is None: variables = self.Eval.parse_variables(self.Alarms[alarm].formula)
                formula = self.Alarms[alarm].formula if alarm in self.Alarms else ''
                VALUE = dict((k,v) for k,v in previous.items() if any(searchCl(self.Eval.parse_tag(v[0]+'/'+v[1],'.'),k) for v in variables))
                VALUE.update((a,bool(v.active)) for a,v in self.Alarms.items() if a in formula)
            except Exception,e: 
                self.warning('Exception parsing values: %s'%traceback.format_exc()) #except2str(e))
                VALUE = {'Exception':str(previous)}
            return VALUE
        else:
            #get_target = lambda v: v[0] + (v[1] and '/%s'%v[1]) + (v[2] and '.%s'%v[2])
            #self.Eval.last.update(dict((get_target(v),'...') for v in variables if v not in self.Eval.last))
            return self.Eval.last
    
    
    def updateAlarms(self):
        self.pause.wait()
        self.info( 'In PyAlarm::updateAlarms()')
        self.pause.clear()
        self.kill.clear()
        polled_attrs = []
        
        # Alarms will not start evaluation until StartupDelay seconds has passed.
        self.StartupDelay = max((self.StartupDelay, 3.))
        self.info( 'PyAlarm::updateAlarms: waiting %d s' % self.StartupDelay)
        if time.time()<(self.TStarted+self.StartupDelay):
            self.info('Alarms evaluation not started yet, '
                'waiting StartupDelay=%d seconds.'%self.StartupDelay)
            self.pause.wait(self.StartupDelay-(time.time()-self.TStarted))
            
        #Checking that the background test process is running
        if self.worker:
            if not self.worker.isAlive(): self.worker.start()
            self.pause.wait(self.PollingPeriod)
            
        while not self.kill.isSet():
            self.info( 'In PyAlarm::updateAlarms(): process()')
            self.pause.clear()
            try:
                #Initializing alarm values used in formulas
                _locals = self.update_locals(check=True)
                try:
                    #self.Alarms.servers.db.get_info()
                    dbd = fandango.tango.get_database_device()
                    dbd.state()
                except:
                    self.warning('Tango database is not available!\n%s'
                                % traceback.format_exc())
                    self.set_state(PyTango.DevState.FAULT)
                    ## This wait is here just to prevent the loop to 
                    # spin continuously
                    # The update_locals command will not allow 
                    # background process to die
                    for k in self.Alarms.servers:
                        self.pause.wait(self.PollingPeriod / 
                                        len(self.Alarms.servers))
                        if self.worker: 
                            _locals = self.update_locals(_locals, update=True)
                    continue

                ###############################################################
                try:
                    self.lock.acquire()
                    timewait = float(self.PollingPeriod)/(len(self.Alarms) or 1.)
                    self.debug( 'updateAlarms(): timewait between polling is %f s'%timewait)
                    myAlarms = sorted(a for a in self.Alarms.items() if not self.CheckDisabled(a[0])) #copied without disabled alarms
                finally: 
                    self.lock.release()
                self.debug('\n'+
                    'Enabled alarms to process in next %d s cycle (UseProcess=%s): %s ' 
                    % (self.PollingPeriod,self.UseProcess,[a[0] for a in myAlarms])
                    +'\n'+'#'*80)
                if not self.get_enabled(force=True): 
                    self.info( 'ALARM SENDING IS DISABLED!!')

                ###############################################################
                # NOT using a subProcess and Using Taurus to update the variables
                if not self.worker and self.UseTaurus:
                    try: #When using Taurus it will minimize the CPU usage
                        import taurus
                        tpolling = max((250,1e3*self.PollingPeriod/2.))
                        self.info('\tSet taurus polling period = %d ms'%tpolling)
                        for a in taurus.Factory().tango_attrs.keys():
                            if a not in polled_attrs:
                                polled_attrs.append(a)
                                TA = taurus.Attribute(a)
                                try:
                                    TA.activatePolling(tpolling,force=True)
                                except:
                                    self.warning('\tThis Taurus release doesnt allow to force polling!')
                                    TA.changePollingPeriod(tpolling)
                    except Exception,e: 
                        self.debug('\tunable to set taurus polling period: %s'%e)
                        #self.warning(traceback.format_exc())
                elif self.worker:
                    self.info('Worker last alive at %s: %s,%s'%(time.ctime(self.worker.last_alive),self.worker._process.is_alive(),self.worker._receiver.is_alive()))
                    
                ###############################################################
                    
                for tag_name,alarm in myAlarms: #Format is:    TAG3:LT/VC/Dev1/Pressure > 1e-4
                    self.pause.wait(timewait) #Pause should be done at the start of the loop
                    ##########################################################
                    self.process_alarm(tag_name,alarm,_locals)
                    ##########################################################
                    # END OF ALARMS LOOP
                
                self._lastupdate = time.time()
                if not myAlarms: self.pause.wait(timewait)
                else: self.info('\n'+'-'*80)
                self.Uncatched=''
            
            except Exception,e:
                self.set_state(PyTango.DevState.FAULT)
                from traceback import format_exc
                tr=format_exc()
                msg = ( 'Uncatched exception in PyAlarm::updateAlarmsThread:\n%s'%tr + '\n' + '='*80)
                self.error(msg)
                self.set_status(msg)
                self.Uncatched+=tr+'\n'
                self.pause.wait(timewait)
                
            # END OF MAIN LOOP
            
        self.info( 'In updateAlarms(): Thread finished')
        return
      
    def process_alarm(self,tag_name,alarm,_locals):
        """
        Process method called from updateAlarms for each alarm 
        """
        
        now = self.last_attribute_check = time.time()
        ##########################################################
        alarm.get_state(force=True)
        WAS_OK = alarm.counter<self.AlarmThreshold
        self.info('Checking alarm tag %s'%tag_name)
        self.debug(alarm.formula)

        # Cache management
        #try: del self.LastValues.pop(tag_name)
        #except: self.error(traceback.format_exc())
        self.LastValues[tag_name] = variables  = {}
        
        VALUE = self.EvaluateFormula(alarm.formula,
                tag_name=tag_name,as_string=False,lock=True,
                _locals=_locals,variables=variables)
        
        self.debug('was_ok/counter/active/VALUE: %s,%s,%s,%s'
                   %(WAS_OK,alarm.counter,alarm.active,VALUE))
        if WAS_OK:
            self.info('\tupdateAlarms(%s) = %s' % (tag_name,type(VALUE) 
                        if isinstance(VALUE,Exception) else VALUE))
                  
        # ALARM May be failed or removed during the thread iteration
        if (  tag_name not in self.Alarms or 
            (VALUE is None and tag_name in self.FailedAlarms) ):
            alarm.get_state(force=True)
            return

        elif VALUE:
            # The alarm condition is ACTIVE
            ###################################################
            #WAS_OK = alarm.counter<self.AlarmThreshold
            
            # counter and active will not have same values if the alarm 
            # is not acknowledged for a while
            
            if not alarm.counter and not alarm.active:
                # Storing the data at first condition trigger
                self.PastValues[tag_name] = variables.copy() \
                        if hasattr(variables,'copy') else None             
            
            if WAS_OK:
                #Alarm counters are not increased above Threshold
                alarm.counter+=1
            
            if WAS_OK: self.info(
              'Alarm %s triggered for %d/%d cycles'
              %(tag_name,alarm.counter,self.AlarmThreshold))

            if alarm.counter>=self.AlarmThreshold:

                # Sending ALARM
                ########################################################
                if not alarm.active:
                    # Alarm is sent only if it was not active or it was 
                    # recovered and just came back to alarm or has passed 
                    # the reminder cycle
                    self.set_alarm(tag_name)
                    #Storing the values that will be sent in the report
                    self.LastAlarms.append(time.ctime(now)+': '+tag_name)

                    if alarm.tag not in self.AcknowledgedAlarms: 
                        # <=== HERE IS WHERE THE ALARM IS SENT!
                        self.send_alarm(tag_name,message='ALARM',
                                        values=variables or None) 

                # Sending REMINDER
                ########################################################
                elif not alarm.acknowledged and self.Reminder and (
                        (alarm.recovered and WAS_OK and self.AlertOnRecovery) 
                        or alarm.last_sent<(now-self.Reminder)):
                    self.info('==========> ALARM %s reminder is sent after '\
                        '%s seconds being active.'%(alarm.tag,self.Reminder))
                    if alarm.tag not in self.AcknowledgedAlarms: 
                      self.send_alarm(tag_name,message='REMINDER',
                                      values=variables or None)
                      
                self.alarm_attr_read(tag_name) #pushing is done every cycle
                
                alarm.recovered = 0
        else:
            # The alarm is NOT active
            ###################################################

            if alarm.counter: 
                alarm.counter-=1
                
            if not alarm.counter and alarm.active: 
                #The alarm condition was active in the previous cycle
                #Storing the values that will be sent in the report
                self.PastValues[tag_name] = variables.copy() \
                    if hasattr(variables,'copy') else None 

                # Alarm RECOVERED
                #####################################################
                if not alarm.recovered:
                    self.info('The alarm is still active ... '
                        +'but values came back to reality!')
                    alarm.recovered = alarm.set_time()
                    
                    if self.AlertOnRecovery and \
                            alarm.tag not in self.AcknowledgedAlarms: 
                        self.send_alarm(tag_name,message='RECOVERED',
                                        values=variables)

                # Alarm AUTO-RESET
                #####################################################
                elif self.AutoReset and alarm.recovered<(now-self.AutoReset):
                    self.info('==========> ALARM %s has been reset '
                        'automatically after %s seconds being inactive.'
                        %(alarm.tag,self.AutoReset))
                    self.free_alarm(alarm.tag,notify=False)
                    
                    if alarm.tag not in self.AcknowledgedAlarms: 
                        self.send_alarm(tag_name,message='AUTORESET',
                                        values=variables)

        alarm.get_state(force=True) #alarm.updated done here
        self.debug('Alarm %s counter is : %s' % (tag_name, alarm.counter))

        if tag_name in self.FailedAlarms: 
            self.FailedAlarms.pop(tag_name)
            
        return

    #@self_locked
    def set_alarm(self,tag_name):
        result = False
        self.debug( '#'*80)
        self.info( 'In set_alarm(%s)'%tag_name)
        try:
            self.lock.acquire()
            if not self.Alarms[tag_name].active:
                now = time.time()
                self.Alarms[tag_name].active = now
                self.Alarms[tag_name].recovered = 0
                self.Alarms[tag_name].set_time(now)
                result = True
                # pushing should be done AFTER notification
        except:
            self.error('set_alarm(%s):\n%s' % 
                       (tag_name, traceback.format_exc()))
        finally:
            self.lock.release()
        return result

    def get_active_alarms(self):
        return [k for k,v in self.Alarms.items() if v.active]

    #@self_locked
    def free_alarm(self,tag_name,comment='',message=None, notify=True):
        """ message for freeing alarm must be RESET/ACKNOWLEDGED/DISABLED """
        was_active = False
        try:
            self.lock.acquire()
            result = False
            alarm_obj = self.Alarms[tag_name]
            receivers = self.parse_receivers(tag_name,message=message)
            was_active = alarm_obj.active
            self.info('-'*80)
            self.info('In free_alarm(%s,%s,%s,%s,%s)'
                      %(tag_name,comment,message,notify,was_active))
            if message=='RESET' and self.CheckDisabled(tag_name): 
                self.Enable(tag_name)
            if not was_active:
                result = False
            else:
                #RESETING THE ALARM (Acknowledge just puts it in "Silent mode")
                if message!='ACKNOWLEDGED': 
                    date = self.Alarms[tag_name].active
                    #Storing the values that triggered the alarm
                    self.PastAlarms[date].append(tag_name)
                    #self.PastValues[tag_name] = None 
                    self.Alarms[tag_name].clear()
                    self.Alarms[tag_name].set_time()
                    self.info("Alarm %s:%s cleared"%(date,tag_name))
                    
                    if tag_name in self.AcknowledgedAlarms: 
                      self.AcknowledgedAlarms.remove(tag_name)
                      
            self.alarm_attr_read(tag_name) #pushing is done here
        except:
            s = traceback.format_exc()
            self.warning( 'free_alarm(%s) failed!:1: %s' % (tag_name,s))
        finally:
            self.lock.release()
            
        try: #NOTIFICATION SHOULD NOT BE WITHIN THE LOCK
            if was_active and self.get_enabled() and notify:
                #This is executed only when called from ResetAlarm() command
                #AutoReset notification is called from updateAlarms to include attribute values in report
                self.send_alarm(tag_name,message=message or 'RESET',comment=comment)

        except:
            self.warning( 'free_alarm(%s) failed!:2: %s' % (tag_name,traceback.format_exc()))
            
        self.info('-'*80)
        return result

    #########################################################################################################
    ##@name Alarm Sending
    #@{

    def send_alarm(self,tag_name,message='',values=None,receivers=None,comment=''):
        """
        This method parses receivers and:
         - Sends email/SMS for alarms
         - Sends email/sms for RECOVERED/REMINDER if specified in AlertOnRecovery property
         - Triggers snapshots for ALARMS
         - Saves HTML report
         
        ACKNOWLEDGE/RESET messages are not managed here, but in free_alarm
        """
        self.info('\n\n')
        self.info('='*80)
        self.info(shortstr('In PyAlarm.send_alarm(%s,%s,%s)'%(tag_name,message,values),255))
        alarm = (self.Alarms.get(tag=tag_name) or [None])[0]
        try:
          if alarm and self.MaxMessagesPerAlarm and self.Alarms[tag_name].sent >= self.MaxMessagesPerAlarm: 
              #This counter is reset calling .clear() from free_alarm()
              self.debug('*'*80)
              self.warning('Too many alarms (%d) already sent for %s!!!' % (self.Alarms[tag_name].sent, tag_name))
              self.debug('*'*80)
              return
            
          try:
              self.lock.acquire()
              receivers = self.parse_receivers(tag_name,receivers=receivers,message=message)
              mail_receivers = self.parse_receivers(tag_name,'@',receivers,message=message)
              sms_receivers = self.parse_receivers(tag_name,'SMS',receivers,message=message)
              tg_receivers = self.parse_receivers(tag_name,'TG',receivers,message=message)
              action_receivers = self.parse_action_receivers(tag_name,message,receivers)
              #self.info('receivers:'+';'.join(str(r) for r in (receivers,mail_receivers,sms_receivers,action_receivers))[:240]+'...')
              self.info(('%s receivers:\n\t%s\n\tmail:%s\n\t'
                        'sms:%s\n\ttg:%s\n\taction[%s]: %s'%(
                        tag_name,alarm.receivers,mail_receivers,sms_receivers,
                        tg_receivers,len(action_receivers),action_receivers)))
          finally:
              self.lock.release()
          
          self.info('\n\n')
          if message not in ('ALARM','REMINDER') and not self.AlertOnRecovery:
              mail_receivers = sms_receivers = tg_receivers = []

          if alarm:
            report = self.GenerateReport(tag_name,mail_receivers or '',message=message,
                        user_comment=comment,values=values)
          else: 
            #Sending a test message (no alarm involved)
            report = [message, tag_name+'-'+message,','.join(mail_receivers)]

          if self.get_enabled():
            
            def rcatched(method):
              
              def _rcatched(*args,**kwargs):
                
                self.info('send_alarm(%s) => %s(%s,%s)'% 
                          (tag_name, method, args, kwargs))
                try: 
                    r = method(*args,**kwargs)
                except:
                    msg = getattr(method,'__name__',str(method))
                    msg = '%s crashed! \n%s'%(msg,traceback.format_exc())
                    self.warning(msg)
                    report[0] += '\n'+'-'*80+'\n'+msg
                return r
              
              return _rcatched
          
            ## ACTIONS MUST BE EVALUATED FIRST PRIOR TO NOTIFICATION
            if action_receivers:
                self.info('-'*80)
                for ac in action_receivers:
                    rcatched(self.trigger_action)(
                        tag_name,ac,message=message)          
              
            if alarm and self.Alarms[tag_name].severity=='DEBUG':
                self.warning('%s Alarm with severity==DEBUG do not trigger '
                    'messages'%tag_name)

            #NOTIFICATION OF ALARMS
            else:
              #Disabling sms message for messages not related to new alarms
              if sms_receivers and message in ('ALARM',) \
                    or 'sms' in str(self.AlertOnRecovery).lower(): 
                  rcatched(self.SendSMS)(
                    tag_name,sms_receivers,message=message,values=values)
                  
            # telegram
            if tg_receivers: 
              self.info('-'*80)
              try:
                self.SendTelegram(tag_name, tg_receivers,
                                  message=message, values=report)
              except:
                self.warning('Exception sending telegram!: %s'
                             % traceback.format_exc())
                
            # SNAP ARCHIVING
            if (self.snap and SNAP_ALLOWED
                and (message in ('ALARM','ACKNOWLEDGED','RESET') 
                    or self.AlertOnRecovery and message=='RECOVERED')
                and (self.UseSnap 
                    or self.parse_receivers(tag_name,'SNAP',
                                            receivers,message=message))):
                if (self.Alarms[tag_name].severity!='DEBUG'): 
                    rcatched(self.trigger_snapshot)(tag_name,message)

            #LOGGING (by file or email)
            
            # html logs
            if self.parse_receivers(tag_name,'HTML',receivers,message=message):
                r = rcatched(self.GenerateReport)(tag_name,message=message,
                                      values=values,html=True)
                rcatched(self.SaveHtml)(r)
                
            ## emails
            if mail_receivers: 
                r = rcatched(self.SendMail)(report)
                          
            self.info('-'*80),self.update_flag_file()

          else:
            self.info('=============> ALARM SENDING DISABLED!!')

          try:
            self.update_log_file(tag=tag_name,report=report,
                                 message=message or '')
          except:
            self.warning(traceback.format_exc())
            
          if alarm:
            self.Alarms[tag_name].sent += 1
            self.Alarms[tag_name].last_sent = time.time()
                
          return report
              
        except Exception,e:
            self.warning('PyAlarm.send_alarm crashed with exception:\n%s' 
              % traceback.format_exc())
        self.info('-'*80)
        return ''

    def update_flag_file(self):
        ''' If there's Active Alarms writes a 1 to the file specified 
        by FlagFile property, else writes 0 '''
        try:
            self.lock.acquire()
            AlarmsToNotify = self.get_active_alarms()
            f=open(self.FlagFile,'w')
            f.writelines(['1\n' if AlarmsToNotify else '0\n'])
            f.close()
            self.info( 'update_flag_file(%s,%s)' 
              % ('1' if AlarmsToNotify else '0' ,self.FlagFile))

        except Exception,e:
            self.warning( 'Exception in PyAlarm.update_flag_file: %s' % str(e))
            return False
          
        finally:
            self.lock.release()
        return True

    def update_log_file(self,argin='',tag='',report='',message=''):
        """
        """
        self.debug('update_log_file(%s,%s,%s,%s)'%(argin,tag,report,message))
        logfile = (argin.strip() or self.LogFile or '').strip()
        if not logfile or logfile == '/dev/null': 
            return
        try:
            self.lock.acquire()
            logfile = self.parse_defines(logfile,tag=tag,message=message)
            
            if not report:
                report = []
                report.append('%s PyAlarm Device Server at %s\n\n' 
                  % (self.get_name(),time.ctime()))
                
                if self.get_active_alarms():
                    report.append('Active Alarms are:\n')
                    [report.append('\t%s:%s:%s\n'
                      %(k,time.ctime(self.Alarms[k].active),
                                    self.Alarms[k].formula)) 
                      for k in self.get_active_alarms()]
                      
                else: 
                    report.append( "There's No Active Alarms\n")
                    
                if self.PastAlarms: 
                    report.append( '\n\n' + 'Past Alarms were:' 
                      + '\n\t'.join([''] 
                      + ['%s:%s'%(','.join(k),time.ctime(d)) 
                              for d,k in self.PastAlarms.items()]) +'\n')

            if isSequence(report):
                report = '\n'.join(report)

            if clmatch('(folderds|tango)[:].*',logfile):                 
                self.info('Sending alarm log to %s'%logfile)
                try:
                    fandango.device.FolderAPI().save(
                            logfile,logfile,report,asynch=True)
                except:
                    self.warning(traceback.format_exc())
            else:
                self.info('Saving alarm values to %s'%logfile)
                f=open(logfile,'a')
                f.write(report)
                f.close()
                
        except Exception,e:
            self.warning( 'Exception in PyAlarm.update_log_file: %s' 
                          % traceback.format_exc())
        finally:
            self.lock.release()

    def trigger_action(self, alarm, args, message='',asynch=True):
        """
        Executing a command on alarm/disable/reset/acknowledge: 
            ACTION(alarm:command,mach/alarm/beep/play_sequence,$DESCRIPTION)
        
        see full description at doc/recipes/ActionsOnAlarm.rst
        """
        if isString(alarm): tag = alarm
        else: tag,alarm = alarm.tag,alarm
          
        action = args if isSequence(args) else re.split('[,;]',args)
        self.info('In PyAlarm.trigger_action(%s,%s)'%(tag,args))
        if action[0] in ('command','attribute'):
            try:
                dev = action[1].rsplit('/',1)[0]
                
                dp = PyTango.DeviceProxy(dev)
                if not fandango.tango.check_device(dev):
                  exc = '%s receiver is not running!'%dev
                  self.error(exc)
                  raise Exception(exc)
                
                cmd = [action[1].rsplit('/',1)[1]]+action[2:]
                cmd = self.parse_defines(cmd,tag,message=message)

                try: #This eval will try to pass numeric/float arguments
                    arg = [eval(c) for c in cmd[1:]]
                except: 
                    arg = [s.strip("' ") for s in cmd[1:]]

                if arg:
                    if len(arg)==1:
                        if action[0] == 'command':
                            t = str(dp.command_query(cmd[0]).in_type)
                        else:
                            t = str(dp.attribute_query(cmd[0]).data_format)
                        if not clsearch('array|spectrum|image',t):
                            arg = arg[0]
                    
                    elif len(arg)>1 and clmatch('[\[\(].*',str(arg[0])):
                        arg = eval(','.join(arg))
                    
                if action[0] == 'command':
                    self.info(('\tlaunching: %s / %s (%s)' % (dev,cmd[0],cmd[1:]))[:120])
                    cargs = [cmd[0],arg] if arg else [cmd[0]]
                    m = dp.command_inout_asynch if asynch else dp.command_inout
                    val = m(*cargs)
                    #self.info('\t:'+str(val)+','+str(asynch))
                else:
                    self.info('\twriting attribute: %s / %s = %s' % (dev,cmd[0],cmd[1:]))
                    val = PyTango.DeviceProxy(dev).write_attribute(cmd[0],arg)
                    #self.info('\t'+str(val))
            except:
                self.warning('Unable to execute action %s'%str(action))
                self.warning(traceback.format_exc())
        elif action[0]=='system':
          if action[1] in self.AllowedActions:
            try:
                cmd = self.parse_defines(action[1],tag,message=message)
                self.info('OS action: %s'%str(cmd))                
                os.system(cmd+'&')
            except:
                self.warning('Unable to execute action %s'%str(action))
                self.warning(traceback.format_exc())
          else:
                self.info('OS action not allowed: %s'%str(action))
        else:
            self.warning('\tUnknown Action: %s'%action[0])

    ##@}
    #########################################################################################################

    ## @name Snap Contexts Methods
    #@{

    #SnapContext(359,Name,Author,Reason,Attributes[3],Snapshots[0])
    def trigger_snapshot(self,tag_name, user_comment=None):
        """
        First check for existing contexts with name=tag_name.
        If 1 exists and reason is Alarm, it is used. If reason is not, a new one is created.
        Then an snapshot is taken for the cotnext.
        """
        self.info( "In "+self.get_name()+"::trigger_snapshot(%s)"%tag_name)
        try:
            formula = self.Alarms[tag_name].formula
            self.info(formula)
            variables = self.Eval.parse_variables(formula)
            existingAttrsAllowed=[]
            for var in variables:
                if (self.snap.check_attribute_allowed(str(var[0]+'/'+var[1]))): 
                    existingAttrsAllowed.append(str(var[0]+'/'+var[1]))
    
            if (len(existingAttrsAllowed)==0):
                self.warning('Cannot take a snapshot - alarm attributes list empty!')
                return
    
            res=self.snap.db.search_context(tag_name)
            res = sorted(c for c in res if c['reason']=='ALARM')
            cids = sorted(c['id_context'] for c in res)
            
            if not res: 
                if not self.CreateNewContexts: 
                    self.warning('Automatic Context Creation DISABLED!, Sorry, use CreateAlarmContext method')
                    return
                self.info('Creating ctx: name: '+tag_name+', descr: '+formula)
                self.info('atts:')
                for a in existingAttrsAllowed:
                    self.info(a)
                cid = self.CreateAlarmContext([tag_name]+existingAttrsAllowed)
                ctx = self.snap.get_context(cid)
                self.info('snap.descr: '+formula)
            else:
                if len(res)>1: self.warning('Multiple contexts declared for this Alarm, using newest!: %s'%str(cids))
                ctx=self.snap.get_context(cids[-1])
    
            self.snap.db.update_context_attributes(self.snap.db.get_context_ids(tag_name)[0], existingAttrsAllowed)
            if user_comment and user_comment!='ALARM':
                if user_comment in ('DISABLED','RECOVERED'):
                    ctx.take_snapshot(comment=shortstr(user_comment,255))
                else: 
                    ctx.take_snapshot(comment=shortstr('ACKNOWLEDGED: %s'%user_comment,255))
            else: 
                ctx.take_snapshot(comment=shortstr('ALARM: %s'%self.Alarms[tag_name].description,255))
                
        except Exception,e:
            self.warning( 'Exception in trigger snapshot: %s' % traceback.format_exc())
        return
    ##@}

    def AddNewAlarm(self, argin):
        #@todo: This command has to be refactored and added as Expert command
        self.info( "In "+ self.get_name()+ "::AddNewAlarm()")
        #    Add your own code here
        argout = ['FAILED']
        try:
            self.lock.acquire()
            alarm = self.parse_alarm(argin)
            if not alarm:
                argout = 'INVALID ALARM FORMAT: %s'%alarm['tag']
            elif alarm['tag'] in self.Alarms:
                argout = 'ALARM TAG %s ALREADY EXISTS!'%alarm['tag']
            else:
                self.AlarmList.append(argin)
                self.init_device(update_properties=False)
                self.db.put_device_property(self.get_name(),{'AlarmList':self.AlarmList})
                argout = self.AlarmList[:]
        finally:
            self.lock.release()
        return argout

    def RemoveAlarm(self, argin):
        #@todo: This command has to be refactored and added as Expert command
        self.info("In "+self.get_name()+"::RemoveAlarm()")
        #    Add your own code here
        argout = ['FAILED']
        try:
            self.lock.acquire()
            if argin in self.Alarms and self.Alarms[argin].active: return 'ALARM SHOULD BE ACKNOWLEDGED FIRST!'
            match = [a for a in self.AlarmList if a.lower().startswith(argin.lower()+':')]
            if not match: return 'UNKNOWN ALARM TAG!'
            self.AlarmList.pop(match[0])
            self.init_device(update_properties=False)
            self.db.put_device_property(self.get_name(),{'AlarmList':self.AlarmList})
            argout = self.AlarmList[:]
        finally:
            self.lock.release()
        return argout 

    # #########################################################################################################

    ## @name POGO Generated Methods    

#------------------------------------------------------------------
#    Device constructor
#------------------------------------------------------------------
    def __init__(self,cl, name):
        #This code should be executed only at server_init() and not at device.init()
        print('In PyAlarm.__init__(%s,%s)'%(cl,name))
        PyTango.Device_4Impl.__init__(self,cl,name)
        self.call__init__(fandango.log.Logger,name,
                use_tango = True, #already True by default, printf may cause I/O issues
                format='%(levelname)-8s %(asctime)s %(name)s: %(message)s')
        
        self.setLogLevel('DEBUG')
        panic._proxies[name] = self
        init_callbacks(period_ms=50.)
        self.db = PyTango.Database()
        self.db.put_device_property(name,{'VersionNumber':__RELEASE__})
        self.snap = None
        
        #Persistent data:
        self.TStarted = time.time() #Used to calcullate StartupDelay and Enabled behavior
        self.Alarms = None #dictionary for Alarm Structs
        self.SMS_Sent = collections.deque() #Epochs when messages has been sent
        self.Proxies = defaultdict_fromkey(lambda key: PyTango.DeviceProxy(key)) #list of proxies
        # Last evaluated values for each tag
        self.LastValues = {}
        # Attribute values when the alarm was triggered.
        self.PastValues = {} 
        # Last generated report for each alarm change.
        self.Reports = defaultdict(dict) 

        # Array Attributes
        self.FailedAlarms = fandango.CaselessDict() #This list will keep a track of those alarms that couldn't be evaluated
        self.PastAlarms = defaultdict_fromkey(lambda key: list()) #{date1:[tag1,tag2,tag3,...],date2:...}        
        self.AcknowledgedAlarms = set() #This list will keep a track of acknowledged alarms
        self.DisabledAlarms = {} #This list will keep a track of disabled alarms, and its timeouts
        self.LastAlarms = []
        self.SentEmails = defaultdict(int)
        self.SentSMS = defaultdict(int)
        
        self.Eval = None
        self.EvalTimes = {}
        self.Uncatched = ''
        self.DynamicAttributes = []
        self._lastupdate = 0
        self._initialized = False #Enabled once dyn_attr has finished

        self.worker = None
        self.lock=threading.RLock();
        self.kill=threading.Event();
        self.pause=threading.Event();
        self.threadname=name
        self.updateThread = None
        self.last_attribute_check = 0
        
        self.debug('Out of __init__()')
        PyAlarm.init_device(self,allow=True)

#------------------------------------------------------------------
#    Device destructor
#------------------------------------------------------------------
    def __del__(self):
        self.info( 'In PyAlarm.__del__() method ...')
        self.delete_device(True)

    def delete_device(self,stop=False):
        self.warning( "0[Device delete_device method] for device %s"%self.get_name())
        if stop:
            self.stop()
        else: 
            self.pause.set()
            self.warning(" ... restarting ...")
            self.set_state(PyTango.DevState.INIT)

        # Do not do that or you may have seg faults!
        #print "1[Device delete_device method] for device",self.get_name()
        #for dp in self.Proxies.values(): del dp
        #print "2[Device delete_device method] for device",self.get_name()

#------------------------------------------------------------------
#    Device initialization
#------------------------------------------------------------------
    def init_device(self,update_properties=True,allow=True):
        """
        This method will be called first for creating the device.
        It will be called afterwards to force a reloading 
        of Alarms or Properties
        """
        self.info('#'*80)
        self.info( "In "+self.get_name()+
            "::init_device(update_properties=%s,allow=%s)"
            %(update_properties,allow))
        
        self.last_status = 0
        self.last_push = 0
        self.last_summary = []

        # A class object will keep all declared alarms 
        # to search for children alarms and duplications
        if type(self).Panic is None: 
            type(self).Panic = panic.AlarmAPI()
        
        if not allow: 
            raise Exception('init_device() is not allowed,'
              ' please restart the server')
        try:
            if update_properties or not self._initialized: 
                
                # Reading this from DB at init() may cause BAD_CORB_INV_ORDER
                # Will be done in background by updateAlarms thread
                self.debug('clear phonebook, will be updated on next update_locals()')
                self.PhoneBook = None #self.Alarms.get_phonebook()

                self.get_device_properties(self.get_device_class())
                self.info('properties reloaded, PhoneBook: %s' % self.PhoneBook)
                
                self.info("Current Alarm server configuration is:\n\t"
                    +"\n\t".join(
                    sorted("%s: %s"%(k,getattr(self,k,None)) for k in 
                    panic.PyAlarmDefaultProperties)))
                
                try:
                    #Reloading the alarm properties
                    self.lock.acquire()
                    if self.Alarms is None:
                        #Everything that was active/inactive is erased here?
                        self.Alarms = panic.AlarmAPI(self.get_name()) 
                        
                    #This second part is not called for the first init(); 
                    # only for the next ones
                    #It's just for checking if alarms has been added/removed 
                    # from the API
                    else:
                        self.Alarms.load(self.get_name())
                        self.Alarms.get_global_receivers(renew=True)
                        alarm_attrs = dict((a,v.get_attribute()) 
                                           for a,v in self.Alarms.items())
                        for a in self.DynamicAttributes[:]:
                            if a not in alarm_attrs.values():
                                try:
                                    self.info('Removing %s attribute'%a)
                                    self.remove_attribute(a)
                                    self.DynamicAttributes.remove(a)
                                    if self.worker: self.worker.pop(a)
                                except:
                                    self.warning( traceback.format_exc())
                        for a,v in alarm_attrs.items():
                            self.info('Alarm:'+(a))#.formula,v.receivers))
                            if v not in self.DynamicAttributes[:]:
                                self.create_alarm_attribute(a)
                                
                    # Set PyAlarm-driven access (_time is forced)
                    [a.set_time() for a in self.Alarms.values() 
                        if a._time is None]                            
                except Exception,e:
                    raise e
                finally: self.lock.release()                
                
                
                #@TODO: Period should be in SECONDS!: 
                #this patch must be DEPRECATED
                if self.PollingPeriod>3600: 
                    self.warning('PERIODS IN MILLISECONDS ARE DEPRECATED!: '
                        '%s ms >> %s s'
                        %(self.PollingPeriod,self.PollingPeriod*1e-3))
                    #Converting from ms to s
                    self.PollingPeriod = self.PollingPeriod*1e-3
                  
                if (str(self.AlertOnRecovery).strip().lower()
                       in ('false','no','none')): 
                    self.AlertOnRecovery=''
                       
                if str(self.Reminder).strip().lower()=='false': self.Reminder=0
                
                if not self.UseTaurus:
                    fandango.tango.USE_TAU,fandango.tango.TAU = False,None
                else:
                    self.info('UseTaurus = %s'%self.UseTaurus)
                if self.Eval is None:
                    evalClass = (SingletonTangoEval 
                                 if self.UseProcess and PROCESS 
                                 else fandango.tango.TangoEval)
                    
                    #Note, a cache too large will filter 
                    # oscillations in alarms using .delta
                    self.Eval = self.Panic._eval = (evalClass)(
                      timeout=self.EvalTimeout,keeptime=self.PollingPeriod/2.,
                      trace=False, #self.LogLevel.upper()=='DEBUG',
                      cache=1+self.AlarmThreshold, 
                      use_tau=False)
                    
                    [self.Eval.add_macro(*m) for m in self.Panic.macros]
                    ## MAY TRIGGER EXCEPTIONS AT INIT!!
                    #self.update_locals(check=True,update=True)
                    self.Eval.set_timeout(self.EvalTimeout)
                    
                if hasattr(self.Eval,'clear'): self.Eval.clear()
                
                if self.UseProcess and PROCESS and not self.worker:
                  
                    #Do not reduce worker timeout or you may have problems 
                    #if main thread idles (e.g. Tango database is down)
                    self.info('Configuring WorkerProcess ...')
                    self.worker = WorkerProcess(self.Eval,start=True,
                      timeout=self.PollingPeriod*max((self.AlarmThreshold,3)))
                    self.worker.send('set_timeout','set_timeout',
                                     self.EvalTimeout)
                    self.worker.command('import threading')
                    
                    #,timewait=0.05*self.PollingPeriod/len(self.Alarms))
                    self.worker.add('previous',target='dict([(k,str(v)) '
                        'for k,v in executor.previous.items()])',
                        args='',period=self.PollingPeriod/2.,
                        expire=self.AlarmThreshold*self.PollingPeriod)
                    
                    if self.UseTaurus:
                        try: 
                            self.worker.command('import taurus')
                            self.worker.add('update_polling',
                              '[taurus.Attribute(a).changePollingPeriod(%d) '
                              'for a in taurus.Factory().tango_attrs.keys()]'
                              %max((500,1e3*self.PollingPeriod/2.)),
                              period=self.PollingPeriod)
                            
                        except: print traceback.format_exc()
                        
                    #raise Exception,'The StartupDelay should be asynchronous!
                    #It cannot be called before any "command" call
                    self.worker.pause(self.StartupDelay)
                    self.info('Configured WorkerProcess, waiting %s seconds'
                      ' in background ...'%self.StartupDelay)

            for tag,alarm in self.Alarms.items():
                self.info('\n\t%s: %s\n\t\tFormula: %s\n\t\tSeverity: %s\n'
                  '\t\tReceivers: %s'%(tag,alarm.description,alarm.formula,
                                       alarm.severity,alarm.receivers))
                  
            self.set_change_event('State',True,False)
            self.set_change_event('AlarmSummary',True,False)

            #Create Alarm Attributes (not called in first init(), only after
            if self._initialized: self.dyn_attr()

            #Get SnapConfig
            if SNAP_ALLOWED and (self.UseSnap or any('SNAP' in str(a.receivers) 
                      for a in self.Alarms.values())):
                try:
                    self.snap = snap.SnapAPI()
                except Exception,e: 
                    self.warning('SnapConfig failed: %s'%e)

            if not self._initialized: 
                self.set_state(PyTango.DevState.ON)
                
            if not self.updateThread or not self.updateThread.isAlive(): 
                self.pause.clear()
                self.start()
                
            self.info( 'Ready to accept request ...')
            self.info('#'*80)
            self.setLogLevel(self.LogLevel)

        except Exception,e:
            self.info( 'Exception in PyAlarm.init_device(): \n%s'
                      %traceback.format_exc())
            self.set_state(PyTango.DevState.FAULT)
            raise e
          
        finally:
            self.pause.set() #starts updateAlarmsThread
            self.kill.clear()
        return

#------------------------------------------------------------------
#    Always excuted hook method
#------------------------------------------------------------------
    def always_executed_hook(self):
        self.debug("In "+ self.get_name()+ "::always_excuted_hook()")
        now = time.time()  
        # Status/Events processed only once per second
        if (self.last_status+1.) <= now:
            self.StateMachine()
        self.debug("Out of "+ self.get_name()+ "::always_excuted_hook()")


#==================================================================
#
#    PyAlarm read/write attribute methods
#
#==================================================================
#------------------------------------------------------------------
#    Read Attribute Hardware
#------------------------------------------------------------------
    def read_attr_hardware(self,data):
        #self.debug("In "+self.get_name()+"::read_attr_hardware()")
        pass
    
#------------------------------------------------------------------
#    Read VersionNumber attribute
#------------------------------------------------------------------
    def read_VersionNumber(self, attr):
        #self.debug( "In "+self.get_name()+"::read_VersionNumber()")

        #    Add your own code here
        attr_VersionNumber_read = __RELEASE__ 
        attr.set_value(attr_VersionNumber_read)
        
#------------------------------------------------------------------
#    Read LastAlarm attribute
#------------------------------------------------------------------
    def read_LastAlarm(self, attr):
        #self.debug( "In "+self.get_name()+"::read_LastAlarm()")

        #    Add your own code here
        attr_LastAlarm_read = ""
        attr_LastAlarm_read = self.LastAlarms.pop(0)
        attr.set_value(attr_LastAlarm_read)

#---- LastAlarm attribute State Machine -----------------
    def is_LastAlarm_allowed(self,req_type=None):

        if not self.LastAlarms:
                self.info('LastAlarms list is emptied once it is read,'
                  ' is used only for archiving purposes.')
                PyTango.Except.throw_exception('EmptyList',
                  'LastAlarms already read/archived','read_LastAlarm')
                return False
        return True

#------------------------------------------------------------------
#    Read AlarmConfiguration attribute
#------------------------------------------------------------------
    def read_AlarmConfiguration(self, attr):
        #self.debug("In "+self.get_name()+"::read_AlarmConfiguration()")

        #    Add your own code here
        attr_AlarmConfig_read = []
        attr_AlarmConfig_read = ['%s:%s'%(prop,getattr(self,prop)) 
                  for prop in PyAlarmClass.device_property_list.keys() 
                  if prop not in panic.ALARM_TABLES]
        attr.set_value(attr_AlarmConfig_read)

#------------------------------------------------------------------
#    Read ActiveAlarms attribute
#------------------------------------------------------------------
    def read_ActiveAlarms(self, attr):
        #self.debug( "In "+self.get_name()+"::read_ActiveAlarms()")

        #    Add your own code here
        attr_ActiveAlarms_read = ['%s:%s:%s' 
            % (tag_name,time.ctime(alarm.active),alarm.formula) 
            for tag_name,alarm in self.Alarms.items() if alarm.active][-512:]
        attr.set_value(attr_ActiveAlarms_read, len(attr_ActiveAlarms_read))

#------------------------------------------------------------------
#    Read AcknowledgedAlarms attribute
#------------------------------------------------------------------
    def read_AcknowledgedAlarms(self, attr):
        #self.debug( "In "+self.get_name()+"::read_AcknowledgedAlarms()")

        #    Add your own code here
        attr_AcknowledgedAlarms_read = sorted(self.AcknowledgedAlarms)[-512:]
        attr.set_value(attr_AcknowledgedAlarms_read, 
                       len(attr_AcknowledgedAlarms_read))

#------------------------------------------------------------------
#    Read DisabledAlarms attribute
#------------------------------------------------------------------
    def read_DisabledAlarms(self, attr):
        #self.debug( "In "+self.get_name()+"::read_DisabledAlarms()")

        #    Add your own code here
        [self.CheckDisabled(t) for t in self.DisabledAlarms]
        attr_DisabledAlarms_read = sorted('%s:%s'%(a,time2str(t)) 
                        for a,t in self.DisabledAlarms.items())[-512:]
        attr.set_value(attr_DisabledAlarms_read, len(attr_DisabledAlarms_read))

#------------------------------------------------------------------
#    Read FailedAlarms attribute
#------------------------------------------------------------------
    def read_FailedAlarms(self, attr):
        #self.debug( "In "+self.get_name()+"::read_FailedAlarms()")

        #    Add your own code here
        attr_FailedAlarms_read = self.FailedAlarms.keys()[-512:]
        attr.set_value(attr_FailedAlarms_read, len(attr_FailedAlarms_read))


#------------------------------------------------------------------
#    Read PastAlarms attribute
#------------------------------------------------------------------
    def read_PastAlarms(self, attr):
        #self.debug( "In "+self.get_name()+"::read_PastAlarms()")

        #    Add your own code here
        attr_PastAlarms_read = []
        dates = self.PastAlarms.keys()
        dates.sort(reverse=True)
        for date in dates:
            for tag_name in self.PastAlarms[date]:
                attr_PastAlarms_read.append('%s:%s:%s' 
                  % (tag_name,time.ctime(date),self.Alarms[tag_name].formula))
                if len(attr_PastAlarms_read) == 512: break
        
        attr.set_value(attr_PastAlarms_read, len(attr_PastAlarms_read))

#------------------------------------------------------------------
#    Read AlarmList attribute
#------------------------------------------------------------------
    def read_AlarmList(self, attr):
        #self.debug( "In "+self.get_name()+"::read_AlarmList()")

        #    Add your own code here
        attr_AlarmList_read = sorted('%s:%s'%(a.tag,a.formula) 
                                     for a in self.Alarms.values())
        attr.set_value(attr_AlarmList_read, len(attr_AlarmList_read))
        
#------------------------------------------------------------------
#    Read AlarmSummary attribute
#------------------------------------------------------------------
    def read_AlarmSummary(self, attr):
        #self.debug( "In "+self.get_name()+"::read_AlarmSummary()")

        #    Add your own code here
        attr.set_value(self.last_summary, len(self.last_summary))
        

#------------------------------------------------------------------
#    Read AlarmReceivers attribute
#------------------------------------------------------------------
    def read_AlarmReceivers(self, attr):
        #self.debug( "In "+self.get_name()+"::read_AlarmReceivers()")

        #    Add your own code here
        attr_AlarmReceivers_read = sorted('%s:%s'%(k,v.receivers) 
                        for k,v in self.Alarms.items())
        attr.set_value(attr_AlarmReceivers_read, len(attr_AlarmReceivers_read))


#------------------------------------------------------------------
#    Read PhoneBook attribute
#------------------------------------------------------------------
    def read_PhoneBook(self, attr):
        self.debug( "In "+self.get_name()+"::read_PhoneBook()")

        #    Add your own code here
        attr_PhoneBook_read = sorted('%s:%s'%(k,v) 
                    for k,v in self.Alarms.get_phonebook().items() if v)
        attr.set_value(attr_PhoneBook_read, len(attr_PhoneBook_read))
        self.debug('Phonebook: %s' % str(attr_PhoneBook_read))


#------------------------------------------------------------------
#    Read SentEmails attribute
#------------------------------------------------------------------
    def read_SentEmails(self, attr):
        #self.debug( "In "+self.get_name()+"::read_SentEmails()")

        #    Add your own code here
        attr_SentEmails_read = []
        for key,value in self.SentEmails.items():
            attr_SentEmails_read.append(key)
            attr_SentEmails_read.append(str(value))
        self.info( 'SentEmails are: %s'%attr_SentEmails_read)
        attr.set_value(attr_SentEmails_read, 2, len(attr_SentEmails_read)/2)


#------------------------------------------------------------------
#    Read SentSMS attribute
#------------------------------------------------------------------
    def read_SentSMS(self, attr):
        self.debug( "In "+self.get_name()+"::read_SentSMS()")

        #    Add your own code here
        attr_SentSMS_read = []
        for key,value in self.SentSMS.items():
            attr_SentSMS_read.append(key)
            attr_SentSMS_read.append(str(value))
        self.debug( 'SentSMS are: %s'%attr_SentSMS_read)
        attr.set_value(attr_SentSMS_read, 2, len(attr_SentSMS_read)/2)
        
    def getMemUsage(self):
        return fandango.linos.get_memory()/1e3
    
    def read_MemUsage(self, attr):
        self.debug("In read_MemUsage()")
        attr.set_value(self.getMemUsage())
        
    def read_LastUpdate(self, attr):
        self.debug("In read_LastUpdate()")
        attr.set_value(self._lastupdate)        


#==================================================================
#
#    PyAlarm command methods
#
#==================================================================

#------------------------------------------------------------------
#    EvaluateFormula command:
#
#    Description: Evaluate an Alarm formula
#
#    argin:  DevString    formula to evaluate
#    argout: DevString
#------------------------------------------------------------------
    def EvaluateFormula(self,argin,tag_name=None,as_string=True,
        lock=False,_locals=None,variables=None):
        """
        This method can be called from both updateAlarms thread or external clients 
        """
        #    Add your own code here
        external = not tag_name #command called outside of process_alarm()
        
        t0,STATE,RAISE,VALUE = time.time(),False,False,None
        variables = notNone(variables,{})
        try:
            if lock: self.lock.acquire()
            # Update Locals (Done here to minimize time in which WorkerProcess is IDLE)
            if argin in self.Alarms: 
                argin,tag_name = self.Alarms[argin].formula,argin
            _locals = _locals or {}
            _locals['TAG'] = tag_name
            _locals = self.update_locals(_locals)
            formula = self.Panic.replace_alarms(argin) 
            #This replace those alarm names that are not in locals()
            varnames = self.Eval.parse_variables(formula,_locals)
            
            (self.debug if tag_name else self.info)(
              'In EvaluateFormula(%s): %d variables from %s'%(
                tag_name or formula,len(varnames),
                  sorted(set([tuple(v)[0] for v in varnames]))))
              
            STATE = any((not attribute or attribute.lower().strip() == 'state') 
                        for device,attribute,what in varnames)

            RAISE = ((STATE and self.RethrowState) 
                        or self.RethrowAttribute 
                        or fandango.isFalse(self.IgnoreExceptions))

            if not RAISE: 
                RAISE = fandango.NaN if fandango.isNaN(self.IgnoreExceptions)\
                    else None
                
            self.debug('In EvaluateFormula(%s): '
                'STATE = %s, RAISE = %s, RS=%s, RA=%s, Ignore=%s'
                    %(tag_name or formula,STATE,RAISE,self.RethrowState,
                        self.RethrowAttribute,self.IgnoreExceptions))
            
            ##################################################################
            # ALARM EVALUATION
            
            if self.worker and tag_name:
                if tag_name in self.Alarms:
                    alarm = self.Alarms[tag_name]
                    self.debug('\tself.worker.get(%s)'%alarm.tag)
                    VALUE = self.worker.get(alarm.tag,None,_raise=RAISE)
                else:
                    STATE = True
                    raise Exception('UNKNOWN ALARM %s!!'%tag_name)
            else:
                if 'debug' in str(self.getLogLevel()).lower(): 
                    self.Eval._trace = True
                    
                VALUE = self.Eval.eval(formula,_raise=RAISE)
            ##################################################################

            if tag_name: #not external
                # variables value returned as process_alarms cache
                variables.update(
                    self.get_last_values(alarm=tag_name,variables=varnames))
            else:
                variables.update(self.Eval.last)

            svalue = str(type(VALUE)) if isinstance(VALUE,Exception) \
                        else str(VALUE)
            self.debug(shortstr('EvaluateFormula(%s): %s, Values = %s'
                                %(tag_name or formula,svalue,variables),512))

        except Exception,e:
            
            desc = except2str(e)
            if STATE: 
                self.warning('-> Exception while checking State alarm %s:'
                    %tag_name+'\n%s'%formula+'\n%s'%(traceback.format_exc()))
            else:
                self.info( '-> Exception while checking alarm %s:\n%s'
                          %(tag_name or formula,except2str(e) ) )
            if RAISE: #(self.RethrowState and STATE) or self.RethrowAttribute:
                # Exceptions reading State attributes trigger alarms
                ###############################################################
                VALUE = fandango.NaN if fandango.isNaN(RAISE) else e
                #desc or str(e) or 'Exception!' #Must Have a Value!
                #variables = self.get_last_values(alarm=tag_name,variables=variables)
                variables.update({tag_name or 'VALUE':VALUE})
            elif external:
                VALUE = str(VALUE) + '(%s )'%e
            else:
                if tag_name: 
                    self.FailedAlarms[tag_name]=desc
                self.info('-> Exceptions in Non-State attributes (%s) '\
                    'do not trigger Alarm'%(tag_name or formula))

        finally: 
            if lock: self.lock.release()

        if external:
            return ('%s (IgnoreExceptions=%s,RethrowState=%s,'
                    'RethrowAttribute=%s)'
                        %(VALUE,self.IgnoreExceptions,self.RethrowState,
                          self.RethrowAttribute))
        else:
            self.EvalTimes[tag_name] = time.time()-t0            
            return str(VALUE) if as_string else VALUE

#------------------------------------------------------------------
#    ResetAlarm command:
#
#    Description: Reset alarm, it will be removed from active alarms
#
#    argin:  DevVarStringArray    This is used to inform which alarm should be reset and message. If it doesn't exst an error occurs
#    argout: DevVarStringArray    If succeed, returns the list of ActiveAlarms
#------------------------------------------------------------------
    def ResetAlarm(self, argin):
        self.info( "In "+self.get_name()+"::ResetAlarm()")
        #    Add your own code here
        if len(argin)==1: raise Exception('UserMessageRequiredAs2ndArgument')#tag,userMessage = argin[0],''
        else: tag,userMessage = argin[:2]

        argout = self.get_active_alarms() if self.free_alarm(tag, userMessage, message='RESET') else [] 
        return argout

#------------------------------------------------------------------
#    Acknowledge command:
#
#    Description: Acknowledge alarm no more reminders will be sent
#
#    argin:  DevString    This is used to inform which alarm should be acknowledged. , no more reminders will be sent.
#    argout: DevString    If succeed, returns DONE
#------------------------------------------------------------------
    def Acknowledge(self, argin):
        self.info( "In "+self.get_name()+"::Acknowledge()")
        #    Add your own code here
        if len(argin)==1: tag,userMessage = argin[0],''
        else: tag,userMessage = argin[:2]
        if str(argin[0]) in self.Alarms:
            self.Alarms[str(argin[0])].acknowledged = time.time()
            self.AcknowledgedAlarms.add(str(argin[0]))

        argout = self.get_active_alarms() if self.free_alarm(tag, userMessage, message='ACKNOWLEDGED') else []
        return argout

#------------------------------------------------------------------
#    Renounce command:
#
#    Description: Renounce (opposite to acknowledge) all emails will be send again
#
#    argin:  DevString    This is used to inform which alarm should be renounced.
#    argout: DevString    If succeed, returns DONE
#------------------------------------------------------------------
    def Renounce(self, argin):
        self.info( "In "+self.get_name()+"::Renounce()")
        #    Add your own code here
        argin = str(argin[0] if isSequence(argin) else argin)
        if argin in self.Alarms:
            self.Alarms[argin].acknowledged = 0
            self.AcknowledgedAlarms.remove(argin)
            return 'DONE'
        else: return '%s_NotFound'%argin

#------------------------------------------------------------------
#    Enable command:
#
#    Description: Enable alarm that was put to disable state
#
#    argin:  DevString    This is used to inform which alarm should be enabled.
#    argout: DevString    If succeed, returns DONE
#------------------------------------------------------------------
    def Enable(self, argin):
        self.info( "In "+self.get_name()+"::Enable(%s)"%argin)
        #    Add your own code here
        argin = str(argin)
        if argin in self.Alarms:
            #self.Alarms[argin].active = True
            self.DisabledAlarms.pop(argin)
            self.Alarms[argin].disabled = 0
            return 'DONE'
        else: return '%s_NotFound'%argin

#------------------------------------------------------------------
#    Disable command:
#
#    Description: Disable enabled alarm.
#
#    argin:  DevString    (TAG,comment,[timeout s/m/h/d]) Disable an alarm (skips update loop) until timeout.
#    argout: DevString    If succeed, returns DONE
#------------------------------------------------------------------
    def Disable(self, argin):
        """
        (TAG,comment,[timeout s/m/h/d]) Disable an alarm (skips update loop) until timeout.
        """
        self.info( "In "+self.get_name()+"::Disable(%s)"%argin)
        #    Add your own code here
        args = [(re.match(RAW_TIME,y),y) for x in map(str,argin) 
                for y in map(str.strip,x.split(','))]
        argin = [x[1] for x in args if not x[0]]
        tag,userMessage = argin[0],','.join(argin[1:])
        if tag in self.Alarms:
            try: 
                #Setting Disable Timeout
                self.DisabledAlarms[tag] = time.time()+\
                    [str2time(x[1]) for x in args if x[0]][0]
            except:
                self.DisabledAlarms[tag] = END_OF_TIME
                
            self.Alarms[str(argin[0])].disabled = self.DisabledAlarms[tag]
            # Order of actions matter
            self.free_alarm(tag, userMessage, message='DISABLED')
            self.Alarms[str(argin[0])].active = 0            
            return 'DONE'
        else: 
            raise Exception('%s_NotFound'%argin)

#------------------------------------------------------------------
#    CheckDisabled command:
#
#    Description: Check if the alarm is on DisabledAlarms list.
#
#    argin:  DevString    This is used to inform which alarm should be checked.
#    argout: DevBoolean   True if alarm is disabled else False    
#------------------------------------------------------------------
    def CheckDisabled(self, argin):
        #self.debug( "In "+self.get_name()+"::CheckDisabled(%s)"%argin)
        #    Add your own code here
        argin = str(argin)
        if argin in self.DisabledAlarms: 
            if 0<self.DisabledAlarms[argin]<time.time(): 
                self.info('\tReenabling %s after timeout'%argin)
                self.Enable(argin)
                return False
            else:
                return True
        else: return False

#------------------------------------------------------------------
#    CheckAcknowledged command:
#
#    Description: Check if the alarm is on AcknowledgedAlarms list.
#
#    argin:  DevString    This is used to inform which alarm should be checked.
#    argout: DevBoolean   True if alarm is acknowledged else False    
#------------------------------------------------------------------
    def CheckAcknowledged(self, argin):
        self.debug( "In "+self.get_name()+"::CheckAcknowledged()")
        #    Add your own code here
        argin = str(argin)
        if argin in self.AcknowledgedAlarms: return True
        else: return False

#------------------------------------------------------------------
#    ResetAll command:
#
#    Description: Acknowledge alarm that was put into the file with alarms, it will be removed from active alarms
#
#    argin:  DevString    This command requires a user message
#    argout: DevString    If succed, returns the list of ActiveAlarms reseted
#------------------------------------------------------------------
    def ResetAll(self, argin):
        self.info( "In "+self.get_name()+"::ResetAll()")
        #    Add your own code here
        argout = self.get_active_alarms()
        [self.ResetAlarm([key,argin[0]]) for key in argout]
        return 'RESET:'+','.join(argout or ['DONE'])
    
#------------------------------------------------------------------
#    GetRelease command:
#
#    Description: 
#
#    argin:  DevVoid 
#    argout: DevString
#------------------------------------------------------------------
    def GetRelease(self, argin=None):
        self.info( "In "+self.get_name()+"::GetRelease()")
        #    Add your own code here
        return str(__RELEASE__)

#------------------------------------------------------------------
#    AddReceiver command:
#
#    Description: Returns the actual value of AlarmReceivers
#
#    argin:  DevVarStringArray    Attribute,ReceiverAddress
#    argout: DevVarStringArray    Adds a new Receiver to AlarmReceivers property
#------------------------------------------------------------------
    def AddReceiver(self, argin):
        self.info( "In "+ self.get_name()+ "::AddReceiver()")
        #    Add your own code here
        argout = ['FAILED']
        try:
            self.lock.acquire()
            self.Alarms[argin[0]].add_receiver(argin[1])
            argout = ['%s:%s'%(tag,','.join(alarm.receivers)) for tag,alarm in self.Alarms.items()]
        finally:
            self.lock.release()
        return argout

#------------------------------------------------------------------
#    GetAlarmInfo command:
#
#    Description: Method to retrieve Alarm data from clients
#
#    argin:  DevVarStringArray    tag,request (SETTINGS, VALUES, FORMULA)
#    argout: DevVarStringArray
#------------------------------------------------------------------
    def GetAlarmInfo(self,argin):
        tag = argin[0]
        request = (argin[1:] or ('SETTINGS','STATUS','VALUES'))
        
        assert tag in self.Panic,'UnknownAlarm:%s!'%tag
        alarm = self.Alarms[tag]
        
        if len(request)==1 and str(request[0]).upper() in MESSAGE_TYPES:
                return self.GenerateReport(tag,request)
            
        result = []
        for r in request:
            
            if r in DATA_FIELDS+STATE_FIELDS:
                v = alarm.get_any(r)
                result.append('%s=%s'%(r,v))
                
            elif not r.upper() in INFO_REQUESTS:
                raise Exception('UnknownRequest:%s!'%request)
            
            else:
                r = r.upper()
                
            if r == 'SETTINGS':
                for l in DATA_FIELDS:
                    v = alarm.get_any(l)
                    if l=='message': v=json.dumps(v)
                    result.append('%s=%s'%(l,v))

            if r == 'STATUS':
                for l in STATE_FIELDS:
                    v = alarm.get_any(l)
                    if isNumber(v) and float(v)>1e9:
                        v = time2str(float(v))
                    result.append('%s=%s'%(l,v))                

            def vals_to_str(vs):
                return ['values=%s'%json.dumps(vs)]
                #for k,v in vs.items():
                    #try: s = str(v)
                    #except Exception,e: s = str(e)
                    #if not isNumber(s): s = "'%s'"%s
                    #result.append('%s:%s'%(k,s))
                            
            if r in ('VALUES','SNAP'):
                cache = {'VALUES':self.LastValues,'SNAP':self.PastValues}[r]
                result.extend(vals_to_str(cache.get(tag,{})))

        return result

#------------------------------------------------------------------
#    GenerateReport command:
#
#    Description: Generates report message
#
#    argin:  DevVarStringArray    tag,message,receivers,...,description
#    argout: DevVarStringArray
#------------------------------------------------------------------

    def GenerateReport(self,tag,receivers='',message='DETAILS',
                       values=None, user_comment=None, html=False,
                       evals=False,others=False,pasts=False):
        """
        When called from Tango a single argument is received; which is a list 
        containing all arguments
        
        :param message: Can be one of MESSAGE_TYPES or a different text
        """
        self.info('In GenerateReport(%s,%s,%s)'%(tag,receivers,message))
        result = ['FAILED']
        if isSequence(tag):
            #being called from an external Tango client
            receivers = [m for m in tag[1:] if m not in MESSAGE_TYPES]
            if tag[-1] in MESSAGE_TYPES: message = tag[-1] 
            tag = first(tag)
          
        assert tag in self.Alarms,'UnknownAlarm:%s!'%tag
        alarm = self.Alarms[tag]
        
        if message in INFO_REQUESTS: return self.GetAlarmInfo(tag,message)
        
        #Check last report for this alarm
        if (tag,message,alarm.active,html) in self.Reports[tag]:
            result = self.Reports[tag][(tag,message,alarm.active,html)]
            return result
        
        self.info( 'In GenerateReport(%s,%s,%s,%s,%s)'
            %(tag,message,getattr(alarm,'active',None),
              html,receivers))
          
        #Check email receivers
        #----------------------------------------------------------------------
        if isString(receivers): receivers = receivers.split(',')

        alnum = '[a-zA-Z0-9-_.]+'
        email = '(' + alnum + '@' + alnum + ')'
        maillist = [s for r in receivers for s in re.findall(email,r)]
        maillist = sorted(set(map(str.lower,maillist)))

        report = 'TAG: %s'%tag
        report += '\n%s at %s\n'%(message,time2str())
        actives = self.get_active_alarms()
        subject = '%s %s'%(tag,message)
        values = values or self.PastValues.get(tag,None)

        if message in ('DETAILS',tag):
            first_row = ''
            if self.Alarms[tag].active:
                first_row += ('\n\t' + 'Alarm ACTIVE since %s'
                    %(time2str(self.Alarms[tag].active 
                                   or self.PastAlarms.get(tag,0))))
                if self.Alarms[tag].recovered:
                    first_row += ('\n\t' + 'Alarm conditions recovered at %s'
                        %(time2str(self.Alarms[tag].recovered)))
            else:
                first_row = '\n\t' + 'The alarm is not active.'
                
        elif message in ('ALARM','REMINDER'):
            first_row = ('\n\t' + 'Alarm active since %s'
                %(time2str(self.Alarms[tag].active )
                               or self.PastAlarms.get(tag,0)))
        elif message in ('RECOVERED','AUTORESET'):
            first_row = '\n\t' + '%s at %s' %(message,time2str())
            
        elif message in ('ACKNOWLEDGED','RESET','DETAILS','DISABLED'):
            first_row = '\n\t' + '%s at %s' %(message,time2str())
            first_row += '\n\tDetails -> %s'%user_comment
            
        elif message not in MESSAGE_TYPES:
            first_row = '\n\t%s'%message

        report += first_row
        report += '\n\tAlarmDevice: %s'%self.get_name()
        report += '\n\tDescription: %s'%self.Alarms[tag].parse_description()
        report += '\n\tSeverity: %s'%self.Alarms[tag].parse_severity()
        report += '\n\tFormula: %s'%(self.Alarms[tag].formula)
        
        if values:
            report += '\n\n' + 'Values are: \n'
            if hasattr(values,'items'):
                try: 
                    invkey = lambda t:(t[-1],t[0])
                    for k,v in sorted(values.items(),key=invkey):
                        m = fandango.tango.parse_tango_model(k)
                        dev = m and m.get('devicename')
                        attr = m and m.get('attribute')
                        if dev in self.Panic.devices and attr in self.Panic:
                            k = attr
                        elif k.lower().strip().endswith('/state'): 
                            v = str(PyTango.DevState.values.get(v,v))
                        elif isSequence(v) and len(v) and isString(v[0]): 
                            v = ';\n\t'.join(v)
                        report+='\t%s:\t%s'%(k,v) + '\n'
                except: 
                    msg = traceback.format_exc()
                    report += str(values).strip() or msg
                    self.error('Error parsing values for email:\n%s'%msg)
            else: report+= str(values).strip()
            
        report += '\n\n'+'Alarm receivers are:'+'\n\t%s'%alarm.receivers
        if evals:
            report += '\n\n' + 'EvalTimes are: \n %s\n'%(self.EvalTimes)
        if others and len(actives) > 1:
            try: 
                report+= ('\n\n' + 'Other Active Alarms are:' 
                    + '\n\t'.join([''] + sorted(
                    ['%s:%s:%s'%(k,time2str(v.active),
                                    self.Alarms[k].formula) 
                        for k,v in self.Alarms.items() if v.active])))
            except: pass
        
        if pasts and self.PastAlarms:
            report+= ('\n\n' + 'Past Alarms were:' + '\n\t'.join([''] 
                + ['%s:%s'%(','.join(k),time2str(d)) 
                for d,k in sorted(self.PastAlarms.items())[-10:]]))
        if html:
            result = [report, subject]
        else:
            result = [report, subject, ','.join(maillist)]
            
        self.Reports[tag] = {(tag,message,alarm.active,html):result}
        self.debug( 'Out of GenerateReport(%s,%s,%s)'%(tag,receivers,message))
        self.debug( '>'*80)
        return result
    
    def GenerateSummary(self, push=True):
        sep = ';' #';' #','
        #setup = 'tag','description','formula'
        setup = SUMMARY_FIELDS
        
        [alarm.get_state(force=True) for alarm in self.Alarms.values()]
        attr_AlarmSummary_read = []
        for alarm in self.Alarms.values():
            l = ['%s=%s' % (s,(str if s!='time' else time2str)
                    (getattr(alarm,s) if s!='state' or self.get_enabled() 
                    else 'DSUPR')) for s in setup]
            attr_AlarmSummary_read.append(sep.join(l))
          
        # Should be True only when called from Commands
        if push:
            try:
                changed = []
                for a in self.Alarms:
                    k = 'tag=%s,' % a
                    s = [l for l in attr_AlarmSummary_read if k in l]
                    if s != [l for l in self.last_summary if k in l]:
                        changed.append(a)
                if (changed or len(attr_AlarmSummary_read)!=len(self.last_summary)
                    or self.last_push+(self.PollingPeriod*self.AlarmThreshold) <= time.time()):
                    [self.alarm_attr_read(a,push=True) for a in changed]
                    self.push_change_event('AlarmSummary',attr_AlarmSummary_read)
                    self.last_push = time.time()
                    self.info('read_AlarmSummary(): %d events pushed'  % len(changed))
            except:
                self.error(traceback.format_exc())

        # THIS MUST BE ALWAYS AT THE END
        self.last_summary = sorted(attr_AlarmSummary_read)
        return self.last_summary
        
#------------------------------------------------------------------
#    CreateAlarmContext command:
#
#    Description: Creates an Snap context for this Alarm
#
#    argin:  DevVarStringArray    tag,attributes
#    argout: DevLong              new context id
#------------------------------------------------------------------
    def CreateAlarmContext(self, argin):
        tag_name,existingAttrsAllowed = argin[0],argin[1:]
        self.info('In CreateAlarmContext(%s,%s)'%(tag_name,existingAttrsAllowed))
        res=self.snap.db.search_context(tag_name)
        res = sorted(c for c in res if c['reason']=='ALARM')
        cids = [c['id_context'] for c in res]
        if cids:
            self.warning('Cannot create a context - already exist!')
            return cids[0]
        if not existingAttrsAllowed:
            vars = self.Eval.parse_variables(self.Alarms[tag_name].formula)
            existingAttrsAllowed = [str(var[0]+'/'+var[1]) for var in vars if (self.snap.check_attribute_allowed(str(var[0]+'/'+var[1])))]
        if not existingAttrsAllowed:
            self.warning('Cannot create a context - alarm attributes list empty!')
            return -1        
        ctx=self.snap.create_context('AlarmAPP',tag_name,'ALARM',self.Alarms[tag_name].formula,existingAttrsAllowed)
        return ctx.ID
      
#------------------------------------------------------------------
#    SendAlarm command:
#
#    Description: Testing Alarm receivers
#
#    argin:  DevVarStringArray    ALARM/Formula/True,[receivers, ...]
#    argout: DevString
#------------------------------------------------------------------

    def SendAlarm(self, argin):
        """
        Arguments: ALARM or Formula, receivers to test
        """
        try:
            argin = toSequence(argin)
            alarm = argin[0]
            receivers = argin[1:]

            if alarm not in self.Alarms:
                v = self.EvaluateFormula(alarm)
                if not v:
                  report = str(v)
                alarm = 'SendAlarmCommand'

            else:
                receivers = receivers or self.Alarms[alarm].receivers
            
            report = self.send_alarm(alarm,message='ALARM',receivers=receivers)
            
            return 'DONE'
        except Exception,e:
            msg = traceback.format_exc()
            self.info( 'Exception in PyAlarm.SendAlarm(): \n%s'%msg)
            return msg

#------------------------------------------------------------------
#    SendMail command:
#
#    Description: Sends a mail message
#
#    argin:  DevVarStringArray    message,subject,receivers
#    argout: DevBoolean
#------------------------------------------------------------------

    def SendMail(self, argin):
        """
        Arguments: message, subject, receivers
        """
        self.info('SendMail(%s,%s)'%(self.MailMethod,argin))
        def format4sendmail(report):
            out = report.replace('\r','\n').replace('\n\n','\n')
            out = out.replace('\n','\\n').replace('"',"'") #.replace("'",'')
            return out
        try:
            if self.MailMethod.startswith('smtp'):
                import smtplib
                from email.mime.text import MIMEText
                text = argin[0]
                msg = MIMEText(text)
                msg['Subject'] = argin[1]
                msg['From'] = self.FromAddress
                receivers = argin[2] if isSequence(argin[2]) \
                                else str(argin[2]).split(',')
                msg['To'] = receivers[0]
                s = smtplib.SMTP()
                args = self.FromAddress,receivers,msg.as_string()
                self.info( 'Launching %s command: '%self.MailMethod
                            +shortstr(str(args)))
                s.connect(*(self.MailMethod.split(':')[1:]))
                s.sendmail(*args)

            elif self.MailMethod == 'mail':
                command = 'echo -e "'+format4sendmail(argin[0])+'" '
                command += '| mail -s "%s" ' % argin[1]
                if len(self.MailDashRoption) > 0:
                    command += '-r %s ' % (self.FromAddress)
                else:
                    # Legacy sendmail for old Linux
                    command += '-S from=%s ' % self.FromAddress 
                # Add Receivers
                command += ' -- '+(argin[2] if isString(argin[2]) 
                        else ','.join(argin[2]))
                self.info( 'Launching mail command: '+shortstr(command,512))
                # & needed in Debian to avoid timeouts
                os.system(command+ ' &')
                
                for m in argin[2].split(','):
                    self.SentEmails[m.lower()]+=1
                    
                return 'DONE'
        except Exception,e:
            self.info( 'Exception in PyAlarm.SendMail(): \n%s'
              %traceback.format_exc())
        return 'FAILED'

#------------------------------------------------------------------
#    SaveHtml command:
#
#    Description: Saves a html report
#
#    argin:  DevVarStringArray    message,subject
#    argout: DevBoolean
#------------------------------------------------------------------

    def SaveHtml(self, argin):
        def format4html(report):
            out='<pre>'+report+'</pre>'
            return out
        try:
            report=argin[1]+'\n'+argin[0]
            logfile=argin[1].split(' ', 2)[1]+'.html'
            f=open(self.HtmlFolder+'/'+logfile, 'w')
            report = format4html(report) 
            f.write(report)
            f.close()
            return report
        except Exception,e:
            self.warning( 'Exception in PyAlarm.SaveHtml(): \n%s'%traceback.format_exc())
        return 'FAILED'

#------------------------------------------------------------------
#    SendSMS command:
#
#    Description: Sends an SMS message
#
#    argin:  DevVarStringArray    tag/message,receivers,...,
#    argout: DevBoolean
#------------------------------------------------------------------

    def SendSMS(self,tag,receivers=[],message='TEST',values=None):
        """
        Sending sms, completely dependent of your personal SMS configuration.
        :param tag_name:    Alarm or Test message to be sent
        :param receivers:   SMS numbers to receive the alarm
        """
        if not receivers and hasattr(tag,'__iter__'):
            tag,receivers = tag[0],tag[1:]
        alarm = (self.Alarms.get(tag) or [None])[0]

        self.info( 'In SendSMS(%s,%s,%s,%s)'%(tag,receivers,message,values))
        if not SMS_ALLOWED or not 'smslib' in globals():
            self.warning( 'SMS Messaging is not allowed '
                +'or smslib not available!!!')
            return 'NO_SMS_LIB'
        report = ''
        username,password=self.SMSConfig.split(':',1)
        source = self.FromAddress 
        self.warning('SendSMS from %s as  %s'%(source,username))

        now = time.time()
        try:
            self.lock.acquire()
            while self.SMS_Sent and self.SMS_Sent[0]<now-(24*60*60): 
                self.SMS_Sent.popleft()
            sends = len(self.SMS_Sent)
        finally:
            self.lock.release()
        if sends>=self.SMSMaxPerDay:
            self.warning( 'Max Daily SMS messages (%d) has been exceeded!' 
                % self.SMSMaxPerDay)
            return
        elif receivers:
            sms = '((SMS:)?([\+]?[0-9]{9,13}))'
            smslist = []
            for s in receivers:
                numbers = re.findall(sms,s)
                [smslist.append(t[-1]) for t in numbers if t and len(t)>1]
                smslist = list(set(smslist))
            if smslist:
                try:
                    self.lock.acquire()
                    self.info( 'SMS Sending: the phone numbers to be reported'
                                ' for %s are: %s' % (tag,','.join(smslist)) )
                    
                    if message in ('ALARM',) and tag in self.Alarms:
                      formula,text = (self.Alarms[tag].formula, ';%s'
                              %self.Alarms[tag].parse_description())
                    else:
                        formula,text = '',';%s'%message
                        
                    report = 'Alarm '+tag+': '+text
                    if values: 
                        svalues = str(values).replace('{','').replace('}','')
                        report += ';Values=' + svalues
                        
                    if len(report) > self.SMSMaxLength:
                        report = report[:self.SMSMaxLength-5]+'...'
                        
                    self.info( 'SMS Sending: message is: %s' % (report))
                except:
                    self.warning( 'Exception generating SMS report: %s' 
                                 % traceback.format_exc())
                finally:
                    self.lock.release()

                if 'sms' not in self.AlertOnRecovery.lower() \
                        and message.strip() not in ('ALARM','TEST'):
                    self.warning('SMS sending not allowed for %s message type.'
                                 %message)
                else:
                    #To avoid device hungs all SMS messages are sent 
                    #in a background thread
                    if not hasattr(self,'sms_threads'): self.sms_threads = 0
                    self.sms_threads += 1
                    self.info( 'Sending SMS in a different Thread ... '
                        +'SMS-Send%d'%self.sms_threads)
                    source = (re.findall('[a-zA-Z]+',str(source)) or [''])[0]
                    thr = smslib.SMSThread(message=report, dest=smslist, 
                        username=username, password=password, source=source)
                    thr.setDaemon(True)
                    thr.start()
                    self.SMS_Sent.append(now)
                    
                    self.info( '%d SMS messages sent in the last 24 hours.' 
                                % len(self.SMS_Sent))
                    for s in smslist:
                        self.SentSMS[s.lower()]+=1
                return 'DONE'
        return 'FAILED'
    
    #------------------------------------------------------------------
    #    SendTelegram command:
    #
    #    Description: Sends a Telegram message
    #
    #    argin:  DevVarStringArray    tag/message,receivers,...,
    #    argout: DevBoolean
    #------------------------------------------------------------------
    def SendTelegram(self,tag,receivers=[],message='TEST',values=None):
        """
        Sending telegram
        :param tag_name:    Alarm or Test message to be sent
        :param receivers:   telegram chat IDs to receive the alarm
        """
        if not receivers and hasattr(tag, '__iter__'):
            tag,receivers = tag[0], tag[1:]
        alarm = (self.Alarms.get(tag) or [None])[0]
        self.info('In SendTelegram(%s, %s, %s, %s)' %
                   (tag, receivers, message, values))
        report = ''
        token = self.TGConfig
        now = time.time()
        if receivers:
            # Build report
            try:
                self.lock.acquire()
                report = 'TAG: ' + tag
                if values:
                    report = ''
                    self.debug( 'Telegram values: %s' % str( values))
                    report = values[0]
                elif message in ('ALARM',) and tag in self.Alarms:
                    report += '\nFormula: ' \
                              + self.Alarms[tag].formula \
                              + '\nDescription: ' \
                              + self.Alarms[tag].parse_description()
                else:
                    report += str(message)
                self.info('TG Sending: message %s to %s'
                           % (report, ','.join(receivers)))
                report = report.replace('\r', '\n').replace('\n\n', '\n')
                report = report.replace('\n', '%0a').replace("'", '"')
            except:
                self.warning('Exception generating TG report: %s'
                             % traceback.format_exc())
            finally:
                self.lock.release()
                
            if ('tg' not in self.AlertOnRecovery.lower()
                and message.strip() not in ('ALARM','TEST')):
                self.warning('TG sending not allowed for %s message type.'
                             % message)
            else:
                # Send Telegram
                for r in receivers:
                    id = str(r).split(":")[-1]
                    url = ('https://api.telegram.org/bot'
                           + token
                           + "/sendMessage?chat_id="
                           + id
                           + "&text="
                           + report)
                    self.info('Telegram url: "%s"' % url)
                    urllib.urlopen(url)
                return 'DONE'
        return 'FAILED'    

#==================================================================
#
#    PyAlarmClass class definition
#
#==================================================================
#class PyAlarmClass(PyTango.PyDeviceClass):
class PyAlarmClass(PyTango.DeviceClass):

    #    Class Properties
    class_property_list = PANIC_PROPERTIES

    #    Device Properties
    device_property_list = DEVICE_PROPERTIES

    #    Command definitions
    cmd_list = {
        'AddReceiver':
            [[PyTango.DevVarStringArray, "Attribute,ReceiverAddress"],
            [PyTango.DevVarStringArray, "AlarmReceivers values"],
            {
                #'Display level':PyTango.DispLevel.EXPERT,
             } ],
        'EvaluateFormula':
            [[PyTango.DevString, 
              "alarm formula to test with the current environment"],
            [PyTango.DevString,"result obtained"]],
        'ResetAlarm':
            [[PyTango.DevVarStringArray, 
              "This is used to inform which alarm should be reset."
              " If it doesn't exst an error occurs, comment"],
            [PyTango.DevVarStringArray, 
              "If succeed, returns the list of ActiveAlarms"]],
        'Acknowledge':
            [[PyTango.DevVarStringArray, 
              "This is used to inform which alarm should be acknowledged,"
              " no more reminders will be sent, comment"],
            [PyTango.DevVarStringArray, 
              "If succeed, returns the list of ActiveAlarms"]],
        'Renounce':
            [[PyTango.DevString, 
              "This is used to inform which alarm should be renounced,"
              " all reminders will be send again."],
            [PyTango.DevString, "If succeed, returns DONE"]],
        'Enable':
            [[PyTango.DevString, 
              "(TAG,) This is used to inform which alarm should be enabled,"
              " alarm won't skip the updateAlarms loop"],
            [PyTango.DevString, "If succeed, returns DONE"]],
        'Disable':
            [[PyTango.DevVarStringArray, 
              "(TAG,comment,[timeout s/m/h/d]) Disable an alarm, "
              "skips update loop until timeout"],
            [PyTango.DevString, "If succeed, returns DONE"]],
        'ResetAll':
            [[PyTango.DevVarStringArray, "User message"],
            [PyTango.DevString, ""]],
        'GetRelease':
            [[PyTango.DevVoid, ""],
            [PyTango.DevString, ""]],
        'GetAlarmInfo':
            [[PyTango.DevVarStringArray, 
              "tag,request(STATE,SETTINGS,VALUES,SNAP)"],
            [PyTango.DevVarStringArray,"result, subject, receivers"]],            
        'GenerateReport':
            [[PyTango.DevVarStringArray, 
              "tag,message,receivers,...,description"],
            [PyTango.DevVarStringArray,"result, subject, receivers"]],
        'CreateAlarmContext':
            [[PyTango.DevVarStringArray, "tag,attributes,..."],
            [PyTango.DevLong,"new context ID"]],
        'SendAlarm':
            [[PyTango.DevVarStringArray, "alarm/formula/True,receivers,..."],
            [PyTango.DevString,""]],
        'SendMail':
            [[PyTango.DevVarStringArray, "message,subject,receivers"],
            [PyTango.DevString,""]],
        'SaveHtml':
            [[PyTango.DevVarStringArray, "message,subject"],
            [PyTango.DevString,""]],
        'SendSMS':
            [[PyTango.DevVarStringArray, "tag/message,receivers,..."],
            [PyTango.DevString,""]],
        'SendTelegram':
            [[PyTango.DevVarStringArray, "tag/message,receivers,..."],
            [PyTango.DevString,""]],            
        'CheckAcknowledged':
            [[PyTango.DevString, "alarm tag"],
            [PyTango.DevBoolean,"true if alarm is on the list else false"]],
        'CheckDisabled':
            [[PyTango.DevString, "alarm tag"],
            [PyTango.DevBoolean,"true if alarm is on the list else false"]],
        'GenerateSummary':
            [[PyTango.DevVoid, "generates summary, pushes events"],
            [PyTango.DevVarStringArray, "alarm summary"],
            {
                'Display level':PyTango.DispLevel.EXPERT,
                'Polling period': 1000,
            } ],            
        }

    #    Attribute definitions
    attr_list = {
        'VersionNumber':
            [[PyTango.DevString,
            PyTango.SCALAR,
            PyTango.READ],
            {
                'Display level':PyTango.DispLevel.EXPERT,
                'description':"Version number and release note",
            } ],
        'LastAlarm':
            [[PyTango.DevString,
            PyTango.SCALAR,
            PyTango.READ],
            {
                'Display level':PyTango.DispLevel.EXPERT,
                'description':"Returns Alarm:AlarmDate for last alarms until "
                              "each is read once, then it's cleared"
                              " (used for archiving)",
            } ],
        'AlarmConfiguration':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'Display level':PyTango.DispLevel.EXPERT,
                'description':"Returns Property:Value list",
            } ],
        ######################################################################
        'ActiveAlarms':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'description':"Retrieves a list of Active Alarms showing "
                              "AlarmDate:AlarmDescription",
            } ],
        'PastAlarms':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'description':"Returns AlarmDate:ACKDate:AlarmDescription"
                              " for all OLD alarms already acknowledged",
            } ],
        'AlarmList':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'description':"Returns the current definition of"
                              " alarms managed by this device",
            } ],
        'AlarmSummary':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'description':"Returns the current state and definition of"
                    " alarms managed by this device using key=value; syntax",
            } ],            
        'AlarmReceivers':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512],
            {
                'description':"Returns the list of Receivers for each Alarm",
            } ],
        'AcknowledgedAlarms':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512]],
        'DisabledAlarms':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512]],
        'FailedAlarms':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512]],
        ######################################################################
        'PhoneBook':
            [[PyTango.DevString,
            PyTango.SPECTRUM,
            PyTango.READ, 512]],
        'SentEmails':
            [[PyTango.DevString,
            PyTango.IMAGE,
            PyTango.READ, 512,512]],
        'SentSMS':
            [[PyTango.DevString,
            PyTango.IMAGE,
            PyTango.READ, 512,512]],
        ######################################################################
        'MemUsage':
            [[PyTango.DevDouble,
            PyTango.SCALAR,
            PyTango.READ]],
        'LastUpdate':
            [[PyTango.DevDouble,
            PyTango.SCALAR,
            PyTango.READ],
            {
                'description':"Timestamp of the last cycle completed",
            } ],
        }

    def dyn_attr(self,dev_list):
        print ( '>'*40)
        print ("In PyAlarmClass.dyn_attr(%s)"%sorted(dev_list))
        for dev in sorted(dev_list):
            print ("In PyAlarmClass.dyn_attr(%s)"%dev)
            dev.dyn_attr()

#------------------------------------------------------------------
#    PyAlarmClass Constructor
#------------------------------------------------------------------
    def __init__(self, name):
        print ( "In PyAlarmClass  constructor ...")
        import panic
        # Reload default property values
        # @TODO This update seems not needed!
        s = ''
        try:
            for k in panic.PyAlarmDefaultProperties:
                s = k
                p = panic.PyAlarmDefaultProperties[k][-1]
                s = str((k,p))
                PyAlarmClass.device_property_list[k][-1] = p
        
            s = name
            print(s+'...')
            PyTango.DeviceClass.__init__(self, name)
            self.set_type(name);
        except:
            print(s)
            traceback.print_exc()
            raise        
        print('done ...')

#==================================================================
#
#    PyAlarm class main method
#
#==================================================================

def main(args=None):
    import sys
    if args is None:
        args = sys.argv
    try:
        py = PyTango.Util(args)
        py.add_TgClass(PyAlarmClass,PyAlarm,'PyAlarm')
        try:
            if '--ddebug' in sys.argv:
                from fandango.device import DDebug          
                DDebug.addToServer(py,'PyAlarm',args[1])
        except Exception,e:
          print('Unable to add DDebug class to PyAlarm: ',e)
          
        U = PyTango.Util.instance()
        U.server_init()
        U.server_run()

    except PyTango.DevFailed,e:
        print '-------> Received a DevFailed exception:',traceback.format_exc()
    except Exception,e:
        print '-------> An unforeseen exception occured....',traceback.format_exc()
  
if __name__ == '__main__':
    main(sys.argv)
