25 import os, sys, getopt |
25 import os, sys, getopt |
26 |
26 |
27 def usage(): |
27 def usage(): |
28 print """ |
28 print """ |
29 Usage of Beremiz PLC execution service :\n |
29 Usage of Beremiz PLC execution service :\n |
30 %s {[-n name] [-i ip] [-p port] [-x enabletaskbar]|-h|--help} working_dir |
30 %s {[-n name] [-i ip] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir |
31 -n - zeroconf service name |
31 -n - zeroconf service name |
32 -i - ip of interface to bind to (x.x.x.x) |
32 -i - ip of interface to bind to (x.x.x.x) |
33 -p - port number |
33 -p - port number |
34 -h - print this help text and quit |
34 -h - print this help text and quit |
|
35 -a - autostart PLC (0:disable 1:enable) |
35 -x - enable/disable wxTaskbarIcon (0:disable 1:enable) |
36 -x - enable/disable wxTaskbarIcon (0:disable 1:enable) |
36 |
37 |
37 working_dir - directory where are stored PLC files |
38 working_dir - directory where are stored PLC files |
38 """%sys.argv[0] |
39 """%sys.argv[0] |
39 |
40 |
40 try: |
41 try: |
41 opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:h") |
42 opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:a:h") |
42 except getopt.GetoptError, err: |
43 except getopt.GetoptError, err: |
43 # print help information and exit: |
44 # print help information and exit: |
44 print str(err) # will print something like "option -a not recognized" |
45 print str(err) # will print something like "option -a not recognized" |
45 usage() |
46 usage() |
46 sys.exit(2) |
47 sys.exit(2) |
249 self.pyroserver = pyroserver |
255 self.pyroserver = pyroserver |
250 # Set the image |
256 # Set the image |
251 self.UpdateIcon(None) |
257 self.UpdateIcon(None) |
252 |
258 |
253 # bind some events |
259 # bind some events |
|
260 self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START) |
|
261 self.Bind(wx.EVT_MENU, self.OnTaskBarStopPLC, id=self.TBMENU_STOP) |
254 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME) |
262 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME) |
255 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE) |
263 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE) |
256 self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL) |
264 self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL) |
257 self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR) |
265 self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR) |
258 self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT) |
266 self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT) |
265 the menu for the default EVT_RIGHT_DOWN event. Just create |
273 the menu for the default EVT_RIGHT_DOWN event. Just create |
266 the menu how you want it and return it from this function, |
274 the menu how you want it and return it from this function, |
267 the base class takes care of the rest. |
275 the base class takes care of the rest. |
268 """ |
276 """ |
269 menu = wx.Menu() |
277 menu = wx.Menu() |
|
278 menu.Append(self.TBMENU_START, "Start PLC") |
|
279 menu.Append(self.TBMENU_STOP, "Stop PLC") |
270 menu.Append(self.TBMENU_CHANGE_NAME, "Change Name") |
280 menu.Append(self.TBMENU_CHANGE_NAME, "Change Name") |
271 menu.Append(self.TBMENU_CHANGE_INTERFACE, "Change IP of interface to bind") |
281 menu.Append(self.TBMENU_CHANGE_INTERFACE, "Change IP of interface to bind") |
272 menu.Append(self.TBMENU_LIVE_SHELL, "Launch a live Python shell") |
282 menu.Append(self.TBMENU_LIVE_SHELL, "Launch a live Python shell") |
273 menu.Append(self.TBMENU_WXINSPECTOR, "Launch WX GUI inspector") |
283 menu.Append(self.TBMENU_WXINSPECTOR, "Launch WX GUI inspector") |
274 menu.Append(self.TBMENU_CHANGE_PORT, "Change Port Number") |
284 menu.Append(self.TBMENU_CHANGE_PORT, "Change Port Number") |
288 img = img.Scale(22, 22) |
298 img = img.Scale(22, 22) |
289 # wxMac can be any size upto 128x128, so leave the source img alone.... |
299 # wxMac can be any size upto 128x128, so leave the source img alone.... |
290 icon = wx.IconFromBitmap(img.ConvertToBitmap() ) |
300 icon = wx.IconFromBitmap(img.ConvertToBitmap() ) |
291 return icon |
301 return icon |
292 |
302 |
293 def OnTaskBarChangeInterface(self,evt): |
303 def OnTaskBarStartPLC(self, evt): |
294 dlg = ParamsEntryDialog(None, "Enter the ip of the interface to bind", defaultValue=pyroserver.ip) |
304 if self.pyroserver.plcobj is not None: |
|
305 self.pyroserver.plcobj.StartPLC() |
|
306 evt.Skip() |
|
307 |
|
308 def OnTaskBarStopPLC(self, evt): |
|
309 if self.pyroserver.plcobj is not None: |
|
310 self.pyroserver.plcobj.StopPLC() |
|
311 evt.Skip() |
|
312 |
|
313 def OnTaskBarChangeInterface(self, evt): |
|
314 dlg = ParamsEntryDialog(None, "Enter the ip of the interface to bind", defaultValue=self.pyroserver.ip) |
295 dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, "Ip is not valid!"), |
315 dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, "Ip is not valid!"), |
296 ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, "Ip is not valid!") |
316 ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, "Ip is not valid!") |
297 ]) |
317 ]) |
298 if dlg.ShowModal() == wx.ID_OK: |
318 if dlg.ShowModal() == wx.ID_OK: |
299 self.pyroserver.ip = dlg.GetValue() |
319 self.pyroserver.ip = dlg.GetValue() |
300 self.pyroserver.Stop() |
320 self.pyroserver.Stop() |
301 evt.Skip() |
321 evt.Skip() |
302 |
322 |
303 def OnTaskBarChangePort(self,evt): |
323 def OnTaskBarChangePort(self, evt): |
304 dlg = ParamsEntryDialog(None, "Enter a port number ", defaultValue=str(pyroserver.port)) |
324 dlg = ParamsEntryDialog(None, "Enter a port number ", defaultValue=str(self.pyroserver.port)) |
305 dlg.SetTests([(UnicodeType.isdigit, "Port number must be an integer!"), (lambda port : 0 <= int(port) <= 65535 , "Port number must be 0 <= port <= 65535!")]) |
325 dlg.SetTests([(UnicodeType.isdigit, "Port number must be an integer!"), (lambda port : 0 <= int(port) <= 65535 , "Port number must be 0 <= port <= 65535!")]) |
306 if dlg.ShowModal() == wx.ID_OK: |
326 if dlg.ShowModal() == wx.ID_OK: |
307 self.pyroserver.port = int(dlg.GetValue()) |
327 self.pyroserver.port = int(dlg.GetValue()) |
308 self.pyroserver.Stop() |
328 self.pyroserver.Stop() |
309 evt.Skip() |
329 evt.Skip() |
310 |
330 |
311 def OnTaskBarChangeWorkingDir(self,evt): |
331 def OnTaskBarChangeWorkingDir(self, evt): |
312 dlg = wx.DirDialog(None, "Choose a working directory ", pyroserver.workdir, wx.DD_NEW_DIR_BUTTON) |
332 dlg = wx.DirDialog(None, "Choose a working directory ", self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON) |
313 if dlg.ShowModal() == wx.ID_OK: |
333 if dlg.ShowModal() == wx.ID_OK: |
314 self.pyroserver.workdir = dlg.GetPath() |
334 self.pyroserver.workdir = dlg.GetPath() |
315 self.pyroserver.Stop() |
335 self.pyroserver.Stop() |
316 evt.Skip() |
336 evt.Skip() |
317 |
337 |
318 def OnTaskBarChangeName(self,evt): |
338 def OnTaskBarChangeName(self, evt): |
319 dlg = ParamsEntryDialog(None, "Enter a name ", defaultValue=pyroserver.name) |
339 dlg = ParamsEntryDialog(None, "Enter a name ", defaultValue=self.pyroserver.name) |
320 dlg.SetTests([(lambda name : len(name) is not 0 , "Name must not be null!")]) |
340 dlg.SetTests([(lambda name : len(name) is not 0 , "Name must not be null!")]) |
321 if dlg.ShowModal() == wx.ID_OK: |
341 if dlg.ShowModal() == wx.ID_OK: |
322 self.pyroserver.name = dlg.GetValue() |
342 self.pyroserver.name = dlg.GetValue() |
323 self.pyroserver.Restart() |
343 self.pyroserver.Restart() |
324 evt.Skip() |
344 evt.Skip() |
325 |
345 |
326 def OnTaskBarLiveShell(self,evt): |
346 def OnTaskBarLiveShell(self, evt): |
327 if self.pyroserver.plcobj is not None and self.pyroserver.plcobj.python_threads_vars is not None: |
347 if self.pyroserver.plcobj is not None and self.pyroserver.plcobj.python_threads_vars is not None: |
328 from wx import py |
348 from wx import py |
329 #frame = py.shell.ShellFrame(locals=self.pyroserver.plcobj.python_threads_vars) |
349 #frame = py.shell.ShellFrame(locals=self.pyroserver.plcobj.python_threads_vars) |
330 frame = py.crust.CrustFrame(locals=self.pyroserver.plcobj.python_threads_vars) |
350 frame = py.crust.CrustFrame(locals=self.pyroserver.plcobj.python_threads_vars) |
331 frame.Show() |
351 frame.Show() |
370 |
390 |
371 def default_evaluator(callable, *args, **kwargs): |
391 def default_evaluator(callable, *args, **kwargs): |
372 return callable(*args,**kwargs) |
392 return callable(*args,**kwargs) |
373 |
393 |
374 class Server(): |
394 class Server(): |
375 def __init__(self, name, ip, port, workdir, argv, statuschange=None, evaluator=default_evaluator): |
395 def __init__(self, name, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator): |
376 self.continueloop = True |
396 self.continueloop = True |
377 self.daemon = None |
397 self.daemon = None |
378 self.name = name |
398 self.name = name |
379 self.ip = ip |
399 self.ip = ip |
380 self.port = port |
400 self.port = port |
381 self.workdir = workdir |
401 self.workdir = workdir |
382 self.argv = argv |
402 self.argv = argv |
383 self.plcobj = None |
403 self.plcobj = None |
384 self.servicepublisher = None |
404 self.servicepublisher = None |
|
405 self.autostart = autostart |
385 self.statuschange = statuschange |
406 self.statuschange = statuschange |
386 self.evaluator = evaluator |
407 self.evaluator = evaluator |
387 |
408 |
388 def Loop(self): |
409 def Loop(self): |
389 while self.continueloop: |
410 while self.continueloop: |
408 |
429 |
409 # Configure and publish service |
430 # Configure and publish service |
410 # Not publish service if localhost in address params |
431 # Not publish service if localhost in address params |
411 if self.ip != "localhost" and self.ip != "127.0.0.1": |
432 if self.ip != "localhost" and self.ip != "127.0.0.1": |
412 print "Publish service on local network" |
433 print "Publish service on local network" |
413 self.servicepublisher = ServicePublisher.ServicePublisher() |
434 self.servicepublisher = ServicePublisher.ServicePublisher() |
414 self.servicepublisher.RegisterService(self.name, self.ip, self.port) |
435 self.servicepublisher.RegisterService(self.name, self.ip, self.port) |
415 |
436 |
|
437 if self.autostart: |
|
438 self.plcobj.StartPLC() |
|
439 |
416 sys.stdout.flush() |
440 sys.stdout.flush() |
417 |
441 |
418 self.daemon.requestLoop() |
442 self.daemon.requestLoop() |
419 |
443 |
420 def Stop(self): |
444 def Stop(self): |
|
445 self.plcobj.StopPLC() |
421 if self.servicepublisher is not None: |
446 if self.servicepublisher is not None: |
422 self.servicepublisher.UnRegisterService() |
447 self.servicepublisher.UnRegisterService() |
423 del self.servicepublisher |
448 del self.servicepublisher |
424 self.daemon.shutdown(True) |
449 self.daemon.shutdown(True) |
425 |
450 |
426 |
451 |
427 if havewx: |
452 if havewx: |
428 from threading import Semaphore |
453 from threading import Semaphore |
429 wx_eval_lock = Semaphore(0) |
454 wx_eval_lock = Semaphore(0) |
430 app=wx.App(redirect=False) |
455 app=wx.App(redirect=False) |
|
456 mythread = currentThread() |
|
457 |
431 def statuschange(status): |
458 def statuschange(status): |
432 wx.CallAfter(taskbar_instance.UpdateIcon,status) |
459 wx.CallAfter(taskbar_instance.UpdateIcon,status) |
433 |
460 |
434 eval_res = None |
461 eval_res = None |
435 def wx_evaluator(callable, *args, **kwargs): |
462 def wx_evaluator(callable, *args, **kwargs): |
437 eval_res=callable(*args,**kwargs) |
464 eval_res=callable(*args,**kwargs) |
438 #print eval_res |
465 #print eval_res |
439 wx_eval_lock.release() |
466 wx_eval_lock.release() |
440 |
467 |
441 def evaluator(callable, *args, **kwargs): |
468 def evaluator(callable, *args, **kwargs): |
442 wx.CallAfter(wx_evaluator,callable,*args,**kwargs) |
469 # call directly the callable function if call from the wx mainloop (avoid dead lock) |
443 wx_eval_lock.acquire() |
470 if(mythread == currentThread()): |
|
471 callable(*args,**kwargs) |
|
472 else: |
|
473 wx.CallAfter(wx_evaluator,callable,*args,**kwargs) |
|
474 wx_eval_lock.acquire() |
444 return eval_res |
475 return eval_res |
445 |
476 |
446 pyroserver = Server(name, ip, port, WorkingDir, argv, statuschange, evaluator) |
477 pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, statuschange, evaluator) |
447 taskbar_instance = BeremizTaskBarIcon(pyroserver) |
478 taskbar_instance = BeremizTaskBarIcon(pyroserver) |
448 |
479 |
449 pyro_thread=Thread(target=pyroserver.Loop) |
480 pyro_thread=Thread(target=pyroserver.Loop) |
450 pyro_thread.start() |
481 pyro_thread.start() |
451 app.MainLoop() |
482 app.MainLoop() |
452 else: |
483 else: |
453 pyroserver = Server(name, ip, port, WorkingDir, argv) |
484 pyroserver = Server(name, ip, port, WorkingDir, argv, autostart) |
454 pyroserver.Loop() |
485 pyroserver.Loop() |