def doExecute(self,function,runDpName,partition): """ Execute service request. The request is forwarded to each controlled client object. The callback name is given as a string. Parameters: @param function Callback name of the client @param runDpName Name of the RunInfo datapoint @param partition Partition name """ result = None r0 = None ##PVSS.info('Executing action:'+function,timestamp=1) for i in self.objects: if hasattr(i,function): result = getattr(i,function)(runDpName, partition) if r0 is None: r0 = result if result is None: PVSS.error('The controlled object '+i.name+' failed: '+function,timestamp=1,type=PVSS.ILLEGAL_ARG) return result PVSS.info('The controlled object '+i.name+' succeeded: '+function,timestamp=1) return r0
def make_shift_info(match): actor = PVSS.DpVectorActor(mgr) lookup = partition + '_RunInfoCond.' start = len(lookup) lookup = lookup + match type = mgr.typeMgr().type('RunInfoConditions') actor.lookup(DP.original(lookup), type) rd.add(actor.container) rd.execute() #print '---> ',actor.container.size(),'datapoints' look = lookup[:start] for i in actor.container: nam = i.name() nam = nam[nam.find(look) + len(look):nam.find(':_original..')] print '%-24s %s' % (nam, i.data)
def execute(args): "Argument dispatcher to start various streaming/option writers from the command line." wait = 0 typ = 'Storage' nam = None res = None pro = None function = None i = -1 while((i+1)<len(args)): i = i + 1 if args[i] == "-type": typ = args[i+1] i = i + 1 elif args[i] == "-debug": _dbg(int(args[i+1])) i = i + 1 elif args[i] == "-name": nam = args[i+1] i = i + 1 elif args[i] == "-project": pro = args[i+1] i = i + 1 elif args[i] == "-function": function = args[i+1] i = i + 1 elif args[i] == "-wait": wait = 1 else: print 'Ignored option ',args[i] if typ == 'Subfarm': function = runSubfarm elif typ == 'Farm': function = runFarm elif function is None: print 'Unknown action requested.' return None if function and nam: res = function(project=pro,name=nam) if res and wait != 0: import Online.PVSS as PVSS if not PVSS.batchMode(): print 'Hit CTRL-C to quit!' res[0].sleep() print '\n\nDone...\n\n' return 1
def showSummary(self, extended=None): actor = PVSS.DpVectorActor(self.manager) typ = self.manager.typeMgr().type('StreamPartition') actor.lookupOriginal(self.name + '_Slice*.Name', typ) partitions = [] log('Stream Control contains a total of ' + str(len(actor.container)) + ' partitions', timestamp=1) for i in xrange(len(actor.container)): nam = actor.container[i].name() nam = nam[nam.find(':') + 1:nam.find('.')] partitions.append(PartitionInfo.PartitionInfo(self.manager, nam)) for i in partitions: self.reader.add(i.datapoints) self.load() StreamingDescriptor.show(self, extended) return partitions
def findPartition(manager, name, partition): "Select partition by allocation name." actor = PVSS.DpVectorActor(manager) typ = manager.typeMgr().type('StreamPartition') rdr = manager.devReader() actor.lookupOriginal(name + '_Slice??.Name', typ) for i in actor.container: rdr.add(i) if rdr.execute(): for i in actor.container: nam = i.name() nam = nam[nam.find(':') + 1:nam.find('.')] id = int(nam[len(nam) - 2:], 16) # print i.data,partition if i.data == partition: return (i.name(), nam, id) return None return None
def installPartitions(): actor = PVSS.DpVectorActor(mgr) actor = JobOptions._getDevices('RunInfo', '', mgr, 0) actor.lookup('*_RunInfo', mgr.typeMgr().type('RunInfo')) for i in actor.container: name = i.name() name = name[name.find(':') + 1:] name = name[:name.find('_')] log('Partition:' + name, timestamp=1) run = StorageInfo.StorageInfo(mgr, name) run.load() p = JobOptions.Partition(mgr, name) if not p.exists(): p.create() p.load() p.activity.data = run.runTyp.id() p.partID.data = run.partID.id() p.Tell1.data = run.tell1Boards.id() p.save()
def load_det_sub_item(itm, match): actor = PVSS.DpVectorActor(mgr) lookup = partition + itm + match + itm + match + itm[:-1] + '.fsm.currentState' #print 'Lookup:',lookup actor.lookup(DP.original(lookup), typ) rd.add(actor.container) rd.execute() state = {} c = actor.container for i in xrange(c.size()): print i, c.at(i).name(), c.at(i).data for i in actor.container: n = i.name() n = n[n.find('|') + 1:n.find('.fsm.currentState:')] n = n[:n.find(itm)] #print n, i.name(),i.data if not state.has_key(n): state[n] = [] state[n].append(i.data) return state
def _commitFSM(self, dpMap, action): dpv = PVSS.DataPointVector() length = 0 for i in dpMap.keys(): vals = '' for j in dpMap[i]: vals = vals + j.upper() + '|' if len(vals) > 0: dpv.push_back(DataPoint(self.manager, DataPoint.original(i))) dpv.back().data = action + '/DEVICES(S)=' + vals[:-1] if debug: log('%-12s %s' % (action, dpv.back().data)) #log('%s>>>>>>> %-12s %s'%(i,action,dpv.back().data)) self.writer.add(dpv) length = self.writer.length() if not self.writer.execute(): log(self.name + '> PVSS commit failed! [%d datapoints]' % (length, ), timestamp=1) return None return self
def runHLTopts(name='HLT', sim=None): """ Execute job options writer for the HLT farm. Arguments: @param name System name @param sim (Dummy) @author M.Frank """ import Online.PVSS as PVSS import Online.AllocatorControl as Control import Online.RunInfoClasses.HLTFarm as RI import Online.JobOptions.OptionsWriter as JobOptions info = RI.HLTFarmInfoCreator() mgr = _mgr(PVSS.defaultSystemName()) writer = JobOptions.HLTOptionsWriter(mgr, name, info) ctrl = Control.Control(mgr, name + 'JobOptions', 'Writer', [writer]).run() return (ctrl, run(name, mgr, sim))
def runCheckpointManager(name='CHKPT'): """ Execute job options writer for the HLT farm. Arguments: @param name System name @param sim (Dummy) @author M.Frank """ mgr = _mgr(Params.storage_system_name) chkpt = CheckpointManager(mgr, 'CheckpointSvc') ctrl = Control.Control(mgr, 'Storage', 'Alloc', [chkpt]) ctrl.run() wait = 1 if wait != 0: if not PVSS.batchMode(): print 'Hit CTRL-C to quit!' ctrl.sleep() print '\n\nDone...\n\n' return 1 return None
def runFarm(project,name): """ Arguments: @param name System name @author M.Frank """ import Online.PVSS as PVSS; import Online.AllocatorControl as Control import Online.ProcessorFarm.FarmDescriptor as Farm mgr = _mgr(project) if name=='Unknown': rdr = mgr.devReader() actor = PVSS.DpVectorActor(mgr) typ = mgr.typeMgr().type('FarmInfo') actor.lookupOriginal('*.Name',typ) if len(actor.container)>0: rdr.add(actor.container) if rdr.execute(): name = actor.container[0].data print '---> Starting controller for farm:',name farm = Farm.FarmConfigurator(mgr,name) ctrl = Control.Control(mgr,name,'Alloc',[farm]).run() return (ctrl,mgr)
def targetHLT(self): return PVSS.defaultSystemName()+'_FSMDisplay.Tasks'
def allocate(self, rundp_name, partition, recv_slots_per_node=None, strm_slots_per_node=None): """ Allocate slots in the receiving and streaming layer of the storage system. Note: All input data must already be read. @param partition Partition name @param recv_slots_per_node Number of receiving layer slots to be allocated at once per node (if possible) @param strm_slots_per_node Number of streaming layer slots to be allocated at once per node (if possible) @return None on failure, on Succes tuple (all_recv_slots,all_strm_slots) with allocated slots """ if self.recv_slots_per_node is not None: recv_slots_per_node = self.recv_slots_per_node if self.strm_slots_per_node is not None: strm_slots_per_node = self.strm_slots_per_node start = time.time() if debug: print 'Starting action:', start, ' recv_slots_per_node:', recv_slots_per_node, ' strm_slots_per_node:', strm_slots_per_node print 'Starting action:', start, existing = self.getPartition(partition) if existing is not None: error( '[Partition already allocated] Cannot allocate partition for:' + partition) msg = '[WAS_ALRDY_ALLOCATED] Ignore Error on allocate on request from Clara' PVSS.error(msg, timestamp=1, type=PVSS.UNEXPECTEDSTATE) return 'WAS_ALRDY_ALLOCATED' # None # Do not return error. Allocator must stay READY info_obj = self.infoInterface.create(rundp_name, partition).load() if not info_obj.doStreaming(): warning( 'Use flag is not set. No need to allocate resources for partition:' + partition) msg = '[NO_STREAMFLG_SET] Ignore Error on allocate on request from Clara' PVSS.error(msg, timestamp=1, type=PVSS.UNEXPECTEDSTATE) return 'NO_STREAMFLG_SET' # None # Do not return error. Allocator must stay READY self.load() nLayer1Slots = info_obj.numLayer1Slots() nLayer2Slots = info_obj.numLayer2Slots() print 'allocate: nLayer1Slots:', nLayer1Slots, ' nLayer2Slots:', nLayer2Slots part_info = self.allocateSlots(rundp_name, partition, nLayer1Slots, recv_slots_per_node, nLayer2Slots, strm_slots_per_node) if part_info is not None: # Allocation was successful: Now update Info table+tasks pinfo = PartitionInfo.PartitionInfo(self.manager, part_info.name).load() fsm_manip = self.fsmManip(pinfo, '_FwFsmDevice', match='*') slots = fsm_manip.collectTaskSlots() if slots: log('Cleaning task slots for ' + partition, timestamp=1) fsm_manip.reset(slots) return part_info.name self.free(rundp_name, partition) error('Failed to allocate slots of type:' + self.name + ' for partition:' + partition, timestamp=1) msg = '[FAIL_ALLOC_SLOTS] Ignore Error on allocate on request from Clara' PVSS.error(msg, timestamp=1, type=PVSS.UNEXPECTEDSTATE) return 'FAIL_ALLOC_SLOTS' # None # Do not return error. Allocator must stay READY
def execute(args): "Argument dispatcher to start various streaming/option writers from the command line." sim = [] wait = 0 typ = 'Storage' nam = None res = None function = None for i in xrange(len(args)): if args[i] == "-sim": sim.append(args[i + 1]) i = i + 1 elif args[i] == "-type": typ = args[i + 1] i = i + 1 elif args[i] == "-debug": _dbg(int(args[i + 1])) i = i + 1 elif args[i] == "-name": nam = args[i + 1] i = i + 1 elif args[i] == "-function": function = args[i + 1] i = i + 1 elif args[i] == "-wait": wait = 1 else: print 'Ignored option ', args[i + 1] if len(sim) == 0: sim = None if typ == 'RecStorage': function = runRecStorage elif typ == 'Storage': function = runStorage #res = runStorage(sim=sim) elif typ == 'Monitoring': function = runMonitoring #res = runMonitoring(sim=sim) elif typ == 'Reconstruction': function = runReconstruction elif typ == 'HLTOptions': function = runHLTopts elif typ == 'InjectorOptions': function = runInjectoropts elif function is None: print 'Unknown action requested.' return None else: print 'Wrong action option set:', typ return None if function and nam: res = function(name=nam, sim=sim) elif function: res = function(sim=sim) if res and wait != 0: import Online.PVSS as PVSS if not PVSS.batchMode(): print 'Hit CTRL-C to quit!' res[0].sleep() print '\n\nDone...\n\n' if res[1] and len(res[1]) > 0: for i in res[1][1]: del (i) return 1
def _tskLookup(self, dp, type): obj = PVSS.DpVectorActor(self.manager) obj.lookupOriginal(self.name + '_' + self.match + '.' + dp, type) return obj
import os, sys, time, socket import Online.PVSS as PVSS import Online.Utils import UPI as upi log = Online.Utils.log timeStamp = Online.Utils.timeStamp menus = {} CMD_UPDATE = 2 CMD_INCREMENTAL = 3 CMD_QUIT = 4 _timeSensor = upi.gbl.TimeSensor.instance() _iocSensor = upi.gbl.IocSensor.instance() mk_timestamps = not PVSS.batchMode() def targetName(self): return self.name.replace('_Slice', 'Display_Slice') + '.Tasks' def targetHLT(self): return PVSS.defaultSystemName() + '_FSMDisplay.Tasks' class ReallyClose(upi.Interactor): # =========================================================================== def __init__(self, parent): upi.Interactor.__init__(self, self) self.id = 12345 + upi.sensor().newID() self.parent = parent
def run(self): "Start the controls task by activating the listening devices." self.sensor.addListener(self) self.sensor.run(1) PVSS.info(self.name+': Sensor started...',timestamp=1,type=PVSS.CONNECTED) return self
def handleDevice(self): "Callback once per item in the device sensor list on datapoint change." import traceback cmd = '' try: print "Callback once per item in the device sensor list on datapoint change." nam = self.dp().name() cmd = self.dp().value().data() itms = cmd.split('/') print nam,cmd if len(itms) >= 5: command = itms[0] storage = itms[1][:-len(self.postfix)] partition = itms[2] partID = itms[3] runDpName = itms[4] Online.Utils.setPartition(partition) answer = '/'+itms[1]+'/'+partition+"/"+str(partID) result = None if storage == self.name: if command == "CONFIGURE": dp = itms[5] ok = itms[6] err = itms[7] data = PVSS.DataPoint(self.manager,PVSS.DataPoint.original(dp)) data.data = ok try: result = self.doExecute('configure',runDpName,partition) if result is None: data.data = err except Exception, X: PVSS.error('The command:"'+cmd+'" failed:'+str(X),timestamp=1,type=PVSS.ILLEGAL_ARG) traceback.print_exc() data.data = err self.writer.add(data) Online.Utils.log('The command:"'+cmd+'" finished.',timestamp=1) self.writer.execute() Online.Utils.log('---> Wrote answer '+data.data+' to datapoint:'+dp,timestamp=1) return self elif command == "RECOVER_SLICE": dp = itms[5] ok = itms[6] err = itms[7] data = PVSS.DataPoint(self.manager,PVSS.DataPoint.original(dp)) data.data = ok try: result = self.doExecute('recover_slice',runDpName,partition) if result is None: data.data = err except Exception, X: PVSS.error('The command:"'+cmd+'" failed:'+str(X),timestamp=1,type=PVSS.ILLEGAL_ARG) traceback.print_exc() data.data = err self.writer.add(data) self.writer.execute() return self try: if command == "ALLOCATE": result = self.doExecute('allocate',runDpName,partition) if result is not None: return self.makeAnswer(command,answer+'/'+result) elif command == "REALLOCATE": result = self.doExecute('free',runDpName,partition) if result is None: PVSS.error('The command:"free" failed.',timestamp=1,type=PVSS.ILLEGAL_ARG) result = self.doExecute('allocate',runDpName,partition) if result is not None: return self.makeAnswer(command,answer+'/'+result) elif command == "DEALLOCATE": result = self.doExecute('free',runDpName,partition) PVSS.info('Result ('+command+')',timestamp=1,type=PVSS.UNEXPECTEDSTATE) if result == "WAS_NOT_ALLOCATED": msg = '[WAS_NOT_ALLOCATED] Ignore Error on deallocate on request from Clara' PVSS.error(msg,timestamp=1,type=PVSS.UNEXPECTEDSTATE) #result=None if result is not None: return self.makeAnswer(command,answer+'/'+result) elif command == "RECOVER": result = self.doExecute('recover',runDpName,partition) if result is not None: #print answer+'/'+result return self.makeAnswer('READY',answer+'/'+result) msg = 'The command:"'+cmd+'" failed. [Internal Error] ' PVSS.error(msg,timestamp=1,type=PVSS.UNEXPECTEDSTATE) return self.makeAnswer('ERROR',answer) except Exception,X: PVSS.error('The command:"'+cmd+'" failed:'+str(X),timestamp=1,type=PVSS.ILLEGAL_ARG) traceback.print_exc() return self.makeAnswer('ERROR',answer) except: PVSS.error('The command:"'+cmd+'" failed (Unknown exception)',timestamp=1,type=PVSS.ILLEGAL_ARG) traceback.print_exc() return self.makeAnswer('ERROR',answer)
def _dbg(val): "Adjust PVSS debug level" import Online.PVSS as PVSS; PVSS.setDebug(val);
def createModifyDelete(self): nam = 'test_device_01' actor = PVSS.DpVectorActor(self.mgr) typ = self.typeMgr.type('ExampleDP_BarTrend') device = self.devMgr.createDevice(nam, typ, 1) if device.get() is None: print '--> Failed to create device "' + nam + '" of type ', typ.name( ) return None print '--> Successfully created datapoint:', nam, ' of type ', typ.name( ) actor.lookupOriginal(nam + '.*', typ) print 'Found %d datapoints of type %s' % (actor.container.size(), nam) self.reader.add(actor.container) if not self.reader.execute(): print '--> Could not access datapoint:', DataPoint.original(nam) return None actor.container[0].data = std.vector('float')() actor.container[1].data = std.vector('float')() actor.container[2].data = std.vector('float')() actor.container[3].data = std.vector('float')() actor.container[4].data = std.vector('float')() actor.container[5].data = std.vector('float')() actor.container[6].data = std.vector('float')() actor.container[7].data = std.vector('float')() actor.container[8].data = std.vector('int')() actor.container[9].data = std.vector('int')() for i in xrange(10): f = float(i) actor.container[0].data.push_back(f) actor.container[1].data.push_back(2 * f) actor.container[2].data.push_back(3 * f) actor.container[3].data.push_back(4 * f) actor.container[4].data.push_back(5 * f) actor.container[5].data.push_back(6 * f) actor.container[6].data.push_back(7 * f) actor.container[7].data.push_back(8 * f) actor.container[8].data.push_back(9 * i) actor.container[9].data.push_back(10 * i) wr = self.mgr.devWriter() wr.add(actor.container) if not wr.execute(): print '--> Could not access datapoint:', DataPoint.original(nam) return None self.reader.add(actor.container) if self.reader.execute(): f = 0 for i in actor.container: f = f + 1 bad = False for j in xrange(i.data.size()): if i.data[j] != f * j: print '--> Bad data: found', i.data[j], 'in', i.name( ), '[', j, '] Expected:', f * j bad = True if bad: print '--> %-12s Datapoint:%-48s %d ##Elem:%d Data BAD' % ( nam, i.name(), i.value().type(), i.data.size()) else: print '--> %-12s Datapoint:%-48s %d ##Elem:%d Data OK' % ( nam, i.name(), i.value().type(), i.data.size()) else: print '--> Could not access datapoint:', DataPoint.original(nam) return None if not self.devMgr.deleteDevice(nam, 1): print '--> Failed to delete datapoint:', nam, ' of type ', typ.name( ) return None print '--> Successfully deleted datapoint:', nam, ' of type ', typ.name( ) return self
self.servers[nam] = srv log('Started display server:'+self.servers[nam].name,timestamp=1) elif i.data<=0 and self.servers[nam] is not None: log('Stop display server:'+self.servers[nam].name,timestamp=1) self.servers[nam].stop() self.servers[nam] = None return 1 # =========================================================================== def sleep(): "Serve controls requests in daemon mode" if PVSS.batchMode(): Online.Utils.log('Sleeping ....',timestamp=1) else: print 'Sleeping ....' sys.stdout.flush() try: while(1): time.sleep(1) except Exception,X: print 'Exception:',str(X) except: print 'Unknown Exception' if __name__=="__main__": mgr = PVSS.controlsMgr() if PVSS.defaultSystemName()[:3]=='HLT': mgr = BaseDisplayServerManager(mgr) else: mgr = DisplayServerManager(mgr) mgr.run() sleep()
def show(mgr=None, name='TestStorage_Slice00', devices=0): m = mgr if m is None: m = PVSS.controlsMgr() dmp = Dump(mgr, name) dmp.show(devices)
def handleInvalidDevice(self): "Callback once per item in the device sensor list on datapoint change." import traceback cmd = '' try: nam = self.dp().name() PVSS.error('The device '+nam+' is dead.....\n'+\ 'This should never occur and is a serious error condition\n'+\ 'We will exit the system.',timestamp=1,type=PVSS.DPNOTEXISTENT) self.do_sleep = 0 except Exception,X: PVSS.error(str(X),timestamp=1,type=PVSS.DPNOTEXISTENT) traceback.print_exc() return 0 except: PVSS.error('(Unknown exception)',timestamp=1,type=PVSS.DPNOTEXISTENT) traceback.print_exc() return 0 # =========================================================================== def handleDevice(self): "Callback once per item in the device sensor list on datapoint change." import traceback cmd = '' try: print "Callback once per item in the device sensor list on datapoint change." nam = self.dp().name() cmd = self.dp().value().data() itms = cmd.split('/') print nam,cmd if len(itms) >= 5:
import socket import Online.PVSS as PVSS import Online.PVSSSystems as Systems mgr = Systems.controlsMgr('ECS') dp = PVSS.DataPoint( mgr, PVSS.DataPoint.original('ECS:LHCb_RunInfo.SubDetectors.tell1List')) rd = mgr.devReader() rd.add(dp) rd.execute() tell1_ips = {} for i in dp.data: ip = socket.gethostbyname(i) print '%-13s %-13s' % (i, ip) if tell1_ips.has_key(ip): print 'Tell1 board ', i, ip, ' is already known:', tell1_ips[ip] tell1_ips[ip] = i print 'Checked IP addresses of ', dp.data.size(), ' tell 1 boards'
def runDataflow(): import Online.PVSSSystems as Systems mgr = DisplayManager(Systems.controlsMgr(PVSS.defaultSystemName())).run() managers = [mgr] sleep(managers)
def defineTasks(self, partition): """ Define all tasks in the storage layer for a given partition. The result is storen in runInfo datapoints for further processing. """ recv_slots = partition.recvSlices() streams = [] # # Need to change order in this loop to better distribute the tasks # in the storage layers: # num_streams = 0 for j in xrange(len(self.streams.data)): for i in xrange(self.strMult.data[j]): streams.append([self.streams.data[j], i]) num_streams = num_streams + 1 recvNodes = partition.recvNodesFromSlots() dimDns = self.manager.hostName() streamers = [] dataSources = [] recvWriters = [] recvReceivers = [] recvInfrastructure = [] opt = '/' + dimDns + '/' + partition.manager.name( ) + '/' + partition.name + '/' cl0 = '/Class0' + opt cl1 = '/Class1' + opt cl2 = '/Class2' + opt for i in xrange(len(recv_slots)): slot = recv_slots[i] node = slot[:slot.find(':')] sub_farm = 'SF%02d' % (i, ) short_name = sub_farm + '_HLT' task = self.name + '_' + node + '_' + short_name recvReceivers.append(node + '/' + task + '/' + short_name + '/HLTRec' + cl1 + '("' + sub_farm + '",)') dataSources.append(sub_farm + '/' + self.name + '_' + sub_farm + '_Sender/' + sub_farm + '_Sender/HLTSend' + cl2 + '("' + node + ',' + task + '",)') for j in recvNodes: for i in streams: type = i[0] ident = str(i[1]) short_name = type + '_' + ident sender = self.name + '_' + j + '_WRT' + short_name recvWriters.append(j + '/' + sender + '/WRT' + short_name + '/WRT' + type + cl1 + '[("' + j + '-d1")]') for i in self.rcvInfra.data: recvInfrastructure.append(j + '/' + self.name + '_' + j + '_' + i + '/' + i + '/' + i + cl0 + '("' + i + '",)') if self.storeFlag.data == 0: # If we do not store the data, the streaming layer does not have to be instrumented. recvWriters = PVSS.StringVector() empty = PVSS.StringVector() partition.setDataSources(dataSources) partition.setRecvInfrastructure(recvInfrastructure) partition.setRecvReceivers(recvReceivers) partition.setRecvSenders(recvWriters) partition.setStreamInfrastructure(empty) partition.setStreamReceivers(empty) partition.setStreamSenders(empty) if partition.saveTasks(): tasks = partition.collectTasks(tasks={}, with_data_sources=0) return tasks return None
def handleInvalidDevice(self): "Callback once per item in the device sensor list on datapoint change." import traceback try: nam = self.dp().name() PVSS.error('The device '+nam+' is dead.....\n'+\ 'This should never occur and is a serious error condition\n'+\ 'We will exit the system.',timestamp=1,type=PVSS.DPNOTEXISTENT) self.do_sleep = 0 except Exception, X: PVSS.error(str(X), timestamp=1, type=PVSS.DPNOTEXISTENT) traceback.print_exc() return 0 except: PVSS.error('(Unknown exception)', timestamp=1, type=PVSS.DPNOTEXISTENT) traceback.print_exc() return 0 # =========================================================================== def handleDevices(self): "Callback once per device sensor list on datapoint change." return 1 # =========================================================================== def handleDevice(self): "Callback once per item in the device sensor list on datapoint change." import traceback try: print '--> CHANGE: ', self.control.name(