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