Beremiz_service.py
changeset 262 141a7145c099
parent 217 f3eb35df4d87
child 263 0bc32427a459
equal deleted inserted replaced
261:5299c6746fa8 262:141a7145c099
    21 #You should have received a copy of the GNU General Public
    21 #You should have received a copy of the GNU General Public
    22 #License along with this library; if not, write to the Free Software
    22 #License along with this library; if not, write to the Free Software
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    24 
    24 
    25 import os, sys, getopt
    25 import os, sys, getopt
       
    26 
       
    27 try:
       
    28     import wx, re
       
    29     from wx.lib.embeddedimage import PyEmbeddedImage
       
    30     from threading import Thread
       
    31     from types import *
       
    32     havewx = True
       
    33 except:
       
    34     havewx = False
       
    35 
       
    36 BeremizIcon = PyEmbeddedImage(
       
    37 "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAADopJ"
       
    38 "REFUaIG1mntwXNV9xz/n3rt79+5KWq1WsiRLWlkyfgnHsiwMtnkUYptQMBGFMjRhCDANJmMe"
       
    39 "pYV/gJZkpoHMJJm2NG1JBzKBQNIUGlLbmMG1MWAZ25LAtmwZW5Yta/WyHtZ7tSvt7r2nf1yt"
       
    40 "vJJWtmyH386Z3XvuOff+vr/zO7/XWSG4JAlAAzxANlAIFAMBS8oCYF5jY2NhdXV1UWtrq7u9"
       
    41 "vV1vbW1VY7GYMAxDulwuy+VyxUpLS0MVFRVdGzZsqPFnZTUBLUAr0AUMAGHAAhyAAehAbKI/"
       
    42 "CsiUzF0CgDrxMD+wAFhiSbkUKN2/f3/Ztm3bCj/66CNXfX39HORgk8PhoKysLL5p06bWxx9/"
       
    43 "fHtxIFALnAI6gDEgHZg/8X1+AuQgYKZ8oJi9aQKyBJQL+K6U8qdSyg+qq6vb1q9fb05I5Kpa"
       
    44 "enq6vP/++wf27NnzCwF/IeA2AQ8I+Acp5Y8feuih1wRsFOCcjc/ZmHcKyBdws4BnpJS/PXr0"
       
    45 "aNM999wTF0JcNePTm2EYcsuWLe2jo6P/LqV8OxwO77333ntbASngOQHpswo6xdo7gRxgKbDO"
       
    46 "knLt22+/vW7Lli3eUCiUchV1XWfJNYWUzE8jkGtQnGfg0SWmVBmNarR2R2nuGOZ0sJ8zZ4NY"
       
    47 "lpXyOcuXL5cvvfTSwCuvvJJ+5MgRB8DKlSv/q/7IkV8Be7H3xBSaDsCBre9lwI3RWGztM888"
       
    48 "c8trr73mkVIipUyaKCi/toQ7bsimaq1Bmm4iLROwIDFOKCAUhFARqgNFMwj2G7y/d5D/23eS"
       
    49 "YGt7SiDJtGDBghPBlpafA78Fxi8GQAUygcXA2tDo6M1VVVW3V1dXu6WUWJaFZVlIKVlUWsDf"
       
    50 "PRBg3VKQVhxkaolOexVCURCqjmb40TMX8Yf9Mf7ll/9NR0fnrLNycnK6z/f2/gz4DyAy/b6S"
       
    51 "9O0BCoAyS8qljz766K0HDx50q6pKonncbp7/60r+54dFrF0cR5rROTIPIJGWBdJCCA2Hnsb3"
       
    52 "v/stDny2g7zc3FlnjY6Opl+7fHk6tmrPIA17LziALGChJeXCp59+ev2uXbsyVVVFURQsyyI3"
       
    53 "x8dPNhdTVmgizRmqODcSAqFoqE4Pqp5JU/A8D27eQld396xTwuGw2+l0uoEMYJhp/kAVtupk"
       
    54 "ACVAme5y3fnGG2+UCyFQFAUhBIH5Pn71XDGFWbHp8y8fg6KhqE4+qevggcd/Sld37yXnBAKB"
       
    55 "zo6Oji+A3ukMKNhL4wPyjh47ds3rr7++RlVVNE1DVVVystJ59Yn5ZLhmdYaXQRJpjhML91FZ"
       
    56 "Ms4/PnUTq1cUX3KW1+tdj60hM4ymKsCLrfsL+gcGHgwGg/MSkjcMnZ98fz4Bf8JvTaDWDBzu"
       
    57 "HFRn+pybUDSs+JhtoawYwopwzXyV+24LcNdtZTh0D83tg4xH4zMAFBYWGoVFRefbWls/BaYM"
       
    58 "EAKKgGX/u3XrhmefffY50zRFwupsvjuPu1czY6NmltxO4M9evqTkppMZGyU60sFA03b6Gt9D"
       
    59 "WhZCURGqE1VzExUedtYO8PuPmjh8vG1y3ooVKygpKenZtnXrLUATdsw0uQL5QH5ff/8TQ0ND"
       
    60 "OQnpF+en8bdVOsiZIYjLtxDvgvWXDUBRnTgMP+mF68hcsIHhtk8xoyNIK440xxBWmCWFGlXr"
       
    61 "S83himXCOegRfX19ABQVFXl0l8vo7uraTZJDU4UdXV5rGMbDYDsoRVF48tuZ5Gemjp+uFEAy"
       
    62 "aa5M3NllDJ7ZwWRUIU2kGeWr/pGBwz7LPc+by+J5y1BVFZfLha7r+c3NzR8DPUysggJoTzz5"
       
    63 "5A2Kokxu3KXF6VSWWlz9pr04eXIrSC+6eVqv5MDwWGgsFmGooIfx2Bh+vx8pJbqu5wPfJMkn"
       
    64 "aIBobGy8TtM0LMtCCMHNyzVkCtWZC50//juG2/YCoDjceOaVk1F8G3pGIOV4w7+U4dbPJq9H"
       
    65 "4zJ6Ik3PEUikM47pj6L0XPBhK8rL1x2tr38PO5+wNCBjeHh4maqqCCHQNJW1ixWmbfY50/hw"
       
    66 "K6GuLyevh9uqGQru4ZpNb6Uc7zD8U67f6ol1Cr+xIHFt5Y1PAZCVlXUtsARoB6IK8A1VVUXC"
       
    67 "9i8qcJHuujLpz0ZjQy12zJTq3mDz5O/fn4u2ns1yTVkqmRudYv3dbvcCYBF29IDy8COP+JMd"
       
    68 "V0muQP4pdV8oZC2qQijajFtWfIyh4B4AXu2ItnyZZQQQQkkeI3UL6bpgxoUQaobXOw/bfwlt"
       
    69 "ZGTEr6oqUkqEEPjT5YVw+Aooa1EVnrxKAFSnB3fON1Cd6TPGSStGR+0/yR0tHZ2fWIoSz76g"
       
    70 "Nkno7XDZZULkAi6Px5M5PDSUA3Rr4+PjOckb2Jd2ZbqfICN7GUb2souOGeiqN1/d/uOuz8Mt"
       
    71 "GUqGq2C2cQJQhIJ0WrbTEwIhBG63OwO7wKAoQE4iXNY0jQwjkel9fRQx/Gppxe0Fvkz/zKWZ"
       
    72 "hkCgIHVJPB7HNE1M08TlcmVg5y5CAULJeyBuJbB/fTTfW8j3rnuMf7vvTUr9iy46VhECxVQm"
       
    73 "mTdNE2wn5gSEJqXsUBRbvxRFYXRc4WIrYEnk6bAyFLAlMIP2t3zGye7jgO3Vi32lLM8vZ15a"
       
    74 "3oyx89Ly+PuNr/DYu3+Fac20fAKBoqhEw/YKKIqCoijE4/EwdhqA9uGOHZ13bdo0+UIbQGqq"
       
    75 "GYz3vCdV520BMr85y5gv2mrY8dX7M/o3r/0b7lvxnRn9Bd4ibiy5lb1nPk4JQBUqsRFbfRJB"
       
    76 "pmmaISaySQVoS540MKqmZGx7T6z9PaczS7i1lJK/FH341R9nvXdN9pIUvfaGdahOxoaiU1Ro"
       
    77 "AsAYIDXgdPK0pm59xqPeOhcNNviM4kuV8S5Gq4pumPVemjNtJvsCNEVD9GuMj4xPpraqqkbi"
       
    78 "8XgEGEkAOIBdm/QBDIZVuoY08ry2Oe2OmKFjma6CuTKf4cogNz3fZgLBgqxSyudXsn7xn886"
       
    79 "p6GrfkafIhScmk64KYZpmliWxUTA2WbZhaXBBIC4lPJDRVEeTITSp3rSyMscBin5db/VJ7LF"
       
    80 "pfO+CXpk9Q94ZPUP5jocgPrOL6dc27qvYTjcdJzowjRNhBAJZ3u2o73dBLoBSwH4cMeOPyqK"
       
    81 "gsfjwe/3c2aoALQMxoUjNpDtKRTYn6+DDgb30Td6fkqfIhRcDhei08Fg19CkDwDMWCzWBIwy"
       
    82 "kRMkTM5OVVVHdF2npqaGSNxFMFZBt6M45PP4VJfDQFU0hFD+pEC+aDvIy7temNKX2LjpupfT"
       
    83 "H7VP2bxpaWldIyMjo9iGJwp2PgAQMgzjX/Py8l5sbGykoaGBbdvyuOPOG9wl31pMJD3EQKSf"
       
    84 "0PgI4/ExFDG7qZ0LtQ+1UhPcx5u1vyRqRpPZR1M00vR0osclnWfOTdp+KaU0DKN2ZGRkDDjJ"
       
    85 "RLk9eW/mv/DiiyfeeecdbzAYnOxUNZXrb72OVXeuIHOJh/PhXjJ0L6sKr8eUJnIOlbloPMpA"
       
    86 "pI/+cB9tg0G6R87NGCMQqIpKmp5OjjqfT392kOH+kcnaVFFRUUc0Gn23tqamCXgbCMGFFQAY"
       
    87 "9vv9v8nLy3sqGYAZNzmwu4YDu2vIL8rjpm+vxXeLn+rmj+kN9TAaDWFa5lWF4AmP63GmMc+d"
       
    88 "T90vjtHX3Z+QPE6n08rJydl3+PDhEFBPUpE3eQVcwMq7Nm3a88EHHxgXe6GmqZTduISlf1lC"
       
    89 "xBghGo9eMQAhbIvjcXrIzyjk2K+bOHbgK5Irg+Xl5c1DQ0Nbaw4ePINdpR5MzE9WZgsYzMvL"
       
    90 "23epl1pSUrSkEHe2a0rJ/bIYR9i2XtXJNHwEvCWc/l0Hh/bWT1qdeDxOIBAYcrlcu2oOHhwA"
       
    91 "qplQnQQlq5AEIgsXLjwObJztxf4cP1tefoxI3hDB/masOVenLzCOEGiKiksz8Lmz8Ms8dv1k"
       
    92 "LycbGiclL6WksLAwumzZsh01NTX9wDHsotaUhCUZgAWMvfD8881chAJFATLcGVjqGG6nh7gV"
       
    93 "J2qOY0kLS14kmxMCRdhWxqnqZLi85KblEzoU4zf/+S7ne89PqoyUEp/PZ95000276+rqOhtP"
       
    94 "nmwFPseOf6Y/dpIU7DzzZs3heD8Wi6WO6rDD7o13buD676wkkj7CYKSf0HiIaHwcU8ZtIEkS"
       
    95 "FxOMuxwG6XoGWW4/Yyctdr65h+PHLoTeCZNZUFAQ37hx4ycnTpxo+HzfvhbgfaCTpJJiKgAC"
       
    96 "SANWFxYV/aGtrS0TQNd16fV6RU9PTwqhClasXEHlrSuZf10O+EzGYhFiVgzTshACVKHi1HQM"
       
    97 "zc1Y0OT0vha+3HeIlpaWlMJZs2bN+Jo1a3bU1dUF93/+eTuwFbsGlLJUMh2AASwvX7nynSNH"
       
    98 "jizKzs6Obt68+ZDf74/s3Llzza5du4yLbdrMzExKF5biy/aBkCiKQnQsxrn2Ttra2olEZpwQ"
       
    99 "TZLT6eTuu+8+n5ub+2FtbW3Xl198EQR2AkEuUqSaDsAJBNasXfvPXV1dy6uqqurOnDnTE4lE"
       
   100 "Rj7evTv01NNPV23fvn3V2bNnr84VJ5Gqqtxyyy3hioqKA8FgsKmhoeH8qcbGr4DdQD+zHXCn"
       
   101 "AAAXTmtWA+6KVauuO3zoUA+wB/vUfNEPf/SjO7q6uu6pr69fVFdX54jHr6yK4fF4qKysjFRW"
       
   102 "Vh7q7e09cerUqYG62to+oBaoww7YLmmjpwNI/C9Cxz4rrsS2vX3YG0jHzhtueOLJJ9fqur6s"
       
   103 "tbW14ty5c9nNzc3Ozs7OWSM9VVXJycmRZWVl4dLS0jav13u0vb194OzZs8N1tbUD2GayDlvq"
       
   104 "cz6ES5WnJLo07BUZ54IkEgfkLuz/MiwFKr738MMlPp8vDfBHIpFcKaXHNE0jGo3idDrDHo+n"
       
   105 "z+Vy9YRCoUhvb2+ku7s73NPTM9p48mQPdkZ4DHuFL/sQ7kqzxMQ0xwSYTOxzhvyJ5l934406"
       
   106 "IGKxmBUOh83jDQ1j2JnfAHAOOJvEdPxyGb9aADOeg+1HkhvYADVs5sax1XB6uyr6fzqK/HuW"
       
   107 "ycvmAAAAAElFTkSuQmCC")
    26 
   108 
    27 def usage():
   109 def usage():
    28     print """
   110     print """
    29 Usage of Beremiz PLC execution service :\n
   111 Usage of Beremiz PLC execution service :\n
    30 %s {[-n name] [-i ip] [-p port]|-h|--help} working_dir
   112 %s {[-n name] [-i ip] [-p port]|-h|--help} working_dir
    82 
   164 
    83 if not os.path.isdir(WorkingDir):
   165 if not os.path.isdir(WorkingDir):
    84     os.mkdir(WorkingDir)
   166     os.mkdir(WorkingDir)
    85 
   167 
    86 
   168 
    87 pyro.initServer()
   169 
    88 daemon=pyro.Daemon(host=ip, port=port)
   170 class Server():
    89 uri = daemon.connect(PLCObject(WorkingDir, daemon, args),"PLCObject")
   171     def __init__(self, name, ip, port, workdir, args):
    90 
   172         self.continueloop = True
    91 print "The daemon runs on port :",daemon.port
   173         self.daemon = None
    92 print "The object's uri is :",uri
   174         self.name = name
    93 print "The working directory :",WorkingDir
   175         self.ip = ip
    94 
   176         self.port = port
    95 # Configure and publish service
   177         self.workdir = workdir
    96 # Not publish service if localhost in address params
   178         self.args = args
    97 if ip != "localhost" and ip != "127.0.0.1":    
   179         self.plcobj = None
    98     print "Publish service on local network"
   180         self.servicepublisher = ServicePublisher.ServicePublisher()
    99     service = ServicePublisher.ServicePublisher(name, ip, port)
   181 
   100 
   182     def Loop(self):
   101 sys.stdout.flush()
   183         while self.continueloop:
   102 
   184             self.Start()
   103 daemon.requestLoop()
   185         
       
   186     def Restart(self):
       
   187         self.Stop()
       
   188 
       
   189     def Quit(self):
       
   190         self.continueloop = False
       
   191         self.Stop()
       
   192 
       
   193     def Start(self):
       
   194         pyro.initServer()
       
   195         self.daemon=pyro.Daemon(host=self.ip, port=self.port)
       
   196         self.plcobj = PLCObject(self.workdir, self.daemon, self.args)
       
   197         uri = self.daemon.connect(self.plcobj,"PLCObject")
       
   198     
       
   199         print "The daemon runs on port :",self.port
       
   200         print "The object's uri is :",uri
       
   201         print "The working directory :",self.workdir
       
   202         
       
   203         # Configure and publish service
       
   204         # Not publish service if localhost in address params
       
   205         if self.ip != "localhost" and self.ip != "127.0.0.1":    
       
   206             print "Publish service on local network"            
       
   207             self.servicepublisher.RegisterService(self.name, self.ip, self.port)
       
   208         
       
   209         sys.stdout.flush()
       
   210         
       
   211         self.daemon.requestLoop()
       
   212     
       
   213     def Stop(self):
       
   214         self.servicepublisher.UnRegisterService()
       
   215         self.daemon.shutdown(True)
       
   216 
       
   217 class ParamsEntryDialog(wx.TextEntryDialog):
       
   218     if wx.VERSION < (2, 6, 0):
       
   219         def Bind(self, event, function, id = None):
       
   220             if id is not None:
       
   221                 event(self, id, function)
       
   222             else:
       
   223                 event(self, function)
       
   224     
       
   225 
       
   226     def __init__(self, parent, message, caption = "Please enter text", defaultValue = "", 
       
   227                        style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition):
       
   228         wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos)
       
   229         
       
   230         self.Tests = []
       
   231         if wx.VERSION >= (2, 8, 0):
       
   232             self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
       
   233         elif wx.VERSION >= (2, 6, 0):
       
   234             self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetAffirmativeButton().GetId())
       
   235         else:
       
   236             self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId())
       
   237     
       
   238     def OnOK(self, event):
       
   239         value = self.GetValue()
       
   240         texts = {"value" : value}
       
   241         for function, message in self.Tests:
       
   242             if not function(value):
       
   243                 message = wx.MessageDialog(self, message%texts, "Error", wx.OK|wx.ICON_ERROR)
       
   244                 message.ShowModal()
       
   245                 message.Destroy()
       
   246                 return
       
   247         self.EndModal(wx.ID_OK)
       
   248     
       
   249     def GetValue(self):
       
   250         return self.GetSizer().GetItem(1).GetWindow().GetValue()
       
   251     
       
   252     def SetTests(self, tests):
       
   253         self.Tests = tests
       
   254         
       
   255 class DemoTaskBarIcon(wx.TaskBarIcon):
       
   256     TBMENU_CHANGE_NAME = wx.NewId()
       
   257     TBMENU_CHANGE_PORT = wx.NewId()
       
   258     TBMENU_CHANGE_INTERFACE = wx.NewId()
       
   259     TBMENU_CHANGE_WD = wx.NewId()
       
   260     TBMENU_QUIT = wx.NewId()
       
   261     
       
   262     def __init__(self, pyroserver):
       
   263         wx.TaskBarIcon.__init__(self)
       
   264         # Set the image
       
   265         icon = self.MakeIcon(BeremizIcon.GetImage())
       
   266         self.SetIcon(icon, "Beremiz Service")        
       
   267         # bind some events
       
   268         #self.Bind(wx.EVT_TASKBAR_CLICK, self.OnClick)
       
   269         #self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
       
   270         self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME)
       
   271         self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE)
       
   272         self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT)
       
   273         self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
       
   274         self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT)
       
   275     
       
   276     def CreatePopupMenu(self):
       
   277         """
       
   278         This method is called by the base class when it needs to popup
       
   279         the menu for the default EVT_RIGHT_DOWN event.  Just create
       
   280         the menu how you want it and return it from this function,
       
   281         the base class takes care of the rest.
       
   282         """
       
   283         menu = wx.Menu()
       
   284         menu.Append(self.TBMENU_CHANGE_NAME, "Change Name")
       
   285         menu.Append(self.TBMENU_CHANGE_INTERFACE, "Change IP of interface to bind")
       
   286         menu.Append(self.TBMENU_CHANGE_PORT, "Change Port Number")
       
   287         menu.AppendSeparator()
       
   288         menu.Append(self.TBMENU_CHANGE_WD, "Change working directory")
       
   289         menu.Append(self.TBMENU_QUIT, "Quit")
       
   290         return menu
       
   291 
       
   292     def MakeIcon(self, img):
       
   293         """
       
   294         The various platforms have different requirements for the
       
   295         icon size...
       
   296         """
       
   297         if "wxMSW" in wx.PlatformInfo:
       
   298             img = img.Scale(16, 16)
       
   299         elif "wxGTK" in wx.PlatformInfo:
       
   300             img = img.Scale(22, 22)
       
   301         # wxMac can be any size upto 128x128, so leave the source img alone....
       
   302         icon = wx.IconFromBitmap(img.ConvertToBitmap() )
       
   303         return icon
       
   304     
       
   305     def OnTaskBarChangeInterface(self,evt):
       
   306         dlg = ParamsEntryDialog(None, "Enter the ip of the interface to bind", defaultValue=pyroserver.ip)
       
   307         dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, "Ip is not valid!"),
       
   308                        ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, "Ip is not valid!")
       
   309                        ])
       
   310         if dlg.ShowModal() == wx.ID_OK:
       
   311             pyroserver.ip = dlg.GetValue()
       
   312             pyroserver.Stop()
       
   313             
       
   314     def OnTaskBarChangePort(self,evt):
       
   315         dlg = ParamsEntryDialog(None, "Enter a port number ", defaultValue=str(pyroserver.port))
       
   316         dlg.SetTests([(UnicodeType.isdigit, "Port number must be an integer!"), (lambda port : 0 <= int(port) <= 65535 , "Port number must be 0 <= port <= 65535!")])
       
   317         if dlg.ShowModal() == wx.ID_OK:
       
   318             pyroserver.port = int(dlg.GetValue())
       
   319             pyroserver.Stop()
       
   320             
       
   321     
       
   322     def OnTaskBarChangeWorkingDir(self,evt):
       
   323         dlg = wx.DirDialog(None, "Choose a working directory ", pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
       
   324         if dlg.ShowModal() == wx.ID_OK:
       
   325             pyroserver.workdir = dlg.GetPath()
       
   326             pyroserver.Stop()
       
   327             
       
   328     def OnTaskBarChangeName(self,evt):
       
   329         dlg = ParamsEntryDialog(None, "Enter a name ", defaultValue=pyroserver.name)
       
   330         dlg.SetTests([(lambda name : len(name) is not 0 , "Name must not be null!")])
       
   331         if dlg.ShowModal() == wx.ID_OK:
       
   332             pyroserver.name = dlg.GetValue()
       
   333             pyroserver.Restart()
       
   334 
       
   335     def OnTaskBarQuit(self,evt):
       
   336         pyroserver.Quit()
       
   337         self.RemoveIcon()
       
   338         
       
   339 pyroserver = Server(name, ip, port, WorkingDir, args)
       
   340 
       
   341 if havewx:
       
   342     app=wx.App(redirect=False)
       
   343     taskbar_instance = DemoTaskBarIcon(pyroserver)
       
   344     pyro_thread=Thread(target=pyroserver.Loop)
       
   345     pyro_thread.start()
       
   346     app.MainLoop()
       
   347 else:
       
   348     pyroserver.Loop()