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() |