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