source: fact/tools/PyDimCtrl/factdimserver.py@ 14229

Last change on this file since 14229 was 13814, checked in by neise, 12 years ago
evolving
  • Property svn:executable set to *
File size: 13.4 KB
Line 
1#!/usr/bin/python -tti
2
3# for tab completion
4import rlcompleter
5import readline
6readline.parse_and_bind('tab: complete')
7
8import sys # for sys.exit() during bebugging mostly :-)
9import types # for dynamic class construction
10import time # for time.sleep
11from pprint import pprint # for nice printing
12
13from keyword import iskeyword # in case dynamic methods are equal to keywords
14
15import pydim # for the C-API Dim Call Wrappers
16
17# using this line makes 'export DIM_DNS_NODE=daq' obsolete
18pydim.dic_set_dns_node('daq')
19
20class FactDimServer( object ):
21 def __init__(self, name):
22 """ sets name of instance to name of server, all uppercase
23 """
24 self.list_of_states = []
25 self.name = name.upper()
26 self.print_state = False
27 self.print_msg = False
28 self.reg_state_cb()
29 self.reg_msg_cb()
30 self.user_func = None
31 self.__delay_between_cmds = 1.0
32 self.__delay_between_services = 1.0
33 self.__last_cmd_send = -1*float('inf')
34 self.__last_service_got = -1*float('inf')
35
36 def _cmd(self, cmdstr, *args):
37 """ used by all dynamicly created methods, which call a Dim CMD
38 """
39 cmdstr=self.name+'/'+cmdstr.upper()
40 desc = services[self.name][cmdstr.upper()][0]
41
42 # there is a work around for a bug in pydim
43 # even if a command needs no argument, and desc is also empty string
44 # one has to give one ... need to tell Niko about it.
45 if not desc:
46 desc = 'I'
47 args=(1,)
48 elif desc == 'O':
49 args = (0,)
50 while not time.time() - self.__last_cmd_send > self.__delay_between_cmds:
51 time.sleep(0.5)
52 self.__last_cmd_send = time.time()
53 pydim.dic_sync_cmnd_service(cmdstr, args, desc, timeout=None)
54
55
56
57 def _get(self, service):
58 """ used by all dynamicly created methods, which get a service
59 """
60 full_srv_name = self.name+'/'+service.upper()
61 desc = services[self.name][full_srv_name][0]
62
63 while not time.time() - self.__last_service_got > self.__delay_between_services:
64 time.sleep(0.5)
65 self.__last_service_got = time.time()
66
67 return pydim.dic_sync_info_service(full_srv_name, desc)
68
69
70 def __call__(self):
71 """ Wrapper / For Convenience
72 self.state() returns a string (if it exists)
73 *returns* numeric state code, parsed from return of self.state()
74 """
75 if hasattr(self, 'stn'):
76 return self.stn
77 else:
78 raise TypeError(self.name+' has no CMD called STATE')
79
80 def wait(self, state_num, timeout=None):
81 """ waits for a certain state
82 BLOCKING
83 returns True if state was reached
84 returns False if timeout occured
85 raises TypeError if Server has no method state
86 """
87
88 if not hasattr(self, 'stn'):
89 raise TypeError(self.name+' has no CMD called STATE')
90 if timeout == None:
91 timeout = float('inf')
92 else:
93 timeout = float(timeout)
94 start = time.time()
95 while not self.stn == state_num:
96 time.sleep(0.1)
97 if time.time() >= start+timeout:
98 return False
99 return True
100
101 def state_callback(self, state):
102 self.sts = state
103 try:
104 self.stn = int(state[state.find('[')+1 : state.find(']')])
105 except ValueError:
106 self.stn = None
107
108 self.last_st_change = time.time()
109 self.list_of_states.append( (self.last_st_change, self.stn) )
110 if len(self.list_of_states) > 10000:
111 print "list_of_states too long, truncating..."
112 self.list_of_states = self.list_of_states[1000:]
113
114 if self.user_func:
115 self.user_func( self.stn )
116
117 if self.print_state:
118 print state
119
120 def msg_callback(self, msg):
121 if self.print_msg:
122 print msg
123
124 def reg_state_cb(self):
125 if not hasattr(self, 'state'):
126 raise TypeError(self.name+' has no CMD called STATE')
127 service_name = self.name.upper()+'/STATE'
128 self.state_sid = pydim.dic_info_service(service_name, "C", self.state_callback)
129 if not self.state_sid:
130 del self.state_sid
131 raise IOError('could not register STATE client')
132
133 def reg_msg_cb(self):
134 if not hasattr(self, 'state'):
135 raise TypeError(self.name+' has no CMD called STATE')
136 service_name = self.name.upper()+'/MESSAGE'
137 self.msg_sid = pydim.dic_info_service(service_name, "C", self.msg_callback)
138 if not self.msg_sid:
139 del self.msg_sid
140 raise IOError('could not register MESSAGE client')
141
142 def unreg_state_cb(self):
143 if hasattr(self, 'state_sid'):
144 pydim.dic_release_service(self.state_sid)
145 del self.state_sid
146
147 def unreg_msg_cb(self):
148 if hasattr(self, 'msg_sid'):
149 pydim.dic_release_service(self.msg_sid)
150 del self.msg_sid
151
152 def __del__(self):
153 self.unreg_state_cb()
154 self.unreg_msg_cb()
155
156# utility functions for dynamic addid of methods to classes
157def add_command(cls, name):
158 meth_name = name.split('/')[1].lower()
159 if iskeyword(meth_name):
160 meth_name += '_cmd'
161
162 # this is the new command, it simple calls the _cmd() method
163 def new_command(self, *args):
164 self._cmd(meth_name, *args)
165
166 new_command.__name__ = meth_name
167
168 # from this line on, the docstring of the method is created
169 if name in dd:
170 if not dd[name]:
171 new_command.__doc__ = "DESC in SERVICE_DESC is empty ?!"
172 else:
173 new_command.__doc__ = dd[name]
174 else:
175 new_command.__doc__ = "-- no DESC found in SERVICE_DESC --"
176 new_command.__doc__ += '\n'
177 new_command.__doc__ += services[name.split('/')[0]][name][0]
178
179 # this line make the new_command() method, a method of the class cls
180 # giving it the name new_command.__name__
181 setattr( cls, new_command.__name__, new_command)
182
183# add_getter is very similar to add_command,
184# the only difference is, that it calls _get() instead of _cmd()
185# and since _get() has a return value, this return value is vorwarded to the user
186def add_getter(cls, name):
187 meth_name = name.split('/')[1].lower()
188 if iskeyword(meth_name):
189 meth_name += '_cmd'
190 def new_command(self):
191 return self._get(meth_name)
192 new_command.__name__ = meth_name
193 if name in dd:
194 if not dd[name]:
195 new_command.__doc__ = "DESC in SERVICE_DESC is empty ?!"
196 else:
197 new_command.__doc__ = dd[name]
198 else:
199 new_command.__doc__ = "-- no DESC found in SERVICE_DESC --"
200 new_command.__doc__ += '\n'
201 new_command.__doc__ += services[name.split('/')[0]][name][0]
202 setattr( cls, new_command.__name__, new_command)
203
204
205
206
207
208# In order to create classes according to the Dim-Servers, currently connected
209# to the DIS_DNS I have to parse DIS_DNS/SERVER_LIST
210# This is done in two steps, first I get the list of Server Names from DIS_DNS
211# and the I get the list of each servers services and cmds,
212# from each servers SERVICE_LIST and the service/command description
213# from each servers SERVICE_DESC
214# I get quite a lot of information, which I store in python dicts, or
215# even nested dicts, if necessary.
216
217def ParseDnsServerList():
218 # making server list
219 rawlist = pydim.dic_sync_info_service('DIS_DNS/SERVER_LIST','C')
220 # the output needs to be treated a bit .. it is a tuple with only one long string in it
221 # the string contains | and the strange character \x00
222 # I use both to cut the list apart
223 rawlist = rawlist[0].split('\x00')
224 servers_n_hosts = rawlist[0].split('|')
225 server_ids = rawlist[1].split('|')
226
227 servers = {}
228 for i,snh in enumerate(servers_n_hosts):
229 snh = snh.split('@')
230 s = snh[0]
231 h = snh[1]
232 sid = server_ids[i]
233 servers[s] = (sid, h)
234
235 return servers
236
237
238
239# servers should be a dict containing uppercase server names as keys,
240# the values are not needed, so it might be any iteratable python listlike type
241# to be precise
242def ParseServersServiceList( servers ):
243
244 services = {}
245 dd = {}
246 for server in servers:
247 # sl_raw is a tuple, with an really long string, which needs to be parsed
248 sl_raw = pydim.dic_sync_info_service(server+'/SERVICE_LIST','C')[0]
249
250 # even without parsing, I can find out, if this server also gives me a
251 # service description list. In case it does not, this is fine as well
252 # the doc string of the dynamicly created methods, will then contain
253 # a note, that therer was no SERVICE_DESC ...
254 if server+'/SERVICE_DESC' in sl_raw:
255 sd_raw = pydim.dic_sync_info_service(server+'/SERVICE_DESC','C')[0]
256 else:
257 sd_raw = ''
258
259 # now before parsing, I strip off all ASCII zeroes '\x00' and all
260 # line breaks off the *end* of the long string of both
261 # the service list sl
262 # and service description sd
263 #
264 # I think in sl_raw were alse some '\x00' in the middle .. these
265 # are replaced by nothing, in case they are there.
266 sl_raw = sl_raw.rstrip('\x00\n')
267 sl_raw = sl_raw.replace('\x00','')
268 sd_raw = sd_raw.rstrip('\x00\n')
269
270 # The lists are seperated by line breaks, so I split them using this
271 sl = sl_raw.split('\n')
272 sd = sd_raw.split('\n')
273
274 # First I parse the service descriptons, so I have them available,
275 # when I create the dict full of services.
276 # All desciptions look like this
277 # 'SERVER/SERVICE=some descriptive text' or
278 # 'SERVER/SERVICE='
279 # this I use to create the dictionary.
280 # create descripton dict dd from service_desc list sd
281 for d_str in sd:
282 service,equalsign,desc = d_str.partition('=')
283 #if '=' != equalsign:
284 # print "Error: server:", server, "desc:", d_str
285 dd[service] = desc
286
287 # Now I fill ther services dict. Each server gets a nested dict
288 # inside services.
289 # Each service is explained in a string with a '|' in between.
290 # I use this for spliting.
291 # The string look like this
292 # SERVER/SERVICE|format-desc-str(e.g. I:2;C)|-empty- or CMD or RPC|
293 services[server] = {}
294 for service in sl:
295 service = service.split('|')
296 if service[0] in dd:
297 services[server][service[0]] = (
298 service[1], service[2], dd[service[0]])
299 return services, dd
300
301
302servers = ParseDnsServerList()
303services, dd = ParseServersServiceList( servers )
304
305# create one class for each Fact Dim Server
306FactDimServerClasses = []
307for server_name in servers:
308 FactDimServerClasses.append(
309 types.ClassType( server_name, (FactDimServer,), {}) )
310 for cmd in services[server_name]:
311 if 'CMD' in services[server_name][cmd][1]:
312 cmdname = cmd.split('/')[1]
313 add_command(FactDimServerClasses[-1], cmd)
314 elif not services[server_name][cmd][1]:
315 cmdname = cmd.split('/')[1]
316 add_getter(FactDimServerClasses[-1], cmd)
317
318
319
320# create an instace of each of the classes
321# and make it globally known, i.e. known to the Python interpreter
322# all the ServerClass instances are collected in a list
323# so one can get a quick overview --> print dims
324dims = []
325for i,server_name in enumerate(servers):
326 if server_name == 'DIS_DNS':
327 continue
328 new_instance = FactDimServerClasses[i](server_name)
329 dims.append( new_instance )
330 globals()[server_name.lower()] = new_instance
331del new_instance
332del i
333##############################################################################
334# class for colored printing
335
336class bcolors:
337 HEADER = '\033[95m'
338 OKBLUE = '\033[94m'
339 OKGREEN = '\033[92m'
340 WARNING = '\033[93m'
341 FAIL = '\033[91m'
342 ENDC = '\033[0m'
343
344 def disable(self):
345 self.HEADER = ''
346 self.OKBLUE = ''
347 self.OKGREEN = ''
348 self.WARNING = ''
349 self.FAIL = ''
350 self.ENDC = ''
351
352##############################################################################
353# class which implements colored printing
354# method calls can be used instead of Python print calls
355# for conveniently printing colored output.
356
357
358class MSG( bcolors):
359 def __init__(self, verbose = True):
360 """ create MSG instance,
361 default is verbose,
362 sets self.output
363
364 if:
365 self.*output* = True, object behaves as expeted
366 if False, no call return anything
367 """
368 self.output = verbose
369
370 def fail(self, text ):
371 """ print in RED
372 """
373 text = str(text)
374 if self.output:
375 print bcolors.FAIL + "ERROR:" + bcolors.ENDC,
376 print bcolors.FAIL + text + bcolors.ENDC
377
378 def warn(self, text ):
379 """ print in YELLOW
380 """
381 text = str(text)
382 if self.output:
383 print bcolors.WARNING + text + bcolors.ENDC
384
385 def ok(self, text ):
386 """ print in GREEN
387 """
388 text = str(text)
389 if self.output:
390 print bcolors.OKGREEN + text + bcolors.ENDC
391
392 def __call__(self, *args):
393 """ print as Python print would do
394 """
395 if self.output:
396 for arg in args:
397 print arg,
398 print
399
Note: See TracBrowser for help on using the repository browser.