Package vmd :: Module control
[hide private]
[frames] | no frames]

Source Code for Module vmd.control

  1  # $Id$ 
  2  # vmd remote control --- client/server scripts to run VMD from python 
  3  # Copyright (c) 2007-2009 Oliver Beckstein <orbeckst@gmail.com> 
  4  # Released under the GNU Lesser Public License, version 3 or later. 
  5  # 
  6  # using asynchat/asyncore to talk to a VMD server process 
  7  # (see http://www.python.org/doc/current/lib/module-asyncore.html &  
  8  # http://squirl.nightmare.com/medusa/async_sockets.html) 
  9  __docformat__ = "restructuredtext en" 
 10   
 11  import asynchat, asyncore, socket 
 12  import os, sys, readline, string, re 
 13   
 14  try: 
 15      # part of a python egg 
 16      # see http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources 
 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'  # see remote_ctl.tcl 
 24   
 25   
26 -class server:
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 # tcl file is stored in the same directory as this module file 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 # now wait until the server is up (check every 2 seconds) 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
95 - def stop(self):
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
111 - def command(self,*args):
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 """
149 - def __init__(self, host,port=services['vmd']):
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
157 - def handle_connect(self):
158 pass
159
160 - def collect_incoming_data(self,data):
161 """buffer incoming data""" 162 self.ibuffer += data
163
164 - def found_terminator(self):
165 self.push('close\n') # tell the other side we are done
166 # and have them shut down this socket 167 # -- THIS IS PART OF THE PROTOCOL 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
182 - def response(self):
183 return self.ibuffer
184 185
186 -class command(client):
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
206 - def __init__(self,*commands):
207 self._resp = [] 208 commands = [c.strip()+'\n' for c in commands] 209 s = " ".join(commands) # one string to join them all... 210 s = re.sub('\n$','',s) # get rid of last newline 211 self._cmds = string.split(s,'\n') # ...so that we get only nice command bits 212 for c in self._cmds: 213 # print "DEBUG: sending command [%s]" % c 214 cl = client('localhost') # what a waste--single command exchange 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 # print "DEBUG: response [%s]" % r 221 self._resp.append(r[:])
222
223 - def results(self):
224 """Return results from vmd.""" 225 return tuple(self._resp)
226
227 - def commands(self):
228 """Return submitted tcl commands.""" 229 return tuple(self._cmds)
230
231 -class interactive(client):
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
259 - def __init__(self, host,port=services['vmd']):
260 client.__init__(self,host) 261 self.stack_count = 0 # number of command responses to expect
262
263 - def handle_connect(self):
264 # we need one server response 265 self.cmd('puts "Interactive connection from client established."')
266
267 - def found_terminator(self):
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 # enter command processing if no more output 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 # self.push('puts "Disconnection by client request"\n') 285 self.push('close\n') 286 self.close() 287 return # necessary ? 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
296 - def getinput(self):
297 print 'python->vmd> ', 298 return sys.stdin.readline()
299 300 301 # 302 # Test code 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