greg@262: #!/usr/bin/env python greg@262: # -*- coding: utf-8 -*- greg@262: greg@262: #This file is part of Beremiz, a Integrated Development Environment for greg@262: #programming IEC 61131-3 automates supporting plcopen standard and CanFestival. greg@262: # greg@262: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD greg@262: # greg@262: #See COPYING file for copyrights details. greg@262: # greg@262: #This library is free software; you can redistribute it and/or greg@262: #modify it under the terms of the GNU General Public greg@262: #License as published by the Free Software Foundation; either greg@262: #version 2.1 of the License, or (at your option) any later version. greg@262: # greg@262: #This library is distributed in the hope that it will be useful, greg@262: #but WITHOUT ANY WARRANTY; without even the implied warranty of greg@262: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU greg@262: #General Public License for more details. greg@262: # greg@262: #You should have received a copy of the GNU General Public greg@262: #License along with this library; if not, write to the Free Software greg@262: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA greg@262: greg@262: import os, sys, getopt greg@262: greg@262: def usage(): greg@262: print """ greg@262: Usage of Beremiz PLC execution service :\n greg@330: %s {[-n name] [-i ip] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir greg@262: -n - zeroconf service name greg@262: -i - ip of interface to bind to (x.x.x.x) greg@262: -p - port number greg@262: -h - print this help text and quit greg@330: -a - autostart PLC (0:disable 1:enable) greg@271: -x - enable/disable wxTaskbarIcon (0:disable 1:enable) laurent@368: -t - enable/disable Twisted web interface (0:disable 1:enable) greg@262: greg@262: working_dir - directory where are stored PLC files greg@262: """%sys.argv[0] greg@262: greg@262: try: laurent@368: opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:h") greg@262: except getopt.GetoptError, err: greg@262: # print help information and exit: greg@262: print str(err) # will print something like "option -a not recognized" greg@262: usage() greg@262: sys.exit(2) greg@262: greg@262: # default values greg@262: ip = "" greg@262: port = 3000 greg@262: name = os.environ[{ greg@262: "linux2":"USER", greg@262: "win32":"USERNAME", greg@262: }.get(sys.platform, "USER")] greg@330: autostart = False greg@271: enablewx = True greg@271: havewx = False laurent@368: enabletwisted = True laurent@368: havetwisted = False greg@262: greg@262: for o, a in opts: greg@262: if o == "-h": greg@262: usage() greg@262: sys.exit() greg@262: elif o == "-i": greg@262: if len(a.split(".")) == 4 or a == "localhost": greg@262: ip = a greg@262: elif o == "-p": greg@262: # port: port that the service runs on greg@262: port = int(a) greg@262: elif o == "-n": greg@262: name = a greg@271: elif o == "-x": greg@271: enablewx = int(a) laurent@368: elif o == "-t": laurent@368: enabletwisted = int(a) greg@330: elif o == "-a": greg@330: autostart = int(a) greg@262: else: greg@262: usage() greg@262: sys.exit() greg@262: etisserant@301: if len(argv) > 1: greg@262: usage() greg@262: sys.exit() etisserant@301: elif len(argv) == 1: etisserant@301: WorkingDir = argv[0] etisserant@301: elif len(argv) == 0: greg@262: WorkingDir = os.getcwd() etisserant@301: argv=[WorkingDir] greg@262: laurent@361: import __builtin__ laurent@361: if __name__ == '__main__': laurent@361: __builtin__.__dict__['_'] = lambda x: x laurent@361: greg@271: if enablewx: greg@271: try: greg@271: import wx, re greg@330: from threading import Thread, currentThread greg@271: from types import * greg@271: havewx = True greg@271: except: greg@271: havewx = False greg@271: greg@271: if havewx: laurent@361: app=wx.App(redirect=False) laurent@361: laurent@361: # Import module for internationalization laurent@361: import gettext laurent@361: laurent@361: CWD = os.path.split(os.path.realpath(__file__))[0] laurent@361: laurent@361: # Get folder containing translation files laurent@361: localedir = os.path.join(CWD,"locale") laurent@361: # Get the default language laurent@361: langid = wx.LANGUAGE_DEFAULT laurent@361: # Define translation domain (name of translation files) laurent@361: domain = "Beremiz" laurent@361: laurent@361: # Define locale for wx laurent@361: loc = __builtin__.__dict__.get('loc', None) laurent@361: if loc is None: laurent@361: loc = wx.Locale(langid) laurent@361: __builtin__.__dict__['loc'] = loc laurent@361: # Define location for searching translation files laurent@361: loc.AddCatalogLookupPathPrefix(localedir) laurent@361: # Define locale domain laurent@361: loc.AddCatalog(domain) laurent@361: laurent@361: def unicode_translation(message): laurent@361: return wx.GetTranslation(message).encode("utf-8") laurent@361: laurent@361: if __name__ == '__main__': laurent@361: __builtin__.__dict__['_'] = wx.GetTranslation#unicode_translation laurent@361: lbessard@294: try: lbessard@294: from wx.lib.embeddedimage import PyEmbeddedImage lbessard@294: except: lbessard@294: import cStringIO lbessard@294: import base64 lbessard@294: lbessard@294: class PyEmbeddedImage: lbessard@294: def __init__(self, image_string): lbessard@294: stream = cStringIO.StringIO(base64.b64decode(image_string)) lbessard@294: self.Image = wx.ImageFromStream(stream) lbessard@294: def GetImage(self): lbessard@294: return self.Image lbessard@294: greg@271: defaulticon = PyEmbeddedImage( greg@271: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABc5J" greg@271: "REFUSIl9lW1MW9cZx3/n2vf6BQO2MZiXGBISILCVUEUlitYpjaKpXZJ1XZZ2kzJVY9r6IeLD" greg@271: "pGTaNG3KtGmNNGlbpW3VFhRp0l6aZCllpVUqtVNJtBFKE5QXLxCjpCYEY7DBr9hcm3vPPgQY" greg@271: "IQmPdKR7/vd5/v/n5dxzhZSSNeYBOoGDQGcoFPINDAyUDQ0NOUdGRmyGYSiBQGCpoaGhuGnT" greg@271: "psShQ4f6WltbewEBVAK3gCBgrjJKKZFSKlLKeillt5Ty40gkMnnw4MFFQG60ysrKZHd3dyoe" greg@271: "j//bNM0Le/fuPd/e3r5lmRMpJWK5ghrgFeBIT09P4/Hjx73pdFo47HaaNlfRutnJru0OKsoE" greg@271: "E3GVqaSNa6EUw1dvIKWkoqKCrVu3FoeHh9WamppfRiKRn6wUYAUcwE7g2e7u7vrTp09XGIZB" greg@271: "W1Mdv3qtmoBPrG0hHVsMhKLj6nqOqOWn/Pjnv2dgYIC5uTl1uSM71/pbgUbg6bNnz/rPnDnj" greg@271: "dzoddO0P8Oo+jY2suDDD1Zv9DA1dfghXVbVBCFEqpcwAKEDTxMSE58SJE8+oqsq3nq/l1X0a" greg@271: "QihYtNLHLqRET03wuYp7fO9r26mpKlsVUBSl0W63V6/shZTyyIEDB344Njb21JYaG7/5bgkA" greg@271: "Dm8zTS/+7bHZLy0mSN+7yNztt8nPjYHFwfvXDf1P70zZ0ok0LS0tZy9fvvxNAGswGFQnJyef" greg@271: "KnM5+NHLzuUDsrFZ7R68zS/hrGon1PcNMPI0BIzs9tcCNvNfDqxW64uqqvqKxWJc6e3trVVV" greg@271: "leaAk6ryJ5N/9tH3GXv7Je7/5xermN3diMPXCkDfgrkg3UU0txWLxeLw+/1fB1BGR0frbTYb" greg@271: "TXWWDbNeysUoZKbIRIZBPviOzKU8ejLMHyPFcMprrweQ7iUAXC7XPiGEak2lUk02m42mWn1D" greg@271: "gfrnTiKNIrbyzSAUjEKWCx+/Mf+HyELBrLBvBhAIKDdgGsrLy+sAv1UIUa1pGv7yxQ0FbGX1" greg@271: "D+0LQmHW7fVavE5Mo/gAFCCcoOs6NpvNA7gVRVGCmqYRz1hXg7NFU39rjshawjcuvs4P+o/y" greg@271: "24uvE1+I4VCdfGfXUb76+VdWfQQCkbJSKBQoFApJTdMsCvApQDSlAjCTN7I/y5CNllpq1wqE" greg@271: "YmPciIzwwdi7BKevreK7Gp5dfbYoFoozJrquo+v6rMViWbQCV4QQzGTsQJY3kzIhvFpgfYte" greg@271: "7jhCMp9kk7uep+ueWcWj6f8Xqioq8ck0xcIS6XT6vpRy3gqMqKpqRBfKLLNF1ZRV6YBiPDrw" greg@271: "vduefwTL6hl6b74FgFVR0T4rJTU3jcvlymcymal8Ph+z9vf3p7u6uv5y/vz5bw994ld2fmUH" greg@271: "7nYFRVG4Gb3Guv8FpmmQzCcIJ+5w8c5HRFL3UYRC+ZKX633j6LpObW3tDcMwrsODq4Jbt27V" greg@271: "HT58+N7o6KgCYHfY2f2lXfi+6CJbnsAwjUeyXzFFKLgdHqb+mmL8xh22bduWmJycfHN2dvbX" greg@271: "uVwuoQC0tbXlKisrYytBi/lFZsKzOErtTyQWCOxWO36ljvl/FLk+dJOSkhJTUZR35+fn+3K5" greg@271: "XAIeXNcASz6fbxzwrxDYVQdqpARvs498IYchDUxpogiBVVFxqE7U/5Zx4c8fEo/FKS0tlR0d" greg@271: "HZ8ODg6+l06nr6zwrAp4PJ6Qpmlf2L9/fywYDFaOXB0RI1dHaGpuoq29Fa1Uxe62YeZMInei" greg@271: "jAY/IRqNAtDZ2blUV1fXPzg4+F5VVdU/H6p0eYjqsWPHvnz37t0XwuHw7d27d4eTyeTvLl26" greg@271: "FJiamnpim6qrq9mzZ094fHz875FI5J3p6ekr631WBARgaWlpCezYsePeuXPnzFAo5Dp58uS+" greg@271: "dDp91GKxNBYKBW82m3Vomqa7XK7pbDYbnJmZuR2LxYL5fP79WCyWeeys1h/D9e97enqsp06d" greg@271: "8mWzWU+xWPTkcjmXaZpxwzDCsVhsbqNggP8BMJOU3UUUf+0AAAAASUVORK5CYII=") greg@271: greg@271: #---------------------------------------------------------------------- greg@271: starticon = PyEmbeddedImage( greg@271: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABbpJ" greg@271: "REFUSIl9lltsFNcdxn9nZnbHs15fd23j9TXYC0UCKzEhMQ+oIS2g1kQ1pbFStX0opFWsovSh" greg@271: "rUqp2pS2ioTUolaKFOGHqGkiJcKRuDhOaZRiZCsCXyBgCBBfMfbu+oa9s17wzuzl9MH24mDD" greg@271: "XzoPc/6fft+c72jOGSGlZEVlAU8D9cB20zQ9HR0duRcvXszq7e01EomEUlFREa+srLR8Pl+g" greg@271: "sbHx3zk5ORcAFfACA8Bt4CFUSomUUkgpS6SUB6SUH5umOXLgwIEHqqrKJfGao7S0VB49ejRo" greg@271: "2/YnUsrT+/fvb66pqSldYiKlRCytoBB4Gfjx6dOnq5qamjwTExOKqqqU+QrYUJFN7QY32Qbc" greg@271: "vSeYCGtcux1i5M5dAPx+P1VVVQvnzp0ziouLfx8MBt9cXoAGZABbgZ1HjhwpO378eEEymaSi" greg@271: "tIBjPy9lU5nKoyWExF2yjy+mN3HsH+/Q3d3NwMCAsZTI9pVaDXgK2Hr27Nn85ubmEpdh8IMX" greg@271: "ffxirwshVrGXHBQSC/dIRvoZGuz/WkvTtHIhhCGlXABQgI2Tk5P5hw8f3uZwOGj8VjGHXnoC" greg@271: "HJCpJFbkLtr8FXbX+XC79HRPVVW/qqre9LtIKX/S0NDwy76+vq1lhTr/fM2NAmTk+fHv/dea" greg@271: "BlZkDHP0PHODH2NHg1gykw8/X7Dfb7vjTNgJqqurT3R1db0GoF2/fl0fGhqqdWca/K7RhZLO" greg@271: "WSBU55oGGXlVZORVkeV7nsFPDqKL+9TWJCI3n9rojX2mYhjGj4QQv5FSziunTp0qdjqd4hvl" greg@271: "Lnz5j49lrPMNhv7zM6b63knPuQpryMj3A9A2L++nvDaZXheqqrrXrVu3D0C5detWudPpxO/T" greg@271: "Hk8HYnOD3J+8yr3bH6XnZNImHg3xfsgenfHo5QAyJwFAdnb2HiGEppmmWa3rOhtKrCcalNT9" greg@271: "llTSwvBsXISn4nRdbJ5/czRsWvlGhQAEYtFg0kl2dnYZUKgB5U6nk5L82BMNXIU1X3uOWFH5" greg@271: "eWIuy/YYWcjU4qQAxQ22bWMYhgfIU1RV/UrXdWaiDyOyUiLROktoJfDtC8fZfWQbb//v75ix" greg@271: "MDlGnvjVC3+gflNDWiMQKPMalmVh2/a8w+HQFKAHIBR2ABCOS+uN6cTMoFstXmlwZbSba7tv" greg@271: "8hfzT7z+7k+ZnZ0BoK5yR1qjCBV7MoVt29i2PaWqqq0BvUIIQqYORHlrKj6R9BoVj0b04oY9" greg@271: "nEt+yvz3Y5yR/+Xap3XsDb/EtvV1aY1DdTA7HsW2bCKRyLiUclYBelRVldNWAfPSm4oV5ZQJ" greg@271: "Vn/G9Zv2oWt6Ous7e4K81XiC1wNNBO6OIWKgB7Mwp000TYuFw+GxWCw2qbS2tk7k5uae/eDD" greg@271: "Fn594p6SFyxRCjKLUBWF8fBoegTNMVLLm/kwdMyGGON/nePLklv0dl/Cii3gdrtvAzdg8aig" greg@271: "vb296uDBgwMjIyMCwFvoZXv9NvRnIKqHSckUyQdJrtfexPqm5LGVAuNdVaofcCVywfpexLYD" greg@271: "CsDOnTvnioqKzGXdzNQMV9tvkJEyUITyeOAjpYyAc9gxYc/GWyK2HYDF4xog6fV6h1i8FwCo" greg@271: "LK/EncwhkWGxEH9AXLMXM2H1CpQBifI3yeapZ+70d43+cSo4+95yL23g8XiGFUWp3bVrV/Ty" greg@271: "5ctZnR2ddHZ08uxzz1K9eT1GRhJls1gFlsfieK+WpJ5e/3z7pcuXzmia1rJSs3xlOg8dOvTD" greg@271: "8fHx7wQCgb4tW7bMm6b55/Pnz+eGw+FFGJDT5iT1XRWlfxHMZ06+/Vz9dCAQeG9kZKR1x44d" greg@271: "nSdPnkyuZSAArbq6eqOiKAP9/f3xlpaWgra2tlei0eiryWSyKGKa2TcaL+muwcxU5aDf9Gi+" greg@271: "L0Oh0BehUOiaZVlnAoHAzFr7Ih75bVnVb2pqcvf09Phi0ei6+/rUC6lw1k0p5bSUctThcIwP" greg@271: "Dw/HnwT4P6CDl+TMvD0JAAAAAElFTkSuQmCC") greg@271: greg@271: #---------------------------------------------------------------------- greg@271: stopicon = PyEmbeddedImage( greg@271: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAABPRJ" greg@271: "REFUSImdlllsVGUUx3/f/e4sd5iZLjNt6XSFdtgkjWFRePABDaCBGgjamIg81CU0aoxbRHww" greg@271: "+EDkhWjEB5rYGEMUxQTCJg8EoQ2BbgrFCNJWltplgC63naEzd+bO50NLLVAq4STfwz3nfP/f" greg@271: "PSf3O98VSikmmQ94HFgDLDdNM1BfX5955swZX0tLi5FKpbSSkpJkaWlpIhQKdVdVVX2XkZFx" greg@271: "EpBAEGgHLgH/iSqlUEoJpVSBUqpaKXXYNM0r1dXVt6WUajx5ylVYWKi2bdvWY1nWUaXUgQ0b" greg@271: "NtRWVFQUjmuilEKMV5ALvAhsPHDgQFlNTU2gr69Pk1JSFMphTomfRXO8+A243i/oG9I5f6mX" greg@271: "K1evAxAOhykrKxs9duyYkZ+f/0lPT8/2OwXogBtYDKzYunVr0c6dO3Ns26akMIcdbxQyv0hy" greg@271: "rwmh8Bas5/eb89nxRR1NTU20t7cb4x1ZPjlXB2YBiw8ePJhdW1tb4DEMXng6xJtrPQhxn/Y4" greg@271: "QSM12o89fJnOjst3hXRdLxZCGEqpUQANmBuJRLK3bNmy1OFwUPVMPm9VTiMOqLRNYvg6+shv" greg@271: "rFoWwutxTcSklGEpZXDiXZRSr6xbt+6dtra2xUW5Lr7c7EUD3Flhwmu/nRKQGO7CvHaCwY7D" greg@271: "WNEeEmoGe0+PWnuOXHWmrBTl5eW7GxsbNwPoFy5ccHV2di7yzjD4uMqDNtFngZDOKQHurDLc" greg@271: "WWX4Qk/ScfRVXCLGoorU8J+z5gbjxyWGYbwshPhQKTWi7d+/P9/pdIp5xR5C2Q9uS1fDp3T+" greg@271: "8jo32uomfJ7cCtzZYQCOjKhYOmgxI+hBSumdOXPmegDt4sWLxU6nk3BIf7A6EB/sIBY5R/+l" greg@271: "nyd8yrZIRnvZ02tduxVwFQOojBQAfr9/tRBC103TLHe5XMwpSEwLKFj2EWk7gRGYOyaeTtJ4" greg@271: "pnZk+7UhM5FtlAhAIMYAESd+v78IyNWBYqfTSUF2fFqAJ7firufhRFSdTg36rIDhQ6XHnAI0" greg@271: "L1iWhWEYASBLl1L+JaWcfSuqk+u3AUikRer4ADffg/w7gt80fs35r34k3BYh2xNAarooAJ4d" greg@271: "vsHgaP8EWMR17GiaVo8r0+Fw6DrQDDzXO+RgQSjBUFIlPh+wB0vLZD6TrLWrkWRXB29fGAK6" greg@271: "pql1rNXVmrCklJYGtAgh6DXHDsuuG8k+O9M5895tq+atpSwwZ9o2TjZlWTGl1IAGNEsp1c1E" greg@271: "DiMqmI7nZRQJ7j/G6xZWMS/vsYcGkEzG4vF4RDt06FBfZmbmwR/27uOD3f1aVk+BljMjD6lp" greg@271: "/DN07a4VTYw8tL4rrQZgbNixadOm90+dOvX82cZmcbaxmWBukOVrlvJudw1R1xDp8a+kuPM6" greg@271: "Gx8S4LXtCIwNO1asWDGYl5dn3gneunGLc7/+gTttoAntQRrTmgMmpimAHQwGOycnlBaX4rUz" greg@271: "8LszMRweXLr7kWB35oMdCAT+1jRt0cqVK6Otra2+hvoGGuobWPLEEsoXzkbPkLhvR4CBRwJY" greg@271: "Xq/3SGVlZbq7u7utsrJyxDTNz06cOJHZ0tRCS1MLAKuRwNQT9v8AyV27dn1fXl7eqmlae11d" greg@271: "XXLfvn0/+Xy+l6LR6Gu2befFYjFfzrk2FzeHp7mK7jdxz2/LffGamhpvc3NzyLKsbFd3z1PG" greg@271: "aHyBTKdjum0POGzbFAp7qo0xVOtJZdf/C/wRDnL5FYGSAAAAAElFTkSuQmCC") greg@271: greg@343: class ParamsEntryDialog(wx.TextEntryDialog): greg@343: if wx.VERSION < (2, 6, 0): greg@343: def Bind(self, event, function, id = None): greg@343: if id is not None: greg@343: event(self, id, function) greg@343: else: greg@343: event(self, function) greg@343: greg@343: greg@343: def __init__(self, parent, message, caption = "Please enter text", defaultValue = "", greg@343: style = wx.OK|wx.CANCEL|wx.CENTRE, pos = wx.DefaultPosition): greg@343: wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos) greg@343: greg@343: self.Tests = [] greg@343: if wx.VERSION >= (2, 8, 0): greg@343: self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId()) greg@343: elif wx.VERSION >= (2, 6, 0): greg@343: self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetAffirmativeButton().GetId()) greg@271: else: greg@343: self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetSizer().GetItem(3).GetSizer().GetChildren()[0].GetSizer().GetChildren()[0].GetWindow().GetId()) greg@343: greg@343: def OnOK(self, event): greg@343: value = self.GetValue() greg@343: texts = {"value" : value} greg@343: for function, message in self.Tests: greg@343: if not function(value): laurent@361: message = wx.MessageDialog(self, message%texts, _("Error"), wx.OK|wx.ICON_ERROR) greg@343: message.ShowModal() greg@343: message.Destroy() greg@343: return greg@343: self.EndModal(wx.ID_OK) greg@343: event.Skip() greg@343: greg@343: def GetValue(self): greg@343: return self.GetSizer().GetItem(1).GetWindow().GetValue() greg@343: greg@343: def SetTests(self, tests): greg@343: self.Tests = tests greg@343: greg@343: class BeremizTaskBarIcon(wx.TaskBarIcon): greg@343: TBMENU_START = wx.NewId() greg@343: TBMENU_STOP = wx.NewId() greg@343: TBMENU_CHANGE_NAME = wx.NewId() greg@343: TBMENU_CHANGE_PORT = wx.NewId() greg@343: TBMENU_CHANGE_INTERFACE = wx.NewId() greg@343: TBMENU_LIVE_SHELL = wx.NewId() greg@343: TBMENU_WXINSPECTOR = wx.NewId() greg@343: TBMENU_CHANGE_WD = wx.NewId() greg@343: TBMENU_QUIT = wx.NewId() greg@343: greg@343: def __init__(self, pyroserver): greg@343: wx.TaskBarIcon.__init__(self) greg@343: self.pyroserver = pyroserver greg@343: # Set the image greg@343: self.UpdateIcon(None) greg@271: greg@343: # bind some events greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarStartPLC, id=self.TBMENU_START) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarStopPLC, id=self.TBMENU_STOP) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarChangeName, id=self.TBMENU_CHANGE_NAME) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarChangeInterface, id=self.TBMENU_CHANGE_INTERFACE) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD) greg@343: self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT) greg@343: greg@343: def CreatePopupMenu(self): greg@343: """ greg@343: This method is called by the base class when it needs to popup greg@343: the menu for the default EVT_RIGHT_DOWN event. Just create greg@343: the menu how you want it and return it from this function, greg@343: the base class takes care of the rest. greg@343: """ greg@343: menu = wx.Menu() laurent@361: menu.Append(self.TBMENU_START, _("Start PLC")) laurent@361: menu.Append(self.TBMENU_STOP, _("Stop PLC")) laurent@361: menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name")) laurent@361: menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind")) laurent@361: menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell")) laurent@361: menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector")) laurent@361: menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number")) greg@343: menu.AppendSeparator() laurent@361: menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory")) laurent@361: menu.Append(self.TBMENU_QUIT, _("Quit")) greg@343: return menu greg@343: greg@343: def MakeIcon(self, img): greg@343: """ greg@343: The various platforms have different requirements for the greg@343: icon size... greg@343: """ greg@343: if "wxMSW" in wx.PlatformInfo: greg@343: img = img.Scale(16, 16) greg@343: elif "wxGTK" in wx.PlatformInfo: greg@343: img = img.Scale(22, 22) greg@343: # wxMac can be any size upto 128x128, so leave the source img alone.... greg@343: icon = wx.IconFromBitmap(img.ConvertToBitmap() ) greg@343: return icon greg@343: greg@343: def OnTaskBarStartPLC(self, evt): greg@343: if self.pyroserver.plcobj is not None: greg@343: self.pyroserver.plcobj.StartPLC() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarStopPLC(self, evt): greg@343: if self.pyroserver.plcobj is not None: greg@343: self.pyroserver.plcobj.StopPLC() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarChangeInterface(self, evt): laurent@361: dlg = ParamsEntryDialog(None, _("Enter the ip of the interface to bind"), defaultValue=self.pyroserver.ip) laurent@361: dlg.SetTests([(re.compile('\d{1,3}(?:\.\d{1,3}){3}$').match, _("Ip is not valid!")), laurent@361: ( lambda ip :len([x for x in ip.split(".") if 0 <= int(x) <= 255]) == 4, _("Ip is not valid!")) greg@343: ]) greg@343: if dlg.ShowModal() == wx.ID_OK: greg@343: self.pyroserver.ip = dlg.GetValue() greg@343: self.pyroserver.Stop() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarChangePort(self, evt): laurent@361: dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port)) laurent@361: dlg.SetTests([(UnicodeType.isdigit, _("Port number must be an integer!")), (lambda port : 0 <= int(port) <= 65535 , _("Port number must be 0 <= port <= 65535!"))]) greg@343: if dlg.ShowModal() == wx.ID_OK: greg@343: self.pyroserver.port = int(dlg.GetValue()) greg@343: self.pyroserver.Stop() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarChangeWorkingDir(self, evt): laurent@361: dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON) greg@343: if dlg.ShowModal() == wx.ID_OK: greg@343: self.pyroserver.workdir = dlg.GetPath() greg@343: self.pyroserver.Stop() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarChangeName(self, evt): laurent@361: dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=self.pyroserver.name) laurent@361: dlg.SetTests([(lambda name : len(name) is not 0 , _("Name must not be null!"))]) greg@343: if dlg.ShowModal() == wx.ID_OK: greg@343: self.pyroserver.name = dlg.GetValue() greg@343: self.pyroserver.Restart() greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarLiveShell(self, evt): greg@343: if self.pyroserver.plcobj is not None and self.pyroserver.plcobj.python_threads_vars is not None: greg@343: from wx import py greg@343: #frame = py.shell.ShellFrame(locals=self.pyroserver.plcobj.python_threads_vars) greg@343: frame = py.crust.CrustFrame(locals=self.pyroserver.plcobj.python_threads_vars) greg@343: frame.Show() greg@343: else: laurent@361: wx.MessageBox(_("No runnning PLC"), _("Error")) greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarWXInspector(self, evt): greg@343: # Activate the widget inspection tool greg@343: from wx.lib.inspection import InspectionTool greg@343: if not InspectionTool().initialized: greg@343: InspectionTool().Init(locals=self.pyroserver.plcobj.python_threads_vars) greg@271: greg@343: # Find a widget to be selected in the tree. Use either the greg@343: # one under the cursor, if any, or this frame. greg@343: wnd = wx.FindWindowAtPointer() greg@343: if not wnd: greg@343: wnd = wx.GetApp() greg@343: InspectionTool().Show(wnd, True) greg@343: evt.Skip() greg@343: greg@343: def OnTaskBarQuit(self, evt): greg@343: self.pyroserver.Quit() greg@343: self.RemoveIcon() greg@343: wx.CallAfter(wx.GetApp().Exit) greg@343: evt.Skip() greg@343: greg@343: def UpdateIcon(self, plcstatus): greg@343: if plcstatus is "Started" : greg@343: currenticon = self.MakeIcon(starticon.GetImage()) greg@343: elif plcstatus is "Stopped": greg@343: currenticon = self.MakeIcon(stopicon.GetImage()) greg@343: else: greg@343: currenticon = self.MakeIcon(defaulticon.GetImage()) greg@343: self.SetIcon(currenticon, "Beremiz Service") greg@343: greg@343: from runtime import PLCObject, PLCprint, ServicePublisher greg@262: import Pyro.core as pyro greg@262: greg@262: if not os.path.isdir(WorkingDir): greg@262: os.mkdir(WorkingDir) greg@262: etisserant@319: def default_evaluator(callable, *args, **kwargs): etisserant@319: return callable(*args,**kwargs) etisserant@319: greg@262: class Server(): laurent@368: def __init__(self, name, ip, port, workdir, argv, autostart=False, statuschange=None, evaluator=default_evaluator, website=None): greg@262: self.continueloop = True greg@262: self.daemon = None greg@262: self.name = name greg@262: self.ip = ip greg@262: self.port = port greg@262: self.workdir = workdir etisserant@301: self.argv = argv greg@262: self.plcobj = None greg@263: self.servicepublisher = None greg@330: self.autostart = autostart greg@269: self.statuschange = statuschange etisserant@301: self.evaluator = evaluator laurent@368: self.website = website etisserant@301: etisserant@301: def Loop(self): greg@262: while self.continueloop: greg@262: self.Start() greg@262: greg@262: def Restart(self): greg@262: self.Stop() greg@262: greg@262: def Quit(self): greg@262: self.continueloop = False greg@262: self.Stop() greg@262: greg@262: def Start(self): greg@262: pyro.initServer() greg@262: self.daemon=pyro.Daemon(host=self.ip, port=self.port) laurent@368: self.plcobj = PLCObject(self.workdir, self.daemon, self.argv, self.statuschange, self.evaluator, self.website) greg@262: uri = self.daemon.connect(self.plcobj,"PLCObject") greg@262: greg@262: print "The daemon runs on port :",self.port greg@262: print "The object's uri is :",uri greg@262: print "The working directory :",self.workdir greg@262: greg@262: # Configure and publish service greg@262: # Not publish service if localhost in address params greg@262: if self.ip != "localhost" and self.ip != "127.0.0.1": greg@263: print "Publish service on local network" greg@330: self.servicepublisher = ServicePublisher.ServicePublisher() greg@262: self.servicepublisher.RegisterService(self.name, self.ip, self.port) greg@262: greg@330: if self.autostart: greg@330: self.plcobj.StartPLC() greg@330: greg@262: sys.stdout.flush() greg@262: greg@262: self.daemon.requestLoop() greg@262: greg@262: def Stop(self): greg@330: self.plcobj.StopPLC() greg@263: if self.servicepublisher is not None: greg@263: self.servicepublisher.UnRegisterService() greg@263: del self.servicepublisher greg@262: self.daemon.shutdown(True) laurent@368: laurent@368: if enabletwisted: laurent@368: try: laurent@368: if havewx: laurent@368: from twisted.internet import wxreactor laurent@368: wxreactor.install() laurent@368: from twisted.internet import reactor, task laurent@368: from twisted.python import log, util laurent@368: from nevow import rend, appserver, inevow, tags, loaders, athena laurent@368: from nevow.page import renderer laurent@368: laurent@368: havetwisted = True laurent@368: except: laurent@368: havetwisted = False laurent@368: laurent@368: if havetwisted: laurent@368: laurent@368: xhtml_header = ''' laurent@368: laurent@368: ''' laurent@368: laurent@381: class PLCHMI(athena.LiveElement): laurent@381: laurent@381: initialised = False laurent@381: laurent@381: def HMIinitialised(self, result): laurent@381: self.initialised = True laurent@381: laurent@381: def HMIinitialisation(self): laurent@381: self.HMIinitialised(None) laurent@381: laurent@381: class DefaultPLCStartedHMI(PLCHMI): laurent@368: docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[ laurent@368: tags.h1["PLC IS NOW STARTED"], laurent@368: ]) laurent@381: laurent@381: class PLCStoppedHMI(PLCHMI): laurent@368: docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[ laurent@396: tags.h1["PLC IS STOPPED"], laurent@368: ]) laurent@396: laurent@368: class MainPage(athena.LiveElement): laurent@368: jsClass = u"WebInterface.PLC" laurent@368: docFactory = loaders.stan(tags.div(render=tags.directive('liveElement'))[ laurent@368: tags.div(id='content')[ laurent@368: tags.div(render = tags.directive('PLCElement')), laurent@368: ]]) laurent@368: laurent@368: def __init__(self, *a, **kw): laurent@368: athena.LiveElement.__init__(self, *a, **kw) laurent@368: self.pcl_state = False laurent@368: self.HMI = None laurent@368: self.resetPLCStartedHMI() laurent@368: laurent@368: def setPLCState(self, state): laurent@368: self.pcl_state = state laurent@368: if self.HMI is not None: laurent@368: self.callRemote('updateHMI') laurent@368: laurent@368: def setPLCStartedHMI(self, hmi): laurent@368: self.PLCStartedHMIClass = hmi laurent@368: laurent@368: def resetPLCStartedHMI(self): laurent@368: self.PLCStartedHMIClass = DefaultPLCStartedHMI laurent@368: laurent@368: def getHMI(self): laurent@368: return self.HMI laurent@368: laurent@368: def HMIexec(self, function, *args, **kwargs): laurent@368: if self.HMI is not None: laurent@368: getattr(self.HMI, function, lambda:None)(*args, **kwargs) laurent@369: athena.expose(HMIexec) laurent@368: laurent@368: def resetHMI(self): laurent@368: self.HMI = None laurent@368: laurent@368: def PLCElement(self, ctx, data): laurent@368: return self.getPLCElement() laurent@368: renderer(PLCElement) laurent@368: laurent@368: def getPLCElement(self): laurent@368: self.detachFragmentChildren() laurent@368: if self.pcl_state: laurent@368: f = self.PLCStartedHMIClass() laurent@368: else: laurent@368: f = PLCStoppedHMI() laurent@369: f.setFragmentParent(self) laurent@368: self.HMI = f laurent@368: return f laurent@368: athena.expose(getPLCElement) laurent@368: laurent@368: def detachFragmentChildren(self): laurent@368: for child in self.liveFragmentChildren[:]: laurent@368: child.detach() laurent@396: laurent@368: class WebInterface(athena.LivePage): laurent@368: laurent@368: docFactory = loaders.stan([tags.raw(xhtml_header), laurent@368: tags.html(xmlns="http://www.w3.org/1999/xhtml")[ laurent@368: tags.head(render=tags.directive('liveglue')), laurent@368: tags.body[ laurent@368: tags.div[ laurent@368: tags.div( render = tags.directive( "MainPage" )) laurent@368: ]]]]) laurent@368: MainPage = MainPage() laurent@381: PLCHMI = PLCHMI laurent@396: laurent@368: def __init__(self, plcState=False, *a, **kw): laurent@368: super(WebInterface, self).__init__(*a, **kw) laurent@368: self.jsModules.mapping[u'WebInterface'] = util.sibpath(__file__, 'webinterface.js') laurent@368: self.plcState = plcState laurent@368: self.MainPage.setPLCState(plcState) laurent@368: laurent@368: def getHMI(self): laurent@368: return self.MainPage.getHMI() laurent@368: laurent@369: def LoadHMI(self, hmi, jsmodules): laurent@368: for name, path in jsmodules.iteritems(): laurent@368: self.jsModules.mapping[name] = os.path.join(WorkingDir, path) laurent@369: self.MainPage.setPLCStartedHMI(hmi) laurent@368: laurent@368: def UnLoadHMI(self): laurent@368: self.MainPage.resetPLCStartedHMI() laurent@368: laurent@368: def PLCStarted(self): laurent@368: self.plcState = True laurent@368: self.MainPage.setPLCState(True) laurent@368: laurent@368: def PLCStopped(self): laurent@368: self.plcState = False laurent@368: self.MainPage.setPLCState(False) laurent@368: laurent@368: def renderHTTP(self, ctx): laurent@368: """ laurent@368: Force content type to fit with SVG laurent@368: """ laurent@368: req = inevow.IRequest(ctx) laurent@368: req.setHeader('Content-type', 'application/xhtml+xml') laurent@368: return super(WebInterface, self).renderHTTP(ctx) laurent@368: laurent@368: def render_MainPage(self, ctx, data): laurent@368: f = self.MainPage laurent@368: f.setFragmentParent(self) laurent@368: return ctx.tag[f] laurent@368: laurent@368: def child_(self, ctx): laurent@368: self.MainPage.detachFragmentChildren() laurent@368: return WebInterface(plcState=self.plcState) laurent@368: laurent@368: def beforeRender(self, ctx): laurent@368: d = self.notifyOnDisconnect() laurent@368: d.addErrback(self.disconnected) laurent@368: laurent@368: def disconnected(self, reason): laurent@368: self.MainPage.resetHMI() laurent@368: #print reason laurent@368: #print "We will be called back when the client disconnects" laurent@396: laurent@368: if havewx: laurent@368: reactor.registerWxApp(app) laurent@368: res = WebInterface() laurent@368: site = appserver.NevowSite(res) laurent@368: reactor.listenTCP(8009, site) laurent@368: else: laurent@368: res = None greg@262: greg@262: if havewx: etisserant@301: from threading import Semaphore etisserant@301: wx_eval_lock = Semaphore(0) greg@330: mythread = currentThread() greg@330: greg@269: def statuschange(status): greg@269: wx.CallAfter(taskbar_instance.UpdateIcon,status) etisserant@301: etisserant@301: eval_res = None etisserant@301: def wx_evaluator(callable, *args, **kwargs): etisserant@301: global eval_res greg@343: try: greg@343: eval_res=callable(*args,**kwargs) greg@343: except Exception,e: greg@343: PLCprint("#EXCEPTION : "+str(e)) greg@343: finally: greg@343: wx_eval_lock.release() etisserant@301: etisserant@301: def evaluator(callable, *args, **kwargs): greg@330: # call directly the callable function if call from the wx mainloop (avoid dead lock) greg@330: if(mythread == currentThread()): greg@330: callable(*args,**kwargs) greg@330: else: greg@330: wx.CallAfter(wx_evaluator,callable,*args,**kwargs) greg@330: wx_eval_lock.acquire() etisserant@301: return eval_res etisserant@301: laurent@368: pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, statuschange, evaluator, res) etisserant@301: taskbar_instance = BeremizTaskBarIcon(pyroserver) etisserant@301: etisserant@301: pyro_thread=Thread(target=pyroserver.Loop) greg@262: pyro_thread.start() laurent@368: else: laurent@368: pyroserver = Server(name, ip, port, WorkingDir, argv, autostart, website=res) laurent@368: laurent@368: if havetwisted: laurent@368: reactor.run() laurent@368: elif havewx: greg@262: app.MainLoop() greg@262: else: greg@262: pyroserver.Loop()