Package VisionEgg :: Module TCPController
[frames] | no frames]

Source Code for Module VisionEgg.TCPController

  1  # The Vision Egg: TCPController 
  2  # 
  3  # Copyright (C) 2001-2003 Andrew Straw. 
  4  # Author: Andrew Straw <astraw@users.sourceforge.net> 
  5  # URL: <http://www.visionegg.org/> 
  6  # 
  7  # Distributed under the terms of the GNU Lesser General Public License 
  8  # (LGPL). See LICENSE.TXT that came with this file. 
  9  # 
 10  # $Id$ 
 11   
 12  """ 
 13  Allows control of parameter values over the network. 
 14   
 15  Don't use for realtime control unless you think your network is that 
 16  fast and reliable. Also, this code has not been optimized for speed, 
 17  and I think it is unwise to attempt to change the value of controllers 
 18  in realtime.  In other words, do not design an experiment where, on a 
 19  remote computer, you have determined that a certain amount of time has 
 20  passed, and you require a certain new controller value NOW.  In this 
 21  case, it would be better to use parameter=eval_str() with an if 
 22  statement involving time. 
 23   
 24  To control parameters over a network, start a server with an instance 
 25  of TCPServer.  The server spawns an instance of SocketListenController 
 26  for each connected socket.  (Most commonly you will only want 
 27  connection over a single socket.)  The instance of 
 28  SocketListenController handles all communication for that connection 
 29  and serves as a container and (meta) controller for instances of 
 30  TCPController. 
 31   
 32  This module contains ABSOLUTELY NO SECURITY FEATURES, and could easily 
 33  allow arbitrary execution of code on your computer. For this reason, 
 34  if you use this module, I recommend operating behind a firewall. This 
 35  could be an inexpensive "routing switch" used for cable modems, which 
 36  would provide the added benefit that your local network would be 
 37  isolated.  This would elimate all traffic not to or from computers on 
 38  the switch and therefore reduce/eliminate packet collisions, decrease 
 39  latency, and providing a network performance and reliability. To 
 40  address security concerns, you could also write code that implements 
 41  IP address checking or other security features. 
 42   
 43  """ 
 44   
 45  import VisionEgg 
 46  import VisionEgg.Core 
 47  import VisionEgg.FlowControl 
 48  import VisionEgg.ParameterTypes as ve_types 
 49  import socket, select, re, string, types 
 50  import numpy.oldnumeric as Numeric, math # for eval 
 51   
 52  try: 
 53      import Tkinter 
 54  except: 
 55      pass 
 56   
 57  import logging 
 58   
 59  __version__ = VisionEgg.release_name 
 60  __cvs__ = '$Revision$'.split()[1] 
 61  __date__ = ' '.join('$Date$'.split()[1:3]) 
 62  __author__ = 'Andrew Straw <astraw@users.sourceforge.net>' 
 63   
64 -class TCPServer:
65 """TCP server creates SocketListenController upon connection. 66 67 This class is analagous to VisionEgg.PyroHelpers.PyroServer. 68 69 """
70 - def __init__(self, 71 hostname="", 72 port=7834, 73 single_socket_but_reconnect_ok=0, 74 dialog_ok=1, 75 confirm_address_with_gui=1):
76 """Bind to hostname and port, but don't listen yet. 77 78 """ 79 server_address = (socket.getfqdn(hostname),port) 80 self.dialog_ok = dialog_ok 81 self.single_socket_but_reconnect_ok = single_socket_but_reconnect_ok 82 self.buffer = "" 83 self.server_socket=None 84 if not globals().has_key("Tkinter") or (VisionEgg.config.VISIONEGG_TKINTER_OK==0): 85 self.dialog_ok = 0 86 87 class GetServerAddressWindow(Tkinter.Frame): 88 def __init__(self,server_address,**kw): 89 try: 90 Tkinter.Frame.__init__(self,**kw) 91 except AttributeError,x: 92 tk=Tkinter.Tk() # restart Tk and see if that helps 93 Tkinter.Frame.__init__(self,tk,**kw) 94 self.winfo_toplevel().title("Vision Egg: TCP Server get address") 95 self.server_address = server_address 96 hostname,port = self.server_address 97 self.clicked_ok = 0 98 self.hostname = Tkinter.StringVar() 99 self.hostname.set(hostname) 100 self.port = Tkinter.StringVar() 101 self.port.set(port) 102 row = 0 103 Tkinter.Label(self, 104 text="Please enter the hostname and port you would like to listen for connections on.", 105 ).grid(row=row,columnspan=2) 106 row += 1 107 Tkinter.Label(self, 108 text="Hostname (blank means localhost):", 109 ).grid(row=row,column=0,sticky=Tkinter.E) 110 Tkinter.Entry(self,textvariable=self.hostname).grid(row=row,column=1,sticky=Tkinter.W+Tkinter.E,padx=10) 111 row += 1 112 Tkinter.Label(self, 113 text="Port:", 114 ).grid(row=row,column=0,sticky=Tkinter.E) 115 Tkinter.Entry(self,textvariable=self.port).grid(row=row,column=1,sticky=Tkinter.W+Tkinter.E,padx=10) 116 row += 1 117 b = Tkinter.Button(self, 118 text="Bind port and listen for connections", 119 command=self.click_ok) 120 b.grid(row=row,columnspan=2) 121 b.focus_force() 122 b.bind('<Return>',self.click_ok)
123 def click_ok(self,dummy_arg=None): 124 hostname = self.hostname.get() 125 try: 126 port = int(self.port.get()) 127 except: 128 port = self.port.get() 129 self.server_address = (hostname,port) 130 self.clicked_ok = 1 131 self.winfo_toplevel().destroy()
132 133 bound = 0 134 if not bound: 135 # while not bound: # don't loop until the code is cleaner 136 if confirm_address_with_gui and self.dialog_ok: 137 window = GetServerAddressWindow(server_address) 138 window.pack() 139 window.mainloop() 140 if not window.clicked_ok: 141 return # User wants to quit 142 server_address = window.server_address 143 self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 144 self.server_socket.bind(server_address) 145 bound = 1 146
147 - def create_listener_once_connected(self,eval_frequency=None):
148 """Wait for connection and spawn instance of SocketListenController.""" 149 if eval_frequency is None: 150 # Don't listen to socket during go loop -- especially don't want to skip frames then 151 eval_frequency = VisionEgg.FlowControl.Controller.EVERY_FRAME | VisionEgg.FlowControl.Controller.NOT_DURING_GO 152 host,port = self.server_socket.getsockname() 153 fqdn = socket.getfqdn(host) 154 logger = logging.getLogger('VisionEgg.TCPController') 155 logger.info("Awaiting connection to TCP Server at '%s', port %d"%(fqdn,port)) 156 self.server_socket.listen(1) 157 if self.dialog_ok: 158 # Make a Tkinter dialog box 159 class WaitingDialog(Tkinter.Frame): 160 def __init__(self,server_socket=None,**kw): 161 Tkinter.Frame.__init__(self,**kw) 162 self.winfo_toplevel().title('Vision Egg TCP Server') 163 self.server_socket = server_socket 164 host,port = self.server_socket.getsockname() 165 fqdn = socket.getfqdn(host) 166 spacer = Tkinter.Frame(self,borderwidth=30) 167 spacer.pack() 168 Tkinter.Label(spacer,text= 169 """Awaiting connection to TCP Server at "%s", port %d"""%(fqdn,port) 170 ).pack() 171 b = Tkinter.Button(self,text="Cancel",command=self.stop_listening) 172 b.pack(side=Tkinter.BOTTOM) 173 b.focus_force() 174 b.bind('<Return>',self.stop_listening) 175 self.winfo_toplevel().protocol("WM_DELETE_WINDOW", self.stop_listening) 176 self.server_socket.setblocking(0) 177 self.after(1,self.idle_func)
178 def stop_listening(self,dummy=None): 179 raise SystemExit 180 def idle_func(self): 181 try: 182 # This line raises an exception unless there's an incoming connection 183 self.accepted = self.server_socket.accept() 184 self.quit() 185 except socket.error, x: 186 self.after(1,self.idle_func) 187 dialog = WaitingDialog(server_socket = self.server_socket) 188 dialog.pack() 189 dialog.mainloop() 190 client, client_address = dialog.accepted 191 dialog.winfo_toplevel().destroy() 192 else: 193 client, client_address = self.server_socket.accept() 194 if self.single_socket_but_reconnect_ok: 195 return SocketListenController(client, 196 disconnect_ok = 1, 197 server_socket = self.server_socket) 198 else: 199 return SocketListenController(client) 200
201 -class SocketListenController(VisionEgg.FlowControl.Controller):
202 r"""Handle connection from remote machine, control TCPControllers. 203 204 This meta controller handles a TCP socket to control zero to many 205 instances of TCPController. As a subclass of Controller, it gets 206 called at specified moments in time via the Presentation 207 class. When called in this way, it checks for any strings from the 208 TCP socket. It parses this information into a command or fails 209 and sends an error. 210 211 This class is analagous to VisionEgg.PyroHelpers.PyroListenController. 212 213 TCP commands (sent over network socket) 214 ======================================= 215 216 close -- close the connection 217 exit -- close the connection 218 quit -- quit the server program 219 help -- print help message 220 <name> -- show the value of the controller of <name> 221 <name>=const(<args>) -- assign a new ConstantController to <name> 222 <name>=eval_str(<args>) -- assign a new EvalStringController to <name> 223 <name>=exec_str(<args>) -- assign a new ExecStringController to <name> 224 <name>=exec_str(*<args>) -- assign a new unrestricted namespace ExecStringController to <name> 225 226 TCP commands are always on a single line. (Newlines in string 227 literals can be specified by using "\n" without the quotes.) 228 229 The assignment commands share common behavior: 230 231 <name> -- value passed as argument "tcp_name" to method create_tcp_controller 232 <args> -- during_go [, between_go [, eval_frequency [, temporal_variables [, return_type ]]]] 233 234 The <args> string is parsed by the Python's eval() function. If 235 you don't want to explicitly set an argument early in the argument 236 list, but you need to set one late in the list, use "None". If 237 not set, the optional arguments default to: 238 239 eval_frequency = EVERY_FRAME 240 temporal_variables = TIME_SEC_SINCE_GO 241 return_type = (evaluates during_go function to find) 242 between_go = (see below, depends on assignment type) 243 244 The only difference between the assignment commands are in the 245 first two arguments. For "const(...)", the first two arguments 246 are constant values, for "eval_str(...)" they are strings that 247 evaluate to a single variable, and for "exec_str(...)", they are 248 strings that set the variable "x" in their local namespace, which 249 is then returned. (An unrestricted namespace is available with 250 "exec_str(*...)".) If the argument between_go is set to None or 251 is not defined, the behavior depends on the assignment command. 252 If this is a <name>=const(...) assignment, between_go_value is set 253 to during_go_value. If this is a <name>=eval_str(...) or 254 <name>=exec_str(...) assignment, the correct value cannot be 255 guessed, and therefore the between_go_eval function will never be 256 called (the eval_frequency flag NOT_BETWEEN_GO is set). 257 258 Because the default value for temporal_variables is 259 TIME_SEC_SINCE_GO, the variable "t" may be safely used in the 260 during_go string for the eval_str or exec_str assignment commands. 261 See the documentation for VisionEgg.FlowControl.EvalStringController for 262 more information. 263 264 Example commands from TCP port (try with telnet): 265 266 <name>=const(1.0) 267 <name>=eval_str("t*360.0") 268 <name>=exec_str("x=t*360.0") 269 270 <name>=const(0.,1.,EVERY_FRAME) 271 <name>=const(1,None,ONCE) 272 273 <name>=const(1.0,0.0,EVERY_FRAME,TIME_INDEPENDENT,types.FloatType) 274 <name>=eval_str("t*360.0","t_abs*360.0",None,TIME_SEC_ABSOLUTE|TIME_SEC_SINCE_GO) 275 <name>=eval_str("t_abs*360.0","t_abs*360.0",EVERY_FRAME,TIME_SEC_ABSOLUTE,types.FloatType) 276 <name>=exec_str("x=t*360.0","x=0.0",EVERY_FRAME,TIME_SEC_SINCE_GO) 277 <name>=exec_str("print 'Time since go=%f'%(t,)\nx=t*360.0","x=0.0",EVERY_FRAME,TIME_SEC_SINCE_GO) 278 279 """ 280 281 help_string = r""" TCP commands (sent over network socket): 282 283 close -- close the connection 284 exit -- close the connection 285 quit -- quit the server program 286 help -- print this message 287 <name> -- show the value of the controller of <name> 288 <name>=const(<args>) -- assign a new ConstantController to <name> 289 <name>=eval_str(<args>) -- assign a new EvalStringController to <name> 290 <name>=exec_str(<args>) -- assign a new ExecStringController to <name> 291 <name>=exec_str(*<args>) -- assign a new unrestricted namespace ExecStringController to <name> 292 293 TCP commands are always on a single line. (Newlines in string 294 literals can be specified by using "\n" without the quotes.) 295 296 The assignment commands share common behavior: 297 298 <name> -- value passed as argument "tcp_name" to method create_tcp_controller 299 <args> -- during_go [, between_go [, eval_frequency [, temporal_variables [, return_type ]]]] 300 """ 301 302 _re_line = re.compile(r"^(.*)\n",re.MULTILINE) 303 _re_x_finder = re.compile(r'\A|\Wx\s?=[^=]') 304 # The real newlines have already been parsed by the time the buffer gets to these statements: 305 _re_const = re.compile(r'^const\(\s?(.*)\s?\)$',re.DOTALL) 306 _re_eval_str = re.compile(r'^eval_str\(\s?(.*)\s?\)$',re.DOTALL)