1
2
3
4
5
6
7
8
9
10
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
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
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()
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
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
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
148 """Wait for connection and spawn instance of SocketListenController."""
149 if eval_frequency is None:
150
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
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
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
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
305 _re_const = re.compile(r'^const\(\s?(.*)\s?\)$',re.DOTALL)
306 _re_eval_str = re.compile(r'^eval_str\(\s?(.*)\s?\)$',re.DOTALL)