Merged
authorEdouard Tisserant
Fri, 22 Mar 2019 13:26:31 +0100
changeset 2550 f2af2a655868
parent 2530 02d09fc6eb90 (current diff)
parent 2549 8f8735f558c7 (diff)
child 2551 245644bfcd24
Merged
PLCOpenEditor.py
runtime/PLCObject.py
--- a/Beremiz_service.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/Beremiz_service.py	Fri Mar 22 13:26:31 2019 +0100
@@ -31,7 +31,6 @@
 import getopt
 import threading
 from threading import Thread, Semaphore, Lock, currentThread
-import __builtin__
 from builtins import str as text
 from past.builtins import execfile
 from six.moves import builtins
--- a/PLCOpenEditor.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/PLCOpenEditor.py	Fri Mar 22 13:26:31 2019 +0100
@@ -62,7 +62,8 @@
 # Define PLCOpenEditor FileMenu extra items id
 [
     ID_PLCOPENEDITORFILEMENUGENERATE,
-] = [wx.NewId() for _init_coll_FileMenu_Items in range(1)]
+    ID_PLCOPENEDITORFILEMENUGENERATEAS,
+] = [wx.NewId() for _init_coll_FileMenu_Items in range(2)]
 
 
 beremiz_dir = paths.AbsDir(__file__)
@@ -86,6 +87,8 @@
                    kind=wx.ITEM_NORMAL, text=_(u'Save As...') + '\tCTRL+SHIFT+S')
         AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATE,
                    kind=wx.ITEM_NORMAL, text=_(u'Generate Program') + '\tCTRL+G')
+        AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATEAS,
+                   kind=wx.ITEM_NORMAL, text=_(u'Generate Program As...') + '\tCTRL+SHIFT+G')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
                    kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
@@ -108,6 +111,8 @@
         self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
         self.Bind(wx.EVT_MENU, self.OnGenerateProgramMenu,
                   id=ID_PLCOPENEDITORFILEMENUGENERATE)
+        self.Bind(wx.EVT_MENU, self.OnGenerateProgramAsMenu,
+                  id=ID_PLCOPENEDITORFILEMENUGENERATEAS)
         self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
         self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
@@ -118,7 +123,8 @@
                                (wx.ID_OPEN, "open", _(u'Open'), None),
                                (wx.ID_SAVE, "save", _(u'Save'), None),
                                (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
-                               (wx.ID_PRINT, "print", _(u'Print'), None)])
+                               (wx.ID_PRINT, "print", _(u'Print'), None),
+                               (ID_PLCOPENEDITORFILEMENUGENERATE, "Build", _(u'Generate Program'), None)])
 
     def _init_coll_HelpMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_HELP,
@@ -232,6 +238,8 @@
             self.FileMenu.Enable(wx.ID_SAVEAS, True)
             MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, True)
+            MenuToolBar.EnableTool(ID_PLCOPENEDITORFILEMENUGENERATE, True)
+            self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATEAS, True)
         else:
             self.FileMenu.Enable(wx.ID_CLOSE, False)
             self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
@@ -245,6 +253,8 @@
             self.FileMenu.Enable(wx.ID_SAVEAS, False)
             MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
             self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATE, False)
+            MenuToolBar.EnableTool(ID_PLCOPENEDITORFILEMENUGENERATE, False)
+            self.FileMenu.Enable(ID_PLCOPENEDITORFILEMENUGENERATEAS, False)
 
     def OnNewProjectMenu(self, event):
         if self.Controler is not None and not self.CheckSaveBeforeClosing():
@@ -305,27 +315,39 @@
         self.SaveProjectAs()
 
     def OnGenerateProgramMenu(self, event):
+        result = self.Controler.GetProgramFilePath()
+        if not result:
+            self.GenerateProgramAs()
+        else:
+            self.GenerateProgram(result)
+
+    def OnGenerateProgramAsMenu(self, event):
+        self.GenerateProgramAs()
+
+    def GenerateProgramAs(self):
         dialog = wx.FileDialog(self, _("Choose a file"), os.getcwd(), os.path.basename(self.Controler.GetProgramFilePath()),  _("ST files (*.st)|*.st|All files|*.*"), wx.SAVE | wx.CHANGE_DIR)
         if dialog.ShowModal() == wx.ID_OK:
-            filepath = dialog.GetPath()
-            message_text = ""
-            header, icon = _("Done"), wx.ICON_INFORMATION
-            if os.path.isdir(os.path.dirname(filepath)):
-                _program, errors, warnings = self.Controler.GenerateProgram(filepath)
-                message_text += "".join([_("warning: %s\n") % warning for warning in warnings])
-                if len(errors) > 0:
-                    message_text += "".join([_("error: %s\n") % error for error in errors])
-                    message_text += _("Can't generate program to file %s!") % filepath
-                    header, icon = _("Error"), wx.ICON_ERROR
-                else:
-                    message_text += _("Program was successfully generated!")
+            self.GenerateProgram(dialog.GetPath())
+        dialog.Destroy()
+
+    def GenerateProgram(self, filepath=None):
+        message_text = ""
+        header, icon = _("Done"), wx.ICON_INFORMATION
+        if os.path.isdir(os.path.dirname(filepath)):
+            _program, errors, warnings = self.Controler.GenerateProgram(filepath)
+            message_text += "".join([_("warning: %s\n") % warning for warning in warnings])
+            if len(errors) > 0:
+                message_text += "".join([_("error: %s\n") % error for error in errors])
+                message_text += _("Can't generate program to file %s!") % filepath
+                header, icon = _("Error"), wx.ICON_ERROR
             else:
-                message_text += _("\"%s\" is not a valid folder!") % os.path.dirname(filepath)
-                header, icon = _("Error"), wx.ICON_ERROR
-            message = wx.MessageDialog(self, message_text, header, wx.OK | icon)
-            message.ShowModal()
-            message.Destroy()
-        dialog.Destroy()
+                message_text += _("Program was successfully generated!")
+        else:
+            message_text += _("\"%s\" is not a valid folder!") % os.path.dirname(filepath)
+            header, icon = _("Error"), wx.ICON_ERROR
+        message = wx.MessageDialog(self, message_text, header, wx.OK | icon)
+        message.ShowModal()
+        message.Destroy()
 
     def OnPLCOpenEditorMenu(self, event):
         wx.MessageBox(_("No documentation available.\nComing soon."))
--- a/README.md	Fri Mar 22 11:10:37 2019 +0100
+++ b/README.md	Fri Mar 22 13:26:31 2019 +0100
@@ -25,6 +25,9 @@
 		sudo apt-get install python-nevow python-matplotlib python-lxml python-zeroconf python-cycler
 		sudo apt-get install python-autobahn python-u-msgpack
 
+		sudo apt-get install libpython2.7-dev
+		pip2 install --use sslpsk posix_spawn
+
 * Prepare
 
 		mkdir ~/Beremiz
@@ -66,7 +69,8 @@
 		cd ~/Beremiz
 		svn checkout https://svn.code.sf.net/p/bacnet/code/trunk/bacnet-stack/ BACnet
 		cd BACnet
-		make MAKE_DEFINE='-fPIC' all
+		make MAKE_DEFINE='-fPIC' MY_BACNET_DEFINES='-DPRINT_ENABLED=1 -DBACAPP_ALL -DBACFILE -DINTRINSIC_REPORTING -DBACNET_TIME_MASTER -DBACNET_PROPERTY_LISTS=1 -DBACNET_PROTOCOL_REVISION=16' library
+
 
 * Launch Beremiz IDE
 
--- a/connectors/ConnectorBase.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/ConnectorBase.py	Fri Mar 22 13:26:31 2019 +0100
@@ -4,7 +4,7 @@
 # See COPYING file for copyrights details.
 
 from __future__ import absolute_import
-import md5
+import hashlib
 
 
 class ConnectorBase(object):
@@ -12,7 +12,7 @@
     chuncksize = 1024*1024
 
     def BlobFromFile(self, filepath, seed):
-        s = md5.new()
+        s = hashlib.new('md5')
         s.update(seed)
         blobID = self.SeedBlob(seed)
         with open(filepath, "rb") as f:
--- a/connectors/PYRO/PSK_Adapter.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/PYRO/PSK_Adapter.py	Fri Mar 22 13:26:31 2019 +0100
@@ -1,10 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (C) 2019: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+"""
+The TLS-PSK adapter that handles SSL connections instead of regular sockets,
+but using Pre Shared Keys instead of Certificates
+"""
+
 from __future__ import absolute_import
 from __future__ import print_function
 
 import socket
 import re
+import ssl
 import sslpsk
-import ssl
 import Pyro
 from Pyro.core import PyroURI
 from Pyro.protocol import _connect_socket, TCPConnection, PYROAdapter
@@ -12,13 +42,13 @@
 from Pyro.util import Log
 
 
-# The TLS-PSK adapter that handles SSL connections instead of regular sockets,
-# but using Pre Shared Keys instead of Certificates
-#
 class PYROPSKAdapter(PYROAdapter):
-    # This is essentialy the same as in Pyro/protocol.py
-    # only raw_sock wrapping into sock through sslpsk.wrap_socket was added
-    # Pyro unfortunately doesn't allow cleaner customization
+    """
+    This is essentialy the same as in Pyro/protocol.py
+    only raw_sock wrapping into sock through sslpsk.wrap_socket was added
+    Pyro unfortunately doesn't allow cleaner customization
+    """
+
     def bindToURI(self, URI):
         with self.lock:   # only 1 thread at a time can bind the URI
             try:
@@ -37,7 +67,7 @@
                 # receive the authentication challenge string, and use that to build the actual identification string.
                 try:
                     authChallenge = self.recvAuthChallenge(conn)
-                except ProtocolError, x:
+                except ProtocolError as x:
                     # check if we were denied
                     if hasattr(x, "partialMsg") and x.partialMsg[:len(self.denyMSG)] == self.denyMSG:
                         raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(x.partialMsg[-1])])
@@ -70,9 +100,6 @@
     return _getProtocolAdapter(protocol)
 
 
-Pyro.protocol.getProtocolAdapter = getProtocolAdapter
-
-
 _processStringURI = Pyro.core.processStringURI
 
 
@@ -91,4 +118,13 @@
     return _processStringURI(URI)
 
 
-Pyro.core.processStringURI = processStringURI
+def setupPSKAdapter():
+    """
+    Add PyroAdapter to the list of available in
+    Pyro adapters and handle new supported protocols
+
+    This function should be called after
+    reimport of Pyro module to enable PYROS:// again.
+    """
+    Pyro.protocol.getProtocolAdapter = getProtocolAdapter
+    Pyro.core.processStringURI = processStringURI
--- a/connectors/PYRO/__init__.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/PYRO/__init__.py	Fri Mar 22 13:26:31 2019 +0100
@@ -37,13 +37,19 @@
 from Pyro.errors import PyroError
 
 import PSKManagement as PSK
+import connectors.PYRO.PSK_Adapter
 from runtime import PlcStatus
 
-# this module attribute contains a list of DNS-SD (Zeroconf) service types
-# supported by this connector confnode.
-#
-# for connectors that do not support DNS-SD, this attribute can be omitted
-# or set to an empty list.
+
+def switch_pyro_adapter(use_ssl):
+    """
+    Reloads Pyro module with new settings.
+    This is workaround for Pyro, because it doesn't work with SSL wrapper.
+    """
+    # Pyro.config.PYRO_BROKEN_MSGWAITALL = use_ssl
+    reload(Pyro.protocol)
+    if use_ssl:
+        connectors.PYRO.PSK_Adapter.setupPSKAdapter()
 
 
 def PYRO_connector_factory(uri, confnodesroot):
@@ -53,8 +59,9 @@
     confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri)
 
     scheme, location = uri.split("://")
-    if scheme == "PYROS":
-        import connectors.PYRO.PSK_Adapter # pylint: disable=wrong-import-order,unused-import,wrong-import-position
+    use_ssl = scheme == "PYROS"
+    switch_pyro_adapter(use_ssl)
+    if use_ssl:
         schemename = "PYROLOCPSK"
         url, ID = location.split('#')  # TODO fix exception when # not found
         # load PSK from project
@@ -73,10 +80,10 @@
     # Try to get the proxy object
     try:
         RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject")
-    except Exception, e:
+    except Exception as e:
         confnodesroot.logger.write_error(
             _("Connection to {loc} failed with exception {ex}\n").format(
-                loc=location, exo=str(e)))
+                loc=location, ex=str(e)))
         return None
 
     RemotePLCObjectProxy.adapter.setTimeout(60)
--- a/connectors/PYRO_dialog.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/PYRO_dialog.py	Fri Mar 22 13:26:31 2019 +0100
@@ -6,7 +6,6 @@
 from __future__ import absolute_import
 
 from itertools import repeat, islice, chain
-import wx
 
 from connectors.SchemeEditor import SchemeEditor
 
--- a/connectors/SchemeEditor.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/SchemeEditor.py	Fri Mar 22 13:26:31 2019 +0100
@@ -26,7 +26,7 @@
             self.txtctrls[tag] = txtctrl
             for win, flag in [
                     (wx.StaticText(self, label=label),
-                    wx.ALIGN_CENTER_VERTICAL),
+                     wx.ALIGN_CENTER_VERTICAL),
                     (txtctrl, wx.GROW)]:
                 self.fieldsizer.AddWindow(win, flag=flag)
 
@@ -39,7 +39,7 @@
                 self, parent.ctr,
                 # use a callafter, as editor can be deleted by calling SetURI
                 partial(wx.CallAfter, parent.SetURI),
-                self.txtctrls[tag].SetValue)
+                self.txtctrls["ID"].SetValue)
             self.mainsizer.AddWindow(self.idselector)
             self.SetSizer(self.mainsizer)
         else:
--- a/connectors/__init__.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/connectors/__init__.py	Fri Mar 22 13:26:31 2019 +0100
@@ -28,9 +28,7 @@
 
 from __future__ import absolute_import
 from os import listdir, path
-import util.paths as paths
 from connectors.ConnectorBase import ConnectorBase
-from types import ClassType
 
 connectors_packages = ["PYRO", "WAMP"]
 
@@ -119,8 +117,8 @@
         return None
 
     # new class inheriting from generic and specific connector base classes
-    return ClassType(_scheme + "_connector",
-                     (ConnectorBase, connector_specific_class), {})()
+    return type(_scheme + "_connector",
+                (ConnectorBase, connector_specific_class), {})()
 
 
 def EditorClassFromScheme(scheme):
--- a/controls/IDBrowser.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/controls/IDBrowser.py	Fri Mar 22 13:26:31 2019 +0100
@@ -98,8 +98,8 @@
             args(_("Last URI"), COL_URI, width=300 if big else 80),
             args(_("Description"), COL_DESC, width=300 if big else 200,
                  mode=dv.DATAVIEW_CELL_EDITABLE
-                      if self.isManager
-                      else dv.DATAVIEW_CELL_INERT),
+                 if self.isManager
+                 else dv.DATAVIEW_CELL_INERT),
             args(_("Last connection"), COL_LAST, width=120),
         ]
 
--- a/runtime/PLCObject.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/runtime/PLCObject.py	Fri Mar 22 13:26:31 2019 +0100
@@ -28,14 +28,14 @@
 import os
 import sys
 import traceback
+import shutil
 from time import time
-import _ctypes  # pylint: disable=wrong-import-order
+import hashlib
+from tempfile import mkstemp
+from functools import wraps, partial
 from six.moves import xrange
 from past.builtins import execfile
-import md5
-from tempfile import mkstemp
-import shutil
-from functools import wraps, partial
+import _ctypes
 
 from runtime.typemapping import TypeTranslator
 from runtime.loglevels import LogLevelsDefault, LogLevelsCount
@@ -465,7 +465,7 @@
 
     @RunInMain
     def SeedBlob(self, seed):
-        blob = (mkstemp(dir=self.tmpdir) + (md5.new(),))
+        blob = (mkstemp(dir=self.tmpdir) + (hashlib.new('md5'),))
         _fobj, _path, md5sum = blob
         md5sum.update(seed)
         newBlobID = md5sum.digest()
@@ -606,7 +606,7 @@
 
     @RunInMain
     def GetTraceVariables(self, DebugToken):
-        if (DebugToken is not None and DebugToken == self.DebugToken):
+        if DebugToken is not None and DebugToken == self.DebugToken:
             return self.PLCStatus, self._TracesSwap()
         return PlcStatus.Broken, []
 
--- a/runtime/Stunnel.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/runtime/Stunnel.py	Fri Mar 22 13:26:31 2019 +0100
@@ -1,4 +1,5 @@
 from __future__ import absolute_import
+from __future__ import print_function
 import os
 from binascii import b2a_hqx
 try:
@@ -11,6 +12,17 @@
 _PSKpath = None
 
 
+def restartStunnel():
+    """
+    Restart stunnel service using SysV init stript
+    to apply new generated credentials
+    """
+    try:
+        call(restart_stunnel_cmdline)
+    except OSError:
+        print(_("Couldn't restart stunnel service"))
+
+
 def PSKgen(ID, PSKpath):
 
     # b2a_hqx output len is 4/3 input len
@@ -20,7 +32,7 @@
     PSKstring = ID+":"+secretstring
     with open(PSKpath, 'w') as f:
         f.write(PSKstring)
-    call(restart_stunnel_cmdline)
+    restartStunnel()
 
 
 def ensurePSK(ID, PSKpath):
--- a/runtime/WampClient.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/runtime/WampClient.py	Fri Mar 22 13:26:31 2019 +0100
@@ -135,7 +135,7 @@
             self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions)
 
         for name in SubscribedEvents:
-            self.subscribe(GetCallee(name), unicode(name))
+            self.subscribe(GetCallee(name), text(name))
 
         for func in DoOnJoin:
             func(self)
@@ -151,7 +151,7 @@
 
     def publishWithOwnID(self, eventID, value):
         ID = self.config.extra["ID"]
-        self.publish(unicode(ID+'.'+eventID), value)
+        self.publish(text(ID+'.'+eventID), value)
 
 
 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
@@ -343,12 +343,12 @@
 
 def PublishEvent(eventID, value):
     if getWampStatus() == "Attached":
-        _WampSession.publish(unicode(eventID), value)
+        _WampSession.publish(text(eventID), value)
 
 
 def PublishEventWithOwnID(eventID, value):
     if getWampStatus() == "Attached":
-        _WampSession.publishWithOwnID(unicode(eventID), value)
+        _WampSession.publishWithOwnID(text(eventID), value)
 
 
 # WEB CONFIGURATION INTERFACE
--- a/runtime/Worker.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/runtime/Worker.py	Fri Mar 22 13:26:31 2019 +0100
@@ -9,9 +9,9 @@
 
 from __future__ import absolute_import
 import sys
-import thread
 from threading import Lock, Condition
 import six
+from six.moves import _thread
 
 
 class job(object):
@@ -51,20 +51,27 @@
         self.free = Condition(self.mutex)
         self.job = None
 
+    def reraise(self, job):
+        """
+        reraise exception happend in a job
+        @param job: job where original exception happend
+        """
+        exc_type = job.exc_info[0]
+        exc_value = job.exc_info[1]
+        exc_traceback = job.exc_info[2]
+        six.reraise(exc_type, exc_value, exc_traceback)
+
     def runloop(self, *args, **kwargs):
         """
         meant to be called by worker thread (blocking)
         """
-        self._threadID = thread.get_ident()
+        self._threadID = _thread.get_ident()
         self.mutex.acquire()
         if args or kwargs:
             _job = job(*args, **kwargs)
             _job.do()
-            if _job.success:
-                # result is ignored
-                pass
-            else:
-                raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2]
+            if not _job.success:
+                self.reraise(_job)
 
         while not self._finish:
             self.todo.wait()
@@ -86,7 +93,7 @@
 
         _job = job(*args, **kwargs)
 
-        if self._threadID == thread.get_ident():
+        if self._threadID == _thread.get_ident():
             # if caller is worker thread execute immediately
             _job.do()
         else:
@@ -106,10 +113,7 @@
         if _job.success:
             return _job.result
         else:
-            exc_type = _job.exc_info[0]
-            exc_value = _job.exc_info[1]
-            exc_traceback = _job.exc_info[2]
-            six.reraise(exc_type, exc_value, exc_traceback)
+            self.reraise(_job)
 
     def quit(self):
         """
--- a/runtime/spawn_subprocess.py	Fri Mar 22 11:10:37 2019 +0100
+++ b/runtime/spawn_subprocess.py	Fri Mar 22 13:26:31 2019 +0100
@@ -3,6 +3,7 @@
 
 # subset of subprocess built-in module using posix_spawn rather than fork.
 
+from __future__ import print_function
 from __future__ import absolute_import
 import os
 import signal
--- a/tests/first_steps/plc.xml	Fri Mar 22 11:10:37 2019 +0100
+++ b/tests/first_steps/plc.xml	Fri Mar 22 13:26:31 2019 +0100
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="Beremiz" productName="Beremiz" productVersion="1" creationDateTime="2016-10-24T18:09:22"/>
-  <contentHeader name="First Steps" modificationDateTime="2019-02-13T10:30:06">
+  <contentHeader name="First Steps" modificationDateTime="2018-09-26T12:52:51">
     <coordinateInfo>
       <fbd>
         <scaling x="0" y="0"/>
@@ -676,11 +676,6 @@
                 <INT/>
               </type>
             </variable>
-            <variable name="R2">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
           </localVars>
           <externalVars constant="true">
             <variable name="ResetCounterValue">
@@ -813,7 +808,7 @@
                 <relPosition x="85" y="15"/>
               </connectionPointOutAction>
             </step>
-            <actionBlock localId="8" height="63" width="148" executionOrderId="0">
+            <actionBlock localId="8" height="52" width="164" executionOrderId="0">
               <position x="154" y="191"/>
               <connectionPointIn>
                 <relPosition x="0" y="15"/>
@@ -830,14 +825,6 @@
                   </ST>
                 </inline>
               </action>
-              <action localId="0" qualifier="S">
-                <relPosition x="0" y="0"/>
-                <inline>
-                  <ST>
-                    <xhtml:p><![CDATA[ R2 := True;]]></xhtml:p>
-                  </ST>
-                </inline>
-              </action>
               <action localId="0">
                 <relPosition x="0" y="0"/>
                 <inline>
@@ -848,18 +835,18 @@
               </action>
             </actionBlock>
             <selectionConvergence localId="10" height="1" width="431">
-              <position x="70" y="657"/>
+              <position x="70" y="273"/>
               <connectionPointIn>
                 <relPosition x="0" y="0"/>
                 <connection refLocalId="13">
-                  <position x="70" y="657"/>
+                  <position x="70" y="273"/>
                   <position x="70" y="244"/>
                 </connection>
               </connectionPointIn>
               <connectionPointIn>
                 <relPosition x="431" y="0"/>
                 <connection refLocalId="14">
-                  <position x="501" y="657"/>
+                  <position x="501" y="273"/>
                   <position x="501" y="250"/>
                 </connection>
               </connectionPointIn>
@@ -868,12 +855,12 @@
               </connectionPointOut>
             </selectionConvergence>
             <jumpStep localId="12" targetName="Start" height="13" width="12">
-              <position x="280" y="701"/>
+              <position x="280" y="317"/>
               <connectionPointIn>
                 <relPosition x="6" y="0"/>
                 <connection refLocalId="10">
-                  <position x="286" y="701"/>
-                  <position x="286" y="658"/>
+                  <position x="286" y="317"/>
+                  <position x="286" y="274"/>
                 </connection>
               </connectionPointIn>
             </jumpStep>
@@ -883,7 +870,7 @@
                 <relPosition x="10" y="0"/>
                 <connection refLocalId="7">
                   <position x="70" y="242"/>
-                  <position x="70" y="221"/>
+                  <position x="70" y="215"/>
                 </connection>
               </connectionPointIn>
               <connectionPointOut>
@@ -983,11 +970,6 @@
             </variable>
           </inputVars>
           <outputVars>
-            <variable name="Reset0">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
             <variable name="Out">
               <type>
                 <INT/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/beremiz-requirements/Dockerfile	Fri Mar 22 13:26:31 2019 +0100
@@ -0,0 +1,69 @@
+#
+# Dockerfile for Beremiz
+# This container is used to run tests for Beremiz
+#
+# To run test localy use following command executed from beremiz directory:
+# $ docker run --volume=$PWD:/beremiz --workdir="/beremiz" --volume=$PWD/../CanFestival-3:/CanFestival-3 --memory=1g --entrypoint=/beremiz/tests/tools/check_source.sh skvorl/beremiz-requirements
+#
+
+FROM skvorl/python2.7-wxpython
+MAINTAINER Andrey Skvortsov <andrej.skvortzov@gmail.com>
+
+RUN set -xe \
+    && apt-get update \
+    && apt-get install -y --no-install-recommends \
+               python-nevow \
+               python-lxml \
+               python-zeroconf \
+               python-m2crypto \
+               python-autobahn \
+               python-future \
+               python-simplejson \
+    && apt-get install -y --no-install-recommends ca-certificates \
+    && apt-get install -y --no-install-recommends wxglade python-cwiid \
+    && apt-get install -y --no-install-recommends build-essential automake flex bison mercurial python-pip \
+    && apt-get install -y --no-install-recommends \
+               pep8 \
+               pylint \
+               python-pytest \
+               python-pytest-timeout \
+               gettext \
+               python-ddt \
+               libpython2.7-dev \
+    \
+    && apt-get install -y python3-pip \
+    && pip3 install crossbar \
+    \
+    && /usr/bin/pip install gnosis \
+                            pyro \
+                            sslpsk \
+                            posix_spawn \
+    && cd / \
+    && hg clone http://dev.automforge.net/CanFestival-3 \
+    && cd CanFestival-3 \
+    && ./configure \
+    \
+    && cd / \
+    && hg clone -r 24ef30a9bcee1e65b027be2c7f7a8d52c41a7479 https://bitbucket.org/automforge/matiec \
+    && cd matiec \
+    && autoreconf -i \
+    && ./configure \
+    && make \
+    && make install \
+    && mkdir /usr/lib/matiec \
+    && cp -vR lib/* /usr/lib/matiec \
+    && rm -rf /matiec \
+    \
+    && cd / \
+    && hg clone https://bitbucket.org/mjsousa/modbus Modbus \
+    && cd Modbus \
+    && make \
+    \
+    && cd / \
+    && svn checkout https://svn.code.sf.net/p/bacnet/code/trunk/bacnet-stack/ BACnet \
+    && cd BACnet \
+    && make MAKE_DEFINE='-fPIC' all \
+    \
+    && apt-get remove -y bison flex automake python-pip python3-pip libpython2.7-dev \
+    && apt-get autoremove -y \
+    && apt-get clean -y \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/python2.7-wxpython/Dockerfile	Fri Mar 22 13:26:31 2019 +0100
@@ -0,0 +1,11 @@
+#
+# Dockerfile for wxPython3.0 running on python2.7
+#
+
+FROM python:2.7-stretch
+
+RUN set -xe \
+    && apt-get update \
+    && apt-get install -y --no-install-recommends python-wxgtk3.0 python-matplotlib \
+    && apt-get install -y --no-install-recommends python-xvfbwrapper xvfb \
+    && apt-get clean
--- a/tests/tools/check_source.sh	Fri Mar 22 11:10:37 2019 +0100
+++ b/tests/tools/check_source.sh	Fri Mar 22 13:26:31 2019 +0100
@@ -66,7 +66,7 @@
     # remove compiled Python files
     find . -name '*.pyc' -exec rm -f {} \;
 
-    for i in $py_files; do
+    for i in $py3_files; do
         # echo $i
         python3 -m py_compile $i
         if [ $? -ne 0 ]; then
@@ -444,6 +444,8 @@
         echo "No files to check"
         exit 0;
     fi
+
+    py3_files=$(echo $py_files | sed 's/ [[:alnum:]_\-\/.]*pyjslib.py//')
 }