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 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 # tcl file is stored in the same directory as this module file 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 # now wait until the server is up (check every 2 seconds) 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
101 - def stop(self):
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
117 - def command(self,*args):
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 """
159 - def __init__(self, host,port=services['vmd']):
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
167 - def handle_connect(self):
168 pass
169
170 - def collect_incoming_data(self,data):
171 """buffer incoming data""" 172 self.ibuffer += data
173
174 - def found_terminator(self):
175 self.push('close\n') # tell the other side we are done
176 # and have them shut down this socket 177 # -- THIS IS PART OF THE PROTOCOL 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
192 - def response(self):
193 return self.ibuffer
194 195
196 -class command(client):
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
218 - def __init__(self,*commands):
219 self._resp = [] 220 commands = [c.strip()+'\n' for c in commands] 221 s = " ".join(commands) # one string to join them all... 222 s = re.sub('\n$','',s) # get rid of last newline 223 self._cmds = string.split(s,'\n') # ...so that we get only nice command bits 224 for c in self._cmds: 225 # print "DEBUG: sending command [%s]" % c 226 cl = client('localhost') # what a waste--single command exchange 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 # print "DEBUG: response [%s]" % r 233 self._resp.append(r[:])
234
235 - def results(self):
236 """Return results from vmd.""" 237 return tuple(self._resp)
238
239 - def commands(self):
240 """Return submitted tcl commands.""" 241 return tuple(self._cmds)
242
243 -class interactive(client):
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
271 - def __init__(self, host,port=services['vmd']):
272 client.__init__(self,host) 273 self.stack_count = 0 # number of command responses to expect
274
275 - def handle_connect(self):
276 # we need one server response 277 self.cmd('puts "Interactive connection from client established."')
278
279 - def found_terminator(self):
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 # enter command processing if no more output 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 # self.push('puts "Disconnection by client request"\n') 297 self.push('close\n') 298 self.close() 299 return # necessary ? 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
308 - def getinput(self):
309 print 'python->vmd> ', 310 return sys.stdin.readline()
311 312 313 # 314 # Test code 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