Edouard@641: #!/usr/bin/env python Edouard@641: # -*- coding: utf-8 -*- Edouard@641: andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. Edouard@641: # andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1680: # Copyright (C) 2017: Andrey Skvortsov Edouard@641: # andrej@1571: # See COPYING file for copyrights details. Edouard@641: # andrej@1571: # This program is free software; you can redistribute it and/or andrej@1571: # modify it under the terms of the GNU General Public License andrej@1571: # as published by the Free Software Foundation; either version 2 andrej@1571: # of the License, or (at your option) any later version. Edouard@641: # andrej@1571: # This program is distributed in the hope that it will be useful, andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1571: # GNU General Public License for more details. Edouard@641: # andrej@1571: # You should have received a copy of the GNU General Public License andrej@1571: # along with this program; if not, write to the Free Software andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Edouard@641: andrej@1826: andrej@1881: from __future__ import absolute_import andrej@1826: from __future__ import print_function andrej@1732: import os andrej@1732: import sys andrej@1732: import getopt andrej@1783: import threading Edouard@2644: import shlex Edouard@3282: import traceback Edouard@3420: import threading Edouard@2484: from threading import Thread, Semaphore, Lock, currentThread andrej@2450: from builtins import str as text andrej@2443: from past.builtins import execfile andrej@2431: from six.moves import builtins Edouard@2270: Edouard@2270: import runtime Edouard@2476: from runtime.PyroServer import PyroServer Edouard@2000: from runtime.xenomai import TryPreloadXenomai Edouard@2270: from runtime import LogMessageAndException andrej@2416: from runtime import PlcStatus Edouard@2585: from runtime import default_evaluator edouard@2492: from runtime.Stunnel import ensurePSK andrej@1783: import util.paths as paths Edouard@641: Edouard@3420: # In case system time is ajusted, it is better to use Edouard@3420: # monotonic timers for timers and other timeout based operations. Edouard@3420: # hot-patch threading module to force using monitonic time for all Edouard@3420: # Thread/Timer/Event/Condition Edouard@3420: Edouard@3420: from runtime.monotonic_time import monotonic Edouard@3420: threading._time = monotonic Edouard@3420: Edouard@2644: try: Edouard@2644: from runtime.spawn_subprocess import Popen Edouard@2644: except ImportError: Edouard@2644: from subprocess import Popen andrej@1736: andrej@2294: def version(): andrej@2294: from version import app_version andrej@2294: print("Beremiz_service: ", app_version) andrej@2294: andrej@2294: Edouard@641: def usage(): andrej@2294: version() andrej@1826: 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@2323: -n service name (default:None, zeroconf discovery disabled) Edouard@2221: -i IP address of interface to bind to (default:localhost) Edouard@2221: -p port number default:3000 Edouard@2221: -h print this help text and quit Edouard@2221: -a autostart PLC (0:disable 1:enable) (default:0) Edouard@2221: -x enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1) Edouard@2221: -t enable/disable Twisted web interface (0:disable 1:enable) (default:1) Edouard@2221: -w web server port or "off" to disable web server (default:8009) Edouard@2221: -c WAMP client config file (can be overriden by wampconf.json in project) Edouard@2323: -s PSK secret path (default:PSK disabled) Edouard@2221: -e python extension (absolute path .py) Edouard@1434: Edouard@641: working_dir - directory where are stored PLC files andrej@1826: """ % sys.argv[0]) Edouard@641: andrej@1749: Edouard@641: try: Edouard@2644: opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h", ["help", "version", "status-change=", "on-plc-start=", "on-plc-stop="]) yegorslists@2303: except getopt.GetoptError as err: Edouard@641: # print help information and exit: andrej@1826: 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@2311: interface = '' Edouard@641: port = 3000 Edouard@1439: webport = 8009 Edouard@2324: PSKpath = None Edouard@1893: wampconf = None 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: andrej@1742: extensions = [] Edouard@2644: statuschange = [] Edouard@2644: def status_change_call_factory(wanted, args): Edouard@2644: def status_change_call(status): Edouard@2644: if wanted is None or status is wanted: Edouard@2644: cmd = shlex.split(args.format(status)) Edouard@2644: Popen(cmd) Edouard@2644: return status_change_call Edouard@1437: Edouard@641: for o, a in opts: andrej@2294: if o == "-h" or o == "--help": Edouard@641: usage() Edouard@641: sys.exit() andrej@2294: if o == "--version": andrej@2294: version() andrej@2294: sys.exit() Edouard@2644: if o == "--on-plc-start": Edouard@2644: statuschange.append(status_change_call_factory(PlcStatus.Started, a)) Edouard@2644: elif o == "--on-plc-stop": Edouard@2644: statuschange.append(status_change_call_factory(PlcStatus.Stopped, a)) Edouard@2644: elif o == "--status-change": Edouard@2644: statuschange.append(status_change_call_factory(None, a)) Edouard@641: elif o == "-i": Edouard@2311: if len(a.split(".")) == 4: Edouard@2311: interface = a Edouard@2311: elif a == "localhost": Edouard@2311: interface = '127.0.0.1' 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@1439: elif o == "-w": Edouard@1439: webport = None if a == "off" else int(a) Edouard@1439: elif o == "-c": Edouard@1439: wampconf = None if a == "off" else a Edouard@1893: elif o == "-s": Edouard@2324: PSKpath = None if a == "off" else a Edouard@1437: elif o == "-e": Edouard@1955: fnameanddirname = list(os.path.split(os.path.realpath(a))) Edouard@1955: fnameanddirname.reverse() Edouard@1955: extensions.append(fnameanddirname) Edouard@641: else: Edouard@641: usage() Edouard@641: sys.exit() Edouard@641: andrej@1783: andrej@1680: beremiz_dir = paths.AbsDir(__file__) Edouard@1434: 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() andrej@1742: argv = [WorkingDir] Edouard@641: Edouard@2459: builtins.__dict__['_'] = lambda x: x Edouard@2319: # TODO: add a cmdline parameter if Trying Preloading Xenomai makes problem Edouard@2319: TryPreloadXenomai() Edouard@2319: version() Edouard@641: andrej@1736: andrej@1595: def Bpath(*args): andrej@1740: return os.path.join(beremiz_dir, *args) andrej@1595: andrej@1736: andrej@1595: def SetupI18n(): andrej@1595: # Get folder containing translation files andrej@1740: localedir = os.path.join(beremiz_dir, "locale") andrej@1595: # Get the default language andrej@1595: langid = wx.LANGUAGE_DEFAULT andrej@1595: # Define translation domain (name of translation files) andrej@1595: domain = "Beremiz" andrej@1595: andrej@1595: # Define locale for wx andrej@2431: loc = builtins.__dict__.get('loc', None) andrej@1595: if loc is None: andrej@2349: wx.LogGui.EnableLogging(False) andrej@1595: loc = wx.Locale(langid) andrej@2349: wx.LogGui.EnableLogging(True) andrej@2431: builtins.__dict__['loc'] = loc andrej@1595: # Define location for searching translation files andrej@1595: loc.AddCatalogLookupPathPrefix(localedir) andrej@1595: # Define locale domain andrej@1595: loc.AddCatalog(domain) andrej@1595: andrej@1595: import locale andrej@1595: global default_locale andrej@1595: default_locale = locale.getdefaultlocale()[1] andrej@1595: andrej@1595: # sys.stdout.encoding = default_locale andrej@1595: # if Beremiz_service is started from Beremiz IDE andrej@1595: # sys.stdout.encoding is None (that means 'ascii' encoding'). andrej@1595: # And unicode string returned by wx.GetTranslation() are andrej@1595: # automatically converted to 'ascii' string. andrej@1595: def unicode_translation(message): andrej@1595: return wx.GetTranslation(message).encode(default_locale) andrej@1595: Edouard@2459: builtins.__dict__['_'] = unicode_translation Edouard@2459: # builtins.__dict__['_'] = wx.GetTranslation andrej@1595: andrej@1749: Edouard@1919: # Life is hard... have a candy. Edouard@1919: # pylint: disable=wrong-import-position,wrong-import-order Edouard@641: if enablewx: Edouard@641: try: Edouard@1451: import wx Edouard@641: havewx = True andrej@1780: except ImportError: andrej@1826: print("Wx unavailable !") Edouard@641: havewx = False Edouard@641: Edouard@641: if havewx: Edouard@1451: import re edouard@3307: import wx.adv andrej@1590: andrej@1590: if wx.VERSION >= (3, 0, 0): andrej@1590: app = wx.App(redirect=False) andrej@1590: else: andrej@1590: app = wx.PySimpleApp(redirect=False) andrej@1590: app.SetTopWindow(wx.Frame(None, -1)) Edouard@1434: andrej@1595: default_locale = None andrej@1595: SetupI18n() Edouard@1434: Edouard@1067: defaulticon = wx.Image(Bpath("images", "brz.png")) Edouard@1067: starticon = wx.Image(Bpath("images", "icoplay24.png")) Edouard@1067: stopicon = wx.Image(Bpath("images", "icostop24.png")) Edouard@1434: Edouard@641: class ParamsEntryDialog(wx.TextEntryDialog): Edouard@1434: andrej@1744: def __init__(self, parent, message, caption=_("Please enter text"), defaultValue="", andrej@1767: style=wx.OK | wx.CANCEL | wx.CENTRE, pos=wx.DefaultPosition): Edouard@641: wx.TextEntryDialog.__init__(self, parent, message, caption, defaultValue, style, pos) Edouard@1434: Edouard@641: self.Tests = [] andrej@2177: self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId()) Edouard@1434: Edouard@641: def OnOK(self, event): Edouard@641: value = self.GetValue() andrej@1739: texts = {"value": value} Edouard@641: for function, message in self.Tests: Edouard@641: if not function(value): andrej@1745: 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@1434: Edouard@641: def GetValue(self): Edouard@641: return self.GetSizer().GetItem(1).GetWindow().GetValue() Edouard@1434: Edouard@641: def SetTests(self, tests): Edouard@641: self.Tests = tests Edouard@1434: edouard@3307: class BeremizTaskBarIcon(wx.adv.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@1434: Edouard@2270: def __init__(self, pyroserver): edouard@3307: wx.adv.TaskBarIcon.__init__(self) Edouard@641: self.pyroserver = pyroserver Edouard@641: # Set the image Edouard@641: self.UpdateIcon(None) Edouard@1434: 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@1434: 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@2270: menu.AppendSeparator() Edouard@2270: menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name")) Edouard@2270: menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind")) Edouard@2270: menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number")) Edouard@2270: menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory")) Edouard@2270: menu.AppendSeparator() Edouard@2270: menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell")) Edouard@2270: 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@1434: 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@3307: icon = wx.Icon(img.ConvertToBitmap()) Edouard@641: return icon Edouard@1434: Edouard@641: def OnTaskBarStartPLC(self, evt): Edouard@2270: runtime.GetPLCObjectSingleton().StartPLC() Edouard@1434: Edouard@641: def OnTaskBarStopPLC(self, evt): Edouard@2270: runtime.GetPLCObjectSingleton().StopPLC() Edouard@1434: Edouard@641: def OnTaskBarChangeInterface(self, evt): andrej@1593: ip_addr = self.pyroserver.ip_addr andrej@1593: ip_addr = '' if ip_addr is None else ip_addr andrej@1730: dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=ip_addr) andrej@2439: dlg.SetTests([(re.compile(r'\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")), andrej@1767: (lambda x:len([x for x in x.split(".") if 0 <= int(x) <= 255]) == 4, andrej@1773: _("IP is not valid!"))]) Edouard@641: if dlg.ShowModal() == wx.ID_OK: Edouard@644: self.pyroserver.ip_addr = dlg.GetValue() Edouard@1887: self.pyroserver.Restart() Edouard@1434: Edouard@641: def OnTaskBarChangePort(self, evt): Edouard@641: dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port)) andrej@2450: dlg.SetTests([(text.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@1887: self.pyroserver.Restart() Edouard@1434: 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@1887: self.pyroserver.Restart() Edouard@1434: Edouard@641: def OnTaskBarChangeName(self, evt): Edouard@2321: _servicename = self.pyroserver.servicename Edouard@2321: _servicename = '' if _servicename is None else _servicename Edouard@2321: dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=_servicename) andrej@1739: dlg.SetTests([(lambda name: len(name) is not 0, _("Name must not be null!"))]) Edouard@641: if dlg.ShowModal() == wx.ID_OK: andrej@1594: self.pyroserver.servicename = dlg.GetValue() Edouard@641: self.pyroserver.Restart() Edouard@1434: Edouard@959: def _LiveShellLocals(self): Edouard@2270: return {"locals": runtime.GetPLCObjectSingleton().python_runtime_vars} Edouard@1434: 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@1434: 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@1434: Edouard@641: def OnTaskBarQuit(self, evt): Laurent@1121: if wx.Platform == '__WXMSW__': Laurent@1121: Thread(target=self.pyroserver.Quit).start() Edouard@641: self.RemoveIcon() Laurent@1121: wx.CallAfter(wx.GetApp().ExitMainLoop) Edouard@1434: Edouard@641: def UpdateIcon(self, plcstatus): andrej@2416: if plcstatus is PlcStatus.Started: Edouard@1067: currenticon = self.MakeIcon(starticon) andrej@2416: elif plcstatus is PlcStatus.Stopped: Edouard@1067: currenticon = self.MakeIcon(stopicon) Edouard@641: else: Edouard@1067: currenticon = self.MakeIcon(defaulticon) Edouard@641: self.SetIcon(currenticon, "Beremiz Service") Edouard@641: Edouard@641: Edouard@641: if not os.path.isdir(WorkingDir): Edouard@641: os.mkdir(WorkingDir) Edouard@641: andrej@1736: Edouard@641: if enabletwisted: Edouard@641: import warnings Edouard@641: with warnings.catch_warnings(): Edouard@641: warnings.simplefilter("ignore") Edouard@641: try: Edouard@641: if havewx: Edouard@641: from twisted.internet import wxreactor Edouard@641: wxreactor.install() Edouard@1438: from twisted.internet import reactor Edouard@1434: Edouard@641: havetwisted = True andrej@1780: except ImportError: andrej@1826: print(_("Twisted unavailable.")) Edouard@641: havetwisted = False Edouard@641: Edouard@1438: pyruntimevars = {} Edouard@1438: Edouard@641: if havetwisted: Edouard@641: if havewx: Edouard@641: reactor.registerWxApp(app) Edouard@1438: Edouard@2484: twisted_reactor_thread_id = None edouard@2492: ui_thread = None Edouard@2484: Edouard@641: if havewx: Edouard@641: wx_eval_lock = Semaphore(0) Edouard@835: Edouard@1438: def statuschangeTskBar(status): andrej@1740: wx.CallAfter(taskbar_instance.UpdateIcon, status) Edouard@1434: Edouard@1438: statuschange.append(statuschangeTskBar) Edouard@1438: Edouard@867: def wx_evaluator(obj, *args, **kwargs): andrej@1740: tocall, args, kwargs = obj.call Edouard@867: obj.res = default_evaluator(tocall, *args, **kwargs) Edouard@867: wx_eval_lock.release() Edouard@1434: Edouard@867: def evaluator(tocall, *args, **kwargs): Edouard@2484: # To prevent deadlocks, check if current thread is not one of the UI Edouard@2484: # UI threads can be either the one from WX main loop or Edouard@2484: # worker thread from twisted "threadselect" reactor Edouard@2484: current_id = currentThread().ident Edouard@2489: Edouard@2484: if ui_thread is not None \ Edouard@2484: and ui_thread.ident != current_id \ Edouard@2484: and (not havetwisted or ( edouard@2492: twisted_reactor_thread_id is not None edouard@2492: and twisted_reactor_thread_id != current_id)): Edouard@2484: Edouard@2484: o = type('', (object,), dict(call=(tocall, args, kwargs), res=None)) Edouard@2484: wx.CallAfter(wx_evaluator, o) Edouard@2484: wx_eval_lock.acquire() Edouard@2484: return o.res Edouard@2484: else: Edouard@2484: # avoid dead lock if called from the wx mainloop Edouard@2484: return default_evaluator(tocall, *args, **kwargs) Edouard@641: else: Edouard@2270: evaluator = default_evaluator Edouard@641: Edouard@1916: # Exception hooks andrej@1736: Edouard@1906: Edouard@1067: def LogException(*exp): Edouard@1919: LogMessageAndException("", exp) andrej@1749: Edouard@1955: Edouard@1067: sys.excepthook = LogException andrej@1736: andrej@1736: Edouard@1067: def installThreadExcepthook(): Edouard@1067: init_old = threading.Thread.__init__ andrej@1750: Edouard@1067: def init(self, *args, **kwargs): Edouard@1067: init_old(self, *args, **kwargs) Edouard@1067: run_old = self.run andrej@1750: Edouard@1067: def run_with_except_hook(*args, **kw): Edouard@1067: try: Edouard@1067: run_old(*args, **kw) Edouard@1067: except (KeyboardInterrupt, SystemExit): Edouard@1067: raise andrej@1780: except Exception: Edouard@1067: sys.excepthook(*sys.exc_info()) Edouard@1067: self.run = run_with_except_hook Edouard@1067: threading.Thread.__init__ = init andrej@1749: andrej@1749: Edouard@1067: installThreadExcepthook() Edouard@2222: havewamp = False Edouard@1067: Edouard@1446: if havetwisted: andrej@1739: if webport is not None: Edouard@1446: try: andrej@1872: import runtime.NevowServer as NS # pylint: disable=ungrouped-imports Edouard@2669: NS.WorkingDir = WorkingDir Edouard@2216: except Exception: Edouard@2213: LogMessageAndException(_("Nevow/Athena import failed :")) Edouard@1446: webport = None Edouard@2669: Edouard@2220: try: Edouard@2220: import runtime.WampClient as WC # pylint: disable=ungrouped-imports Edouard@2220: WC.WorkingDir = WorkingDir Edouard@2222: havewamp = True Edouard@2220: except Exception: Edouard@2220: LogMessageAndException(_("WAMP import failed :")) Edouard@1446: Edouard@1437: # Load extensions Edouard@1905: for extention_file, extension_folder in extensions: Edouard@1437: sys.path.append(extension_folder) Edouard@1905: execfile(os.path.join(extension_folder, extention_file), locals()) Edouard@1437: Edouard@2323: # Service name is used as an ID for stunnel's PSK Edouard@2323: # Some extension may set 'servicename' to a computed ID or Serial Number Edouard@2323: # instead of using commandline '-n' Edouard@2324: if servicename is not None and PSKpath is not None: Edouard@2324: ensurePSK(servicename, PSKpath) Edouard@2270: Edouard@2270: runtime.CreatePLCObjectSingleton( Edouard@2270: WorkingDir, argv, statuschange, evaluator, pyruntimevars) Edouard@2270: Edouard@2476: pyroserver = PyroServer(servicename, interface, port) Edouard@2270: Edouard@2270: if havewx: Edouard@2270: taskbar_instance = BeremizTaskBarIcon(pyroserver) Edouard@2270: Edouard@1446: if havetwisted: andrej@1739: if webport is not None: Edouard@1446: try: Edouard@2311: website = NS.RegisterWebsite(interface, webport) Edouard@1446: pyruntimevars["website"] = website Edouard@1446: statuschange.append(NS.website_statuslistener_factory(website)) Edouard@1906: except Exception: Edouard@1906: LogMessageAndException(_("Nevow Web service failed. ")) Edouard@1446: Edouard@2222: if havewamp: Edouard@2222: try: Edouard@2324: WC.RegisterWampClient(wampconf, PSKpath) Edouard@2222: WC.RegisterWebSettings(NS) Edouard@2222: except Exception: Edouard@2222: LogMessageAndException(_("WAMP client startup failed. ")) Edouard@1446: Edouard@2738: def FirstWorkerJob(): Edouard@2738: """ Edouard@2738: RPC through pyro/wamp/UI may lead to delegation to Worker, Edouard@2738: then this function ensures that Worker is already Edouard@2738: created when pyro starts Edouard@2738: """ Edouard@2738: global pyro_thread, pyroserver, ui_thread, reactor, twisted_reactor_thread_id Edouard@2738: Edouard@2738: pyro_thread_started = Lock() Edouard@2738: pyro_thread_started.acquire() Edouard@2738: pyro_thread = Thread(target=pyroserver.PyroLoop, Edouard@2738: kwargs=dict(when_ready=pyro_thread_started.release), Edouard@2738: name="PyroThread") Edouard@2738: Edouard@2738: pyro_thread.start() Edouard@2738: Edouard@2738: # Wait for pyro thread to be effective Edouard@2738: pyro_thread_started.acquire() Edouard@2738: Edouard@2738: pyroserver.PrintServerInfo() Edouard@2738: Edouard@2738: # Beremiz IDE detects LOCAL:// runtime is ready by looking Edouard@2738: # for self.workdir in the daemon's stdout. Edouard@2738: sys.stdout.write(_("Current working directory :") + WorkingDir + "\n") Edouard@2738: sys.stdout.flush() Edouard@2738: Edouard@2738: if not (havetwisted or havewx): Edouard@2738: return Edouard@2738: Edouard@1994: ui_thread_started = Lock() Edouard@1994: ui_thread_started.acquire() Edouard@641: if havetwisted: Edouard@1984: # reactor._installSignalHandlersAgain() Edouard@1984: def ui_thread_target(): Edouard@1997: # FIXME: had to disable SignaHandlers install because Edouard@1984: # signal not working in non-main thread Edouard@1984: reactor.run(installSignalHandlers=False) Edouard@1997: else: Edouard@1984: ui_thread_target = app.MainLoop Edouard@1984: Edouard@2600: ui_thread = Thread(target=ui_thread_target, name="UIThread") Edouard@1984: ui_thread.start() Edouard@1984: Edouard@1994: # This order ui loop to unblock main thread when ready. Edouard@1994: if havetwisted: Edouard@2484: def signal_uithread_started(): Edouard@2489: global twisted_reactor_thread_id Edouard@2484: twisted_reactor_thread_id = currentThread().ident Edouard@2484: ui_thread_started.release() Edouard@2484: reactor.callLater(0, signal_uithread_started) Edouard@1997: else: Edouard@1994: wx.CallAfter(ui_thread_started.release) Edouard@1994: Edouard@1994: # Wait for ui thread to be effective Edouard@1994: ui_thread_started.acquire() Edouard@1994: print("UI thread started successfully.") Edouard@1994: Edouard@2738: runtime.GetPLCObjectSingleton().AutoLoad(autostart) Edouard@2738: Edouard@1984: try: Edouard@2738: runtime.MainWorker.runloop(FirstWorkerJob) Edouard@1984: except KeyboardInterrupt: Edouard@1984: pass Edouard@1984: Edouard@641: pyroserver.Quit() Edouard@2601: pyro_thread.join() Edouard@2270: Edouard@2669: plcobj = runtime.GetPLCObjectSingleton() Edouard@3282: try: Edouard@3282: plcobj.StopPLC() Edouard@3282: plcobj.UnLoadPLC() Edouard@3282: except: Edouard@3282: print(traceback.format_exc()) Edouard@2270: Edouard@2595: if havetwisted: Edouard@2595: reactor.stop() Edouard@2601: ui_thread.join() Edouard@2595: elif havewx: Edouard@2595: app.ExitMainLoop() Edouard@2601: ui_thread.join() Edouard@2595: Edouard@641: sys.exit(0)