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

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