Beremiz_service.py
changeset 1475 de4ee16f7c6c
parent 1458 5c87045af670
child 1571 486f94a8032c
equal deleted inserted replaced
1474:28e9d479aa65 1475:de4ee16f7c6c
     1 #!/usr/bin/env python
     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     2 # -*- coding: utf-8 -*-
     3 
     3 
     4 #This file is part of Beremiz, a Integrated Development Environment for
     4 #This file is part of Beremiz, a Integrated Development Environment for
     5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. 
     5 #programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
     6 #
     6 #
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
     8 #
     8 #
     9 #See COPYING file for copyrights details.
     9 #See COPYING file for copyrights details.
    10 #
    10 #
    34            -p        - port number default:3000
    34            -p        - port number default:3000
    35            -h        - print this help text and quit
    35            -h        - print this help text and quit
    36            -a        - autostart PLC (0:disable 1:enable) (default:0)
    36            -a        - autostart PLC (0:disable 1:enable) (default:0)
    37            -x        - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
    37            -x        - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
    38            -t        - enable/disable Twisted web interface (0:disable 1:enable) (default:1)
    38            -t        - enable/disable Twisted web interface (0:disable 1:enable) (default:1)
    39            
    39            -w        - web server port or "off" (default:8009)
       
    40            -c        - WAMP client config file or "off" (default:wampconf.json)
       
    41            -e        - python extension (absolute path .py)
       
    42 
    40            working_dir - directory where are stored PLC files
    43            working_dir - directory where are stored PLC files
    41 """%sys.argv[0]
    44 """%sys.argv[0]
    42 
    45 
    43 try:
    46 try:
    44     opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:h")
    47     opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:h")
    45 except getopt.GetoptError, err:
    48 except getopt.GetoptError, err:
    46     # print help information and exit:
    49     # print help information and exit:
    47     print str(err) # will print something like "option -a not recognized"
    50     print str(err) # will print something like "option -a not recognized"
    48     usage()
    51     usage()
    49     sys.exit(2)
    52     sys.exit(2)
    50 
    53 
    51 # default values
    54 # default values
    52 given_ip = None
    55 given_ip = None
    53 port = 3000
    56 port = 3000
       
    57 webport = 8009
       
    58 wampconf = "wampconf.json"
    54 servicename = None
    59 servicename = None
    55 autostart = False
    60 autostart = False
    56 enablewx = True
    61 enablewx = True
    57 havewx = False
    62 havewx = False
    58 enabletwisted = True
    63 enabletwisted = True
    59 havetwisted = False
    64 havetwisted = False
       
    65 
       
    66 extensions=[]
    60 
    67 
    61 for o, a in opts:
    68 for o, a in opts:
    62     if o == "-h":
    69     if o == "-h":
    63         usage()
    70         usage()
    64         sys.exit()
    71         sys.exit()
    77         enablewx = int(a)
    84         enablewx = int(a)
    78     elif o == "-t":
    85     elif o == "-t":
    79         enabletwisted = int(a)
    86         enabletwisted = int(a)
    80     elif o == "-a":
    87     elif o == "-a":
    81         autostart = int(a)
    88         autostart = int(a)
       
    89     elif o == "-w":
       
    90         webport = None if a == "off" else int(a)
       
    91     elif o == "-c":
       
    92         wampconf = None if a == "off" else a
       
    93     elif o == "-e":
       
    94         extensions.append(a)
    82     else:
    95     else:
    83         usage()
    96         usage()
    84         sys.exit()
    97         sys.exit()
       
    98 
       
    99 beremiz_dir = os.path.dirname(os.path.realpath(__file__))
    85 
   100 
    86 if len(argv) > 1:
   101 if len(argv) > 1:
    87     usage()
   102     usage()
    88     sys.exit()
   103     sys.exit()
    89 elif len(argv) == 1:
   104 elif len(argv) == 1:
    97 if __name__ == '__main__':
   112 if __name__ == '__main__':
    98     __builtin__.__dict__['_'] = lambda x: x
   113     __builtin__.__dict__['_'] = lambda x: x
    99 
   114 
   100 if enablewx:
   115 if enablewx:
   101     try:
   116     try:
   102         import wx, re
   117         import wxversion
   103         from threading import Thread, currentThread
   118         wxversion.select('2.8')
   104         from types import *
   119         import wx
   105         havewx = True
   120         havewx = True
   106     except:
   121     except:
   107         print "Wx unavailable !"
   122         print "Wx unavailable !"
   108         havewx = False
   123         havewx = False
   109 
   124 
   110     if havewx:
   125     if havewx:
       
   126         import re
       
   127         from threading import Thread, currentThread
       
   128         from types import *
   111         app=wx.App(redirect=False)
   129         app=wx.App(redirect=False)
   112         
   130 
   113         # Import module for internationalization
   131         # Import module for internationalization
   114         import gettext
   132         import gettext
   115         
   133 
   116         CWD = os.path.split(os.path.realpath(__file__))[0]
       
   117         def Bpath(*args):
   134         def Bpath(*args):
   118             return os.path.join(CWD,*args)
   135             return os.path.join(beremiz_dir,*args)
   119         
   136 
   120         # Get folder containing translation files
   137         # Get folder containing translation files
   121         localedir = os.path.join(CWD,"locale")
   138         localedir = os.path.join(beremiz_dir,"locale")
   122         # Get the default language
   139         # Get the default language
   123         langid = wx.LANGUAGE_DEFAULT
   140         langid = wx.LANGUAGE_DEFAULT
   124         # Define translation domain (name of translation files)
   141         # Define translation domain (name of translation files)
   125         domain = "Beremiz"
   142         domain = "Beremiz"
   126 
   143 
   137         def unicode_translation(message):
   154         def unicode_translation(message):
   138             return wx.GetTranslation(message).encode("utf-8")
   155             return wx.GetTranslation(message).encode("utf-8")
   139 
   156 
   140         if __name__ == '__main__':
   157         if __name__ == '__main__':
   141             __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
   158             __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation
   142         
   159 
   143         defaulticon = wx.Image(Bpath("images", "brz.png"))
   160         defaulticon = wx.Image(Bpath("images", "brz.png"))
   144         starticon = wx.Image(Bpath("images", "icoplay24.png"))
   161         starticon = wx.Image(Bpath("images", "icoplay24.png"))
   145         stopicon = wx.Image(Bpath("images", "icostop24.png"))
   162         stopicon = wx.Image(Bpath("images", "icostop24.png"))
   146         
   163 
   147         class ParamsEntryDialog(wx.TextEntryDialog):
   164         class ParamsEntryDialog(wx.TextEntryDialog):
   148             if wx.VERSION < (2, 6, 0):
   165             if wx.VERSION < (2, 6, 0):
   149                 def Bind(self, event, function, id = None):
   166                 def Bind(self, event, function, id = None):
   150                     if id is not None:
   167                     if id is not None:
   151                         event(self, id, function)
   168                         event(self, id, function)
   152                     else:
   169                     else:
   153                         event(self, function)
   170                         event(self, function)
   154             
   171 
   155             
   172 
   156             def __init__(self, parent, message, caption = "Please enter text", defaultValue = "", 
   173             def __init__(self, parent, message, caption = "Please enter text", defaultValue = "",
   157                                style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
   174                                style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
   158                 wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
   175                 wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
   159                 
   176 
   160                 self.Tests = []
   177                 self.Tests = []
   161                 if wx.VERSION >= (2, 8, 0):
   178                 if wx.VERSION >= (2, 8, 0):
   162                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
   179                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
   163                 elif wx.VERSION >= (2, 6, 0):
   180                 elif wx.VERSION >= (2, 6, 0):
   164                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetAffirmativeButton().GetId())
   181                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetAffirmativeButton().GetId())
   165                 else:
   182                 else:
   166                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId())
   183                     self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId())
   167             
   184 
   168             def OnOK(self, event):
   185             def OnOK(self, event):
   169                 value = self.GetValue()
   186                 value = self.GetValue()
   170                 texts = {"value" : value}
   187                 texts = {"value" : value}
   171                 for function, message in self.Tests:
   188                 for function, message in self.Tests:
   172                     if not function(value):
   189                     if not function(value):
   174                         message.ShowModal()
   191                         message.ShowModal()
   175                         message.Destroy()
   192                         message.Destroy()
   176                         return
   193                         return
   177                 self.EndModal(wx.ID_OK)
   194                 self.EndModal(wx.ID_OK)
   178                 event.Skip()
   195                 event.Skip()
   179             
   196 
   180             def GetValue(self):
   197             def GetValue(self):
   181                 return self.GetSizer().GetItem(1).GetWindow().GetValue()
   198                 return self.GetSizer().GetItem(1).GetWindow().GetValue()
   182             
   199 
   183             def SetTests(self, tests):
   200             def SetTests(self, tests):
   184                 self.Tests = tests
   201                 self.Tests = tests
   185         
   202 
   186         class BeremizTaskBarIcon(wx.TaskBarIcon):
   203         class BeremizTaskBarIcon(wx.TaskBarIcon):
   187             TBMENU_START = wx.NewId()
   204             TBMENU_START = wx.NewId()
   188             TBMENU_STOP = wx.NewId()
   205             TBMENU_STOP = wx.NewId()
   189             TBMENU_CHANGE_NAME = wx.NewId()
   206             TBMENU_CHANGE_NAME = wx.NewId()
   190             TBMENU_CHANGE_PORT = wx.NewId()
   207             TBMENU_CHANGE_PORT = wx.NewId()
   191             TBMENU_CHANGE_INTERFACE = wx.NewId()
   208             TBMENU_CHANGE_INTERFACE = wx.NewId()
   192             TBMENU_LIVE_SHELL = wx.NewId()
   209             TBMENU_LIVE_SHELL = wx.NewId()
   193             TBMENU_WXINSPECTOR = wx.NewId()
   210             TBMENU_WXINSPECTOR = wx.NewId()
   194             TBMENU_CHANGE_WD = wx.NewId()
   211             TBMENU_CHANGE_WD = wx.NewId()
   195             TBMENU_QUIT = wx.NewId()
   212             TBMENU_QUIT = wx.NewId()
   196             
   213 
   197             def __init__(self, pyroserver, level):
   214             def __init__(self, pyroserver, level):
   198                 wx.TaskBarIcon.__init__(self)
   215                 wx.TaskBarIcon.__init__(self)
   199                 self.pyroserver = pyroserver
   216                 self.pyroserver = pyroserver
   200                 # Set the image
   217                 # Set the image
   201                 self.UpdateIcon(None)
   218                 self.UpdateIcon(None)
   202                 self.level = level
   219                 self.level = level
   203                 
   220 
   204                 # bind some events
   221                 # bind some events
   205                 self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START)
   222                 self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START)
   206                 self.Bind(wx.EVT_MENU, self.OnTaskBarStopPLC, id=self.TBMENU_STOP)
   223                 self.Bind(wx.EVT_MENU, self.OnTaskBarStopPLC, id=self.TBMENU_STOP)
   207                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME)
   224                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME)
   208                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE)
   225                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE)
   209                 self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL)
   226                 self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL)
   210                 self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR)
   227                 self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR)
   211                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT)
   228                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT)
   212                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
   229                 self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
   213                 self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT)
   230                 self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT)
   214             
   231 
   215             def CreatePopupMenu(self):
   232             def CreatePopupMenu(self):
   216                 """
   233                 """
   217                 This method is called by the base class when it needs to popup
   234                 This method is called by the base class when it needs to popup
   218                 the menu for the default EVT_RIGHT_DOWN event.  Just create
   235                 the menu for the default EVT_RIGHT_DOWN event.  Just create
   219                 the menu how you want it and return it from this function,
   236                 the menu how you want it and return it from this function,
   232                     menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
   249                     menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
   233                     menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
   250                     menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
   234                 menu.AppendSeparator()
   251                 menu.AppendSeparator()
   235                 menu.Append(self.TBMENU_QUIT, _("Quit"))
   252                 menu.Append(self.TBMENU_QUIT, _("Quit"))
   236                 return menu
   253                 return menu
   237             
   254 
   238             def MakeIcon(self, img):
   255             def MakeIcon(self, img):
   239                 """
   256                 """
   240                 The various platforms have different requirements for the
   257                 The various platforms have different requirements for the
   241                 icon size...
   258                 icon size...
   242                 """
   259                 """
   245                 elif "wxGTK" in wx.PlatformInfo:
   262                 elif "wxGTK" in wx.PlatformInfo:
   246                     img = img.Scale(22, 22)
   263                     img = img.Scale(22, 22)
   247                 # wxMac can be any size upto 128x128, so leave the source img alone....
   264                 # wxMac can be any size upto 128x128, so leave the source img alone....
   248                 icon = wx.IconFromBitmap(img.ConvertToBitmap() )
   265                 icon = wx.IconFromBitmap(img.ConvertToBitmap() )
   249                 return icon
   266                 return icon
   250             
   267 
   251             def OnTaskBarStartPLC(self, evt):
   268             def OnTaskBarStartPLC(self, evt):
   252                 if self.pyroserver.plcobj is not None: 
   269                 if self.pyroserver.plcobj is not None:
   253                     self.pyroserver.plcobj.StartPLC()
   270                     self.pyroserver.plcobj.StartPLC()
   254             
   271 
   255             def OnTaskBarStopPLC(self, evt):
   272             def OnTaskBarStopPLC(self, evt):
   256                 if self.pyroserver.plcobj is not None:
   273                 if self.pyroserver.plcobj is not None:
   257                     Thread(target=self.pyroserver.plcobj.StopPLC).start()
   274                     Thread(target=self.pyroserver.plcobj.StopPLC).start()
   258             
   275 
   259             def OnTaskBarChangeInterface(self, evt):
   276             def OnTaskBarChangeInterface(self, evt):
   260                 dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip_addr)
   277                 dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=self.pyroserver.ip_addr)
   261                 dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")),
   278                 dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")),
   262                                ( lambda x :len([x for x in x.split(".") if 0 <= int(x) <= 255]) == 4, _("IP is not valid!"))
   279                                ( lambda x :len([x for x in x.split(".") if 0 <= int(x) <= 255]) == 4, _("IP is not valid!"))
   263                                ])
   280                                ])
   264                 if dlg.ShowModal() == wx.ID_OK:
   281                 if dlg.ShowModal() == wx.ID_OK:
   265                     self.pyroserver.ip_addr = dlg.GetValue()
   282                     self.pyroserver.ip_addr = dlg.GetValue()
   266                     self.pyroserver.Stop()
   283                     self.pyroserver.Stop()
   267             
   284 
   268             def OnTaskBarChangePort(self, evt):
   285             def OnTaskBarChangePort(self, evt):
   269                 dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
   286                 dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
   270                 dlg.SetTests([(UnicodeType.isdigit, _("Port number must be an integer!")), (lambda port : 0 <= int(port) <= 65535 , _("Port number must be 0 <= port <= 65535!"))])
   287                 dlg.SetTests([(UnicodeType.isdigit, _("Port number must be an integer!")), (lambda port : 0 <= int(port) <= 65535 , _("Port number must be 0 <= port <= 65535!"))])
   271                 if dlg.ShowModal() == wx.ID_OK:
   288                 if dlg.ShowModal() == wx.ID_OK:
   272                     self.pyroserver.port = int(dlg.GetValue())
   289                     self.pyroserver.port = int(dlg.GetValue())
   273                     self.pyroserver.Stop()
   290                     self.pyroserver.Stop()
   274             
   291 
   275             def OnTaskBarChangeWorkingDir(self, evt):
   292             def OnTaskBarChangeWorkingDir(self, evt):
   276                 dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
   293                 dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
   277                 if dlg.ShowModal() == wx.ID_OK:
   294                 if dlg.ShowModal() == wx.ID_OK:
   278                     self.pyroserver.workdir = dlg.GetPath()
   295                     self.pyroserver.workdir = dlg.GetPath()
   279                     self.pyroserver.Stop()
   296                     self.pyroserver.Stop()
   280             
   297 
   281             def OnTaskBarChangeName(self, evt):
   298             def OnTaskBarChangeName(self, evt):
   282                 dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name)
   299                 dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name)
   283                 dlg.SetTests([(lambda name : len(name) is not 0 , _("Name must not be null!"))])
   300                 dlg.SetTests([(lambda name : len(name) is not 0 , _("Name must not be null!"))])
   284                 if dlg.ShowModal() == wx.ID_OK:
   301                 if dlg.ShowModal() == wx.ID_OK:
   285                     self.pyroserver.name = dlg.GetValue()
   302                     self.pyroserver.name = dlg.GetValue()
   286                     self.pyroserver.Restart()
   303                     self.pyroserver.Restart()
   287             
   304 
   288             def _LiveShellLocals(self):
   305             def _LiveShellLocals(self):
   289                 if self.pyroserver.plcobj is not None:
   306                 if self.pyroserver.plcobj is not None:
   290                     return {"locals":self.pyroserver.plcobj.python_runtime_vars}
   307                     return {"locals":self.pyroserver.plcobj.python_runtime_vars}
   291                 else:
   308                 else:
   292                     return {}
   309                     return {}
   293             
   310 
   294             def OnTaskBarLiveShell(self, evt):
   311             def OnTaskBarLiveShell(self, evt):
   295                 from wx import py
   312                 from wx import py
   296                 frame = py.crust.CrustFrame(**self._LiveShellLocals())
   313                 frame = py.crust.CrustFrame(**self._LiveShellLocals())
   297                 frame.Show()
   314                 frame.Show()
   298             
   315 
   299             def OnTaskBarWXInspector(self, evt):
   316             def OnTaskBarWXInspector(self, evt):
   300                 # Activate the widget inspection tool
   317                 # Activate the widget inspection tool
   301                 from wx.lib.inspection import InspectionTool
   318                 from wx.lib.inspection import InspectionTool
   302                 if not InspectionTool().initialized:
   319                 if not InspectionTool().initialized:
   303                     InspectionTool().Init(**self._LiveShellLocals())
   320                     InspectionTool().Init(**self._LiveShellLocals())
   304 
   321 
   305                 wnd = wx.GetApp()
   322                 wnd = wx.GetApp()
   306                 InspectionTool().Show(wnd, True)
   323                 InspectionTool().Show(wnd, True)
   307             
   324 
   308             def OnTaskBarQuit(self, evt):
   325             def OnTaskBarQuit(self, evt):
   309                 if wx.Platform == '__WXMSW__':
   326                 if wx.Platform == '__WXMSW__':
   310                     Thread(target=self.pyroserver.Quit).start()
   327                     Thread(target=self.pyroserver.Quit).start()
   311                 self.RemoveIcon()
   328                 self.RemoveIcon()
   312                 wx.CallAfter(wx.GetApp().ExitMainLoop)
   329                 wx.CallAfter(wx.GetApp().ExitMainLoop)
   313             
   330 
   314             def UpdateIcon(self, plcstatus):
   331             def UpdateIcon(self, plcstatus):
   315                 if plcstatus is "Started" :
   332                 if plcstatus is "Started" :
   316                     currenticon = self.MakeIcon(starticon)
   333                     currenticon = self.MakeIcon(starticon)
   317                 elif plcstatus is "Stopped":
   334                 elif plcstatus is "Stopped":
   318                     currenticon = self.MakeIcon(stopicon)
   335                     currenticon = self.MakeIcon(stopicon)
   332     except Exception:
   349     except Exception:
   333         res=(None, sys.exc_info())
   350         res=(None, sys.exc_info())
   334     return res
   351     return res
   335 
   352 
   336 class Server():
   353 class Server():
   337     def __init__(self, servicename, ip_addr, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None):
   354     def __init__(self, servicename, ip_addr, port,
       
   355                  workdir, argv, autostart=False,
       
   356                  statuschange=None, evaluator=default_evaluator,
       
   357                  pyruntimevars=None):
   338         self.continueloop = True
   358         self.continueloop = True
   339         self.daemon = None
   359         self.daemon = None
   340         self.servicename = servicename
   360         self.servicename = servicename
   341         self.ip_addr = ip_addr
   361         self.ip_addr = ip_addr
   342         self.port = port
   362         self.port = port
   345         self.plcobj = None
   365         self.plcobj = None
   346         self.servicepublisher = None
   366         self.servicepublisher = None
   347         self.autostart = autostart
   367         self.autostart = autostart
   348         self.statuschange = statuschange
   368         self.statuschange = statuschange
   349         self.evaluator = evaluator
   369         self.evaluator = evaluator
   350         self.website = website
   370         self.pyruntimevars = pyruntimevars
   351     
   371 
   352     def Loop(self):
   372     def Loop(self):
   353         while self.continueloop:
   373         while self.continueloop:
   354             self.Start()
   374             self.Start()
   355         
   375 
   356     def Restart(self):
   376     def Restart(self):
   357         self.Stop()
   377         self.Stop()
   358 
   378 
   359     def Quit(self):
   379     def Quit(self):
   360         self.continueloop = False
   380         self.continueloop = False
   363         self.Stop()
   383         self.Stop()
   364 
   384 
   365     def Start(self):
   385     def Start(self):
   366         pyro.initServer()
   386         pyro.initServer()
   367         self.daemon=pyro.Daemon(host=self.ip_addr, port=self.port)
   387         self.daemon=pyro.Daemon(host=self.ip_addr, port=self.port)
   368         self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website)
   388         self.plcobj = PLCObject(self.workdir, self.daemon, self.argv,
       
   389                                 self.statuschange, self.evaluator,
       
   390                                 self.pyruntimevars)
   369         uri = self.daemon.connect(self.plcobj,"PLCObject")
   391         uri = self.daemon.connect(self.plcobj,"PLCObject")
   370     
   392 
   371         print "Pyro port :",self.port
   393         print "Pyro port :",self.port
   372         print "Pyro object's uri :",uri
   394         print "Pyro object's uri :",uri
   373         print "Current working directory :",self.workdir
   395         print "Current working directory :",self.workdir
   374         
   396 
   375         # Configure and publish service
   397         # Configure and publish service
   376         # Not publish service if localhost in address params
   398         # Not publish service if localhost in address params
   377         if (self.servicename is not None and 
   399         if (self.servicename is not None and
   378             self.ip_addr is not None and 
   400             self.ip_addr is not None and
   379             self.ip_addr != "localhost" and 
   401             self.ip_addr != "localhost" and
   380             self.ip_addr != "127.0.0.1"):
   402             self.ip_addr != "127.0.0.1"):
   381             print "Publishing service on local network"
   403             print "Publishing service on local network"
   382             self.servicepublisher = ServicePublisher.ServicePublisher()
   404             self.servicepublisher = ServicePublisher.ServicePublisher()
   383             self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
   405             self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
   384         
   406 
   385         if self.autostart and self.plcobj.GetPLCstatus()[0] != "Empty":
   407         if self.autostart :
   386             self.plcobj.StartPLC()
   408             self.plcobj.AutoLoad()
   387         
   409             if self.plcobj.GetPLCstatus()[0] != "Empty":
       
   410                 self.plcobj.StartPLC()
       
   411 
   388         sys.stdout.flush()
   412         sys.stdout.flush()
   389         
   413 
   390         self.daemon.requestLoop()
   414         self.daemon.requestLoop()
   391     
   415 
   392     def Stop(self):
   416     def Stop(self):
   393         if self.plcobj is not None:
   417         if self.plcobj is not None:
   394             self.plcobj.StopPLC()
   418             self.plcobj.StopPLC()
   395         if self.servicepublisher is not None:
   419         if self.servicepublisher is not None:
   396             self.servicepublisher.UnRegisterService()
   420             self.servicepublisher.UnRegisterService()
   404         try:
   428         try:
   405             from threading import Thread, currentThread
   429             from threading import Thread, currentThread
   406             if havewx:
   430             if havewx:
   407                 from twisted.internet import wxreactor
   431                 from twisted.internet import wxreactor
   408                 wxreactor.install()
   432                 wxreactor.install()
   409             from twisted.internet import reactor, task
   433             from twisted.internet import reactor
   410             from twisted.python import log, util
   434 
   411             from nevow import rend, appserver, inevow, tags, loaders, athena
       
   412             from nevow.page import renderer
       
   413             
       
   414             havetwisted = True
   435             havetwisted = True
   415         except:
   436         except:
   416             print "Twisted unavailable !"
   437             print "Twisted unavailable."
   417             havetwisted = False
   438             havetwisted = False
   418 
   439 
       
   440 pyruntimevars = {}
       
   441 statuschange = []
       
   442 
   419 if havetwisted:
   443 if havetwisted:
   420     
   444 
   421     xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
       
   422 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
       
   423 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
       
   424 '''
       
   425 
       
   426     class PLCHMI(athena.LiveElement):
       
   427     
       
   428         initialised = False
       
   429     
       
   430         def HMIinitialised(self, result):
       
   431             self.initialised = True
       
   432         
       
   433         def HMIinitialisation(self):
       
   434             self.HMIinitialised(None)
       
   435     
       
   436     class DefaultPLCStartedHMI(PLCHMI):
       
   437         docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[                                    
       
   438                                              tags.h1["PLC IS NOW STARTED"],
       
   439                                              ])
       
   440         
       
   441     class PLCStoppedHMI(PLCHMI):
       
   442         docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[
       
   443                                              tags.h1["PLC IS STOPPED"],
       
   444                                              ])
       
   445     
       
   446     class MainPage(athena.LiveElement):
       
   447         jsClass = u"WebInterface.PLC"
       
   448         docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[
       
   449                                                         tags.div(id='content')[                         
       
   450                                                         tags.div(render = tags.directive('PLCElement')),
       
   451                                                         ]])
       
   452         
       
   453         def __init__(self, *a, **kw):
       
   454             athena.LiveElement.__init__(self, *a, **kw)
       
   455             self.pcl_state = False
       
   456             self.HMI = None
       
   457             self.resetPLCStartedHMI()
       
   458         
       
   459         def setPLCState(self, state):
       
   460             self.pcl_state = state
       
   461             if self.HMI is not None:
       
   462                 self.callRemote('updateHMI')
       
   463         
       
   464         def setPLCStartedHMI(self, hmi):
       
   465             self.PLCStartedHMIClass = hmi
       
   466         
       
   467         def resetPLCStartedHMI(self):
       
   468             self.PLCStartedHMIClass = DefaultPLCStartedHMI
       
   469         
       
   470         def getHMI(self):
       
   471             return self.HMI
       
   472         
       
   473         def HMIexec(self, function, *args, **kwargs):
       
   474             if self.HMI is not None:
       
   475                 getattr(self.HMI, function, lambda:None)(*args, **kwargs)
       
   476         athena.expose(HMIexec)
       
   477         
       
   478         def resetHMI(self):
       
   479             self.HMI = None
       
   480         
       
   481         def PLCElement(self, ctx, data):
       
   482             return self.getPLCElement()
       
   483         renderer(PLCElement)
       
   484         
       
   485         def getPLCElement(self):
       
   486             self.detachFragmentChildren()
       
   487             if self.pcl_state:
       
   488                 f = self.PLCStartedHMIClass()
       
   489             else:
       
   490                 f = PLCStoppedHMI()
       
   491             f.setFragmentParent(self)
       
   492             self.HMI = f
       
   493             return f
       
   494         athena.expose(getPLCElement)
       
   495 
       
   496         def detachFragmentChildren(self):
       
   497             for child in self.liveFragmentChildren[:]:
       
   498                 child.detach()
       
   499     
       
   500     class WebInterface(athena.LivePage):
       
   501 
       
   502         docFactory = loaders.stan([tags.raw(xhtml_header),
       
   503                                    tags.html(xmlns="http://www.w3.org/1999/xhtml")[
       
   504                                        tags.head(render=tags.directive('liveglue')),
       
   505                                        tags.body[
       
   506                                            tags.div[
       
   507                                                    tags.div( render = tags.directive( "MainPage" ))
       
   508                                                    ]]]])
       
   509         MainPage = MainPage()
       
   510         PLCHMI = PLCHMI
       
   511         
       
   512         def __init__(self, plcState=False, *a, **kw):
       
   513             super(WebInterface, self).__init__(*a, **kw)
       
   514             self.jsModules.mapping[u'WebInterface'] = util.sibpath(__file__, os.path.join('runtime', 'webinterface.js'))
       
   515             self.plcState = plcState
       
   516             self.MainPage.setPLCState(plcState)
       
   517 
       
   518         def getHMI(self):
       
   519             return self.MainPage.getHMI()
       
   520         
       
   521         def LoadHMI(self, hmi, jsmodules):
       
   522             for name, path in jsmodules.iteritems():
       
   523                 self.jsModules.mapping[name] = os.path.join(WorkingDir, path)
       
   524             self.MainPage.setPLCStartedHMI(hmi)
       
   525         
       
   526         def UnLoadHMI(self):
       
   527             self.MainPage.resetPLCStartedHMI()
       
   528         
       
   529         def PLCStarted(self):
       
   530             self.plcState = True
       
   531             self.MainPage.setPLCState(True)
       
   532         
       
   533         def PLCStopped(self):
       
   534             self.plcState = False
       
   535             self.MainPage.setPLCState(False)
       
   536             
       
   537         def renderHTTP(self, ctx):
       
   538             """
       
   539             Force content type to fit with SVG
       
   540             """
       
   541             req = inevow.IRequest(ctx)
       
   542             req.setHeader('Content-type', 'application/xhtml+xml')
       
   543             return super(WebInterface, self).renderHTTP(ctx)
       
   544 
       
   545         def render_MainPage(self, ctx, data):
       
   546             f = self.MainPage
       
   547             f.setFragmentParent(self)
       
   548             return ctx.tag[f]
       
   549 
       
   550         def child_(self, ctx):
       
   551             self.MainPage.detachFragmentChildren()
       
   552             return WebInterface(plcState=self.plcState)
       
   553             
       
   554         def beforeRender(self, ctx):
       
   555             d = self.notifyOnDisconnect()
       
   556             d.addErrback(self.disconnected)
       
   557         
       
   558         def disconnected(self, reason):
       
   559             self.MainPage.resetHMI()
       
   560             #print reason
       
   561             #print "We will be called back when the client disconnects"
       
   562         
       
   563     if havewx:
   445     if havewx:
   564         reactor.registerWxApp(app)
   446         reactor.registerWxApp(app)
   565     website = WebInterface()
       
   566     site = appserver.NevowSite(website)
       
   567     
       
   568     website_port = 8009
       
   569     listening = False
       
   570     while not listening:
       
   571         try:
       
   572             reactor.listenTCP(website_port, site)
       
   573             listening = True
       
   574         except:
       
   575             website_port += 1
       
   576     print "Http interface port :",website_port
       
   577 else:
       
   578     website = None
       
   579 
   447 
   580 if havewx:
   448 if havewx:
   581     from threading import Semaphore
   449     from threading import Semaphore
   582     wx_eval_lock = Semaphore(0)
   450     wx_eval_lock = Semaphore(0)
   583     main_thread = currentThread()
   451     main_thread = currentThread()
   584 
   452 
   585     def statuschange(status):
   453     def statuschangeTskBar(status):
   586         wx.CallAfter(taskbar_instance.UpdateIcon,status)
   454         wx.CallAfter(taskbar_instance.UpdateIcon,status)
   587         
   455 
       
   456     statuschange.append(statuschangeTskBar)
       
   457 
   588     def wx_evaluator(obj, *args, **kwargs):
   458     def wx_evaluator(obj, *args, **kwargs):
   589         tocall,args,kwargs = obj.call
   459         tocall,args,kwargs = obj.call
   590         obj.res = default_evaluator(tocall, *args, **kwargs)
   460         obj.res = default_evaluator(tocall, *args, **kwargs)
   591         wx_eval_lock.release()
   461         wx_eval_lock.release()
   592         
   462 
   593     def evaluator(tocall, *args, **kwargs):
   463     def evaluator(tocall, *args, **kwargs):
   594         global main_thread
   464         global main_thread
   595         if(main_thread == currentThread()):
   465         if(main_thread == currentThread()):
   596             # avoid dead lock if called from the wx mainloop 
   466             # avoid dead lock if called from the wx mainloop
   597             return default_evaluator(tocall, *args, **kwargs)
   467             return default_evaluator(tocall, *args, **kwargs)
   598         else:
   468         else:
   599             o=type('',(object,),dict(call=(tocall, args, kwargs), res=None))
   469             o=type('',(object,),dict(call=(tocall, args, kwargs), res=None))
   600             wx.CallAfter(wx_evaluator,o)
   470             wx.CallAfter(wx_evaluator,o)
   601             wx_eval_lock.acquire()
   471             wx_eval_lock.acquire()
   602             return o.res
   472             return o.res
   603     
   473 
   604     pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, statuschange, evaluator, website)
   474     pyroserver = Server(servicename, given_ip, port,
       
   475                         WorkingDir, argv, autostart,
       
   476                         statuschange, evaluator, pyruntimevars)
       
   477 
   605     taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
   478     taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
   606 else:
   479 else:
   607     pyroserver = Server(servicename, given_ip, port, WorkingDir, argv, autostart, website=website)
   480     pyroserver = Server(servicename, given_ip, port,
       
   481                         WorkingDir, argv, autostart,
       
   482                         statuschange, pyruntimevars=pyruntimevars)
       
   483 
   608 
   484 
   609 # Exception hooks s
   485 # Exception hooks s
   610 import threading, traceback
   486 import threading, traceback
   611 def LogException(*exp):
   487 def LogException(*exp):
   612     if pyroserver.plcobj is not None:
   488     if pyroserver.plcobj is not None:
   629                 sys.excepthook(*sys.exc_info())
   505                 sys.excepthook(*sys.exc_info())
   630         self.run = run_with_except_hook
   506         self.run = run_with_except_hook
   631     threading.Thread.__init__ = init
   507     threading.Thread.__init__ = init
   632 installThreadExcepthook()
   508 installThreadExcepthook()
   633 
   509 
       
   510 if havetwisted:
       
   511     if webport is not None :
       
   512         try:
       
   513             import runtime.NevowServer as NS
       
   514         except Exception, e:
       
   515             print "Nevow/Athena import failed :", e
       
   516             webport = None
       
   517         NS.WorkingDir = WorkingDir
       
   518 
       
   519     if wampconf is not None :
       
   520         try:
       
   521             import runtime.WampClient as WC
       
   522         except Exception, e:
       
   523             print "WAMP import failed :", e
       
   524             wampconf = None
       
   525 
       
   526 # Load extensions
       
   527 for extfilename in extensions:
       
   528     extension_folder = os.path.split(os.path.realpath(extfilename))[0]
       
   529     sys.path.append(extension_folder)
       
   530     execfile(extfilename, locals())
       
   531 
       
   532 if havetwisted:
       
   533     if webport is not None :
       
   534         try:
       
   535             website = NS.RegisterWebsite(webport)
       
   536             pyruntimevars["website"] = website
       
   537             statuschange.append(NS.website_statuslistener_factory(website))
       
   538         except Exception, e:
       
   539             print "Nevow Web service failed.", e
       
   540 
       
   541     if wampconf is not None :
       
   542         try:
       
   543             WC.RegisterWampClient(wampconf)
       
   544             pyruntimevars["wampsession"] = WC.GetSession
       
   545             WC.SetServer(pyroserver)
       
   546         except Exception, e:
       
   547             print "WAMP client startup failed.", e
       
   548 
       
   549 
   634 if havetwisted or havewx:
   550 if havetwisted or havewx:
   635     pyro_thread=Thread(target=pyroserver.Loop)
   551     pyro_thread=Thread(target=pyroserver.Loop)
   636     pyro_thread.start()
   552     pyro_thread.start()
   637 
   553 
   638     if havetwisted:
   554     if havetwisted: