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

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