1
2
3
4
5
6
7
8
9 __docformat__ = "restructuredtext en"
10
11 import asynchat, asyncore, socket
12 import os, sys, readline, string, re
13
14 try:
15
16
17 from pkg_resources import resource_filename
18 REMOTE_TCL = resource_filename(__name__,'remote_ctl.tcl')
19 except ImportError:
20 REMOTE_TCL = os.path.join(os.path.split(__file__)[0],'remote_ctl.tcl')
21
22 services = { 'vmd' : 5555 }
23 RESPONSE_TERMINATOR = '__END_OF_VMD_RESPONSE__\r\n'
24
25
27 """The VMD server process."""
28 - def __init__(self,vmdbinary='vmd',
29 server_tcl=REMOTE_TCL,
30 force=False,maxdelay=10,dispdev='text'):
31
32 """Start VMD in text mode and launch the remote server::
33
34 server(**kwargs)
35
36 :Arguments:
37 vmdbinary
38 ``vmd`` (either in PATH or absolute path)
39 server_tcl
40 path to the tcl script that starts the listening server in VMD
41 (the default is correct in 99.9% of cases)
42 force
43 ``False``
44 don't launch new server if one is already active
45 ``True``
46 always start new vmd process
47 maxdelay
48 maximum time to wait for the server to come up in seconds
49 dispdev
50 VMD display device; default is 'text' which runs VMD without
51 graphics. 'win' is the graphical window device
52
53 Bugs
54 ----
55
56 * starting multiple VMD processes does not work as we always use the same port
57
58 """
59 devices = {'graphics':'win','win':'win', 'text':'text','batch':'text'}
60 self.vmdbinary = vmdbinary
61 try:
62 self.dispdev = devices[dispdev]
63 except KeyError:
64 raise ValueError("VMD display device '"+str(dispdev)+"' is not recognized. "+
65 "Choose one of "+str(devices.keys())+".")
66
67
68 self.server_tcl = server_tcl
69 self.vmdbinary = vmdbinary
70 self.startcmd = '%s -dispdev %s -e %s' % (self.vmdbinary, self.dispdev, self.server_tcl)
71 self.start(force=force)
72
73 - def start(self,force=False,maxdelay=10):
74 """Start VMD and launch the remote server::
75
76 server.start(**kwargs)
77
78 :Arguments:
79 force
80 ``False``
81 don't launch new server if one is already active
82 ``True``
83 always start new vmd process
84 maxdelay
85 maximum number of seconds to wait for VMD to start
86 """
87 import time
88
89 if force or not self.ping():
90 args = self.startcmd.split(" ")
91 pid = os.spawnvp(os.P_NOWAIT,args[0],args)
92
93 t = interval = 2
94 time.sleep(interval)
95 while not self.ping() and t < maxdelay:
96 time.sleep(interval)
97 t += interval
98 if not self.ping():
99 raise RuntimeError('Failed to bring up the VMD server.')
100
102 """Shutdown VMD."""
103 command('quit')
104
105 - def ping(self,pid=os.getpid()):
106 """Check if a vmd server can be used.
107
108 Returns True or False.
109
110 Ignore the message 'error: uncaptured python exception' if the server is down.
111 """
112 token = 'ALIVE (ping from pid %d)' % pid
113 c = command('set __pingtest__ {%s}' % token)
114 x, = c.results()
115 return x == token
116
118 """Send commands to the VMD server::
119
120 c = command('cd','set w [atomselect top {water}]', '$w writepdb water.pdb')
121 c.results()
122
123 This is only a thin convenience wrapper for
124 ``vmd.control.command()`` and not strongly tied to the server (as
125 anyone can connect).
126 """
127 return command(*args)
128
129 -class client(asynchat.async_chat):
130 """one command -> response exchange between the client and vmd::
131
132 c = client(host,port=5555)
133 c.cmd(tcl, tcl,...)
134 asyncore.loop()
135
136 Starting VMD as ``vmd -e remote_ctl.tcl`` opens port 5555 for connection.
137 The client only becomes active in the ``asyncore.loop()`` and exits after
138 sending the commands and receiving the response. The response is available
139 as ``c.response()``
140
141 :Parameters:
142 host
143 currently remote_ctl.tcl only allows 'localhost'
144 port
145 port to connect to (typically 5555)
146
147 Methods
148 -------
149
150 c.cmd(tcl, tcl, ...)
151 commands (with embedded newlines!) scheduled for sending and execution in VMD
152 c.response()
153 response from VMD
154
155 Bugs
156 ----
157 Somehow it doesnt like many commands...
158 """
160 asynchat.async_chat.__init__(self)
161 self.port = port
162 self.ibuffer = ""
163 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
164 self.connect( (host,port) )
165 self.set_terminator(RESPONSE_TERMINATOR)
166
169
171 """buffer incoming data"""
172 self.ibuffer += data
173
176
177
178
179 - def cmd(self,*tcl):
180 """Submits the commands to be executed in VMD::
181
182 c.cmd(tcl, tcl, ...)
183
184 Commands (*with embedded newlines!*) scheduled for sending and execution
185 in ``VMD``. All strings will be executed sequentially.
186 """
187 if len(tcl) == 0:
188 raise ValueError, 'at least one Tcl command is required'
189 s = " ".join(tcl)
190 self.push(s)
191
194
195
197 """Send one or more tcl commands to VMD and return response::
198
199 c = command(*tcl)
200
201 Appends a newline to each command if necessary and then
202 feeds every single command separately to vmd. Commands that
203 include newlines are split on newlines. The responses are stored
204 in the tuple c._results (and can be retrieved by the c.results()
205 method).
206
207 Technically, this is unelegant cr^&...
208
209 Methods
210 -------
211
212 c.results()
213 response from VMD
214 c.commands()
215 corresponding commands
216 """
217
219 self._resp = []
220 commands = [c.strip()+'\n' for c in commands]
221 s = " ".join(commands)
222 s = re.sub('\n$','',s)
223 self._cmds = string.split(s,'\n')
224 for c in self._cmds:
225
226 cl = client('localhost')
227 cl.cmd(c + "\n")
228 asyncore.loop()
229 r = cl.response()
230 r = re.sub('\r\n','\n',r)
231 r = re.sub('\n$','',r)
232
233 self._resp.append(r[:])
234
236 """Return results from vmd."""
237 return tuple(self._resp)
238
240 """Return submitted tcl commands."""
241 return tuple(self._cmds)
242
244 """Interactive remote session with vmd::
245
246 interactive(host)
247 asyncore.loop()
248
249 When the ``loop()`` is called the interactive session starts and the prompt
250 is displayed as ``python->vmd>``. You are now connected to the tcl
251 interpreter in vmd. End the session by issuing the command ``close`` or
252 ``EXIT``.
253
254 :Parameters:
255 host
256 currently remote_ctl.tcl only allows 'localhost'
257
258 Commands interpreted by the remote server and not by tcl in vmd:
259
260 ================ ====================================================
261 command description
262 ================ ====================================================
263 close close the current connection (socket)
264 exit exit the server (no more connections possible,
265 but current connections are still open)
266 loglevel N set LOGLEVEL to value N (0<=N<=2) [default: 1]
267 EXIT exit the interactive session
268 ================ ====================================================
269 """
270
274
276
277 self.cmd('puts "Interactive connection from client established."')
278
280 data = self.ibuffer
281 self.ibuffer = ""
282 if data.endswith('\r'):
283 data = data[:-1]
284 print data,
285 self.stack_count -= 1
286
287 if self.stack_count == 0:
288
289 self.cmd()
290
291 - def cmd(self,*commands):
292 if len(commands) == 0:
293 c = self.getinput()
294 if c == 'EXIT\n':
295 print "Exit interactive loop"
296
297 self.push('close\n')
298 self.close()
299 return
300 self.stack_count += 1
301 self.push(c)
302 else:
303 for c in commands:
304 self.stack_count += 1
305 self.push(c+"\n")
306
307
311
312
313
314
315
316 if __name__ == '__main__':
317 print """\
318 >>> [c = interactive('localhost')]
319 done; then execute loop() to enter interactive mode:
320 >>> asyncore.loop()"""
321 c = interactive('localhost')
322