Merge python3
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Wed, 04 Oct 2023 16:24:00 +0200
branchpython3
changeset 3854 d29d67e86532
parent 3853 e1cfb03e6582 (diff)
parent 3837 efe0b5b21842 (current diff)
child 3862 ef434ea0bcec
Merge
svghmi/gen_index_xhtml.xslt
svghmi/hmi_tree.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.github/workflows/run_tests_in_docker.yml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,76 @@
+name: CI Automated testing
+
+on:
+  push:
+    branches: [ python3 ]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+          path: beremiz
+
+    - uses: actions/checkout@v3
+      with:
+          repository: beremiz/matiec
+          ref: e5be6a1f036d21cd7b5ee75ef352783a7cfcc1a7
+          path: matiec
+
+    - uses: actions/checkout@v3
+      with:
+          repository: open62541/open62541
+          ref: v1.3.6
+          path: open62541
+          submodules: recursive
+
+    - name: Restore cached docker image
+      id: cache-docker-restore
+      uses: actions/cache/restore@v3
+      env:
+        cache-name: cache-docker
+      with:
+        path: /tmp/latest.tar
+        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('beremiz/tests/tools/Docker', 'beremiz/requirements.txt') }}
+
+    - if: ${{ steps.cache-docker-restore.outputs.cache-hit == false }}
+      name: Create docker image
+      run: |
+        cd beremiz/tests/tools/Docker
+        ./build_docker_image.sh
+        docker image save --output="/tmp/latest.tar" beremiz_sikuli
+
+    - if: ${{ steps.cache-docker-restore.outputs.cache-hit == false }}
+      name: Save docker image in cache
+      id: cache-docker-save
+      uses: actions/cache/save@v3
+      with:
+        path: /tmp/latest.tar
+        key: ${{ steps.cache-docker-restore.outputs.cache-primary-key }}
+
+    - if: ${{ steps.cache-docker-restore.outputs.cache-hit != false }}
+      name: Re-use docker image
+      run: |
+        docker image load --input="/tmp/latest.tar" 
+
+    - name: Create docker container
+      run: |
+        cd beremiz/tests/tools/Docker
+        ./create_docker_container.sh ${{ github.workspace }}/test
+        
+    - name: Run tests in docker
+      run: |
+        cd beremiz/tests/tools/Docker
+        ./do_test_in_docker.sh
+
+    - name: Upload test resuts artifact
+      uses: actions/upload-artifact@v3
+      if: failure()
+      with:
+        name: test_results
+        path: ${{ github.workspace }}/test
+        retention-days: 5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.gitignore	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,9 @@
+*.pyc
+**/__pycache__
+.vscode
+**/build/**
+*.class
+**/.svghmithumbs/**
+**/my_*.der
+**/my_*.pem
+tests/tools/Docker/requirements.txt
--- a/.hgignore	Fri Aug 18 11:38:20 2023 +0200
+++ b/.hgignore	Wed Oct 04 16:24:00 2023 +0200
@@ -24,3 +24,5 @@
 
 doc/_build
 doc/locale
+
+^.*\$py.class$
--- a/Beremiz.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/Beremiz.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,12 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
 import sys
 import getopt
-from past.builtins import execfile
 
 import wx
 from wx.lib.agw.advancedsplash import AdvancedSplash, AS_NOTIMEOUT, AS_CENTER_ON_SCREEN
@@ -50,6 +47,7 @@
         self.modules = ["BeremizIDE"]
         self.debug = os.path.exists("BEREMIZ_DEBUG")
         self.handle_exception = None
+        self.logf = None
 
     def Bpath(self, *args):
         return os.path.join(self.app_dir, *args)
@@ -62,12 +60,13 @@
         print("-h --help                    Print this help")
         print("-u --updatecheck URL         Retrieve update information by checking URL")
         print("-e --extend PathToExtension  Extend IDE functionality by loading at start additional extensions")
+        print("-l --log path                write content of console tab to given file")
         print("")
         print("")
 
     def SetCmdOptions(self):
-        self.shortCmdOpts = "hu:e:"
-        self.longCmdOpts = ["help", "updatecheck=", "extend="]
+        self.shortCmdOpts = "hu:e:l:"
+        self.longCmdOpts = ["help", "updatecheck=", "extend=", "log="]
 
     def ProcessOption(self, o, a):
         if o in ("-h", "--help"):
@@ -77,6 +76,8 @@
             self.updateinfo_url = a
         if o in ("-e", "--extend"):
             self.extensions.append(a)
+        if o in ("-l", "--log"):
+            self.logf = open(a, 'a')
 
     def ProcessCommandLineArgs(self):
         self.SetCmdOptions()
@@ -103,7 +104,7 @@
 
     def CreateApplication(self):
 
-        BeremizAppType = wx.App if wx.VERSION >= (3, 0, 0) else wx.PySimpleApp
+        BeremizAppType = wx.App
 
         class BeremizApp(BeremizAppType):
             def OnInit(_self):  # pylint: disable=no-self-argument
@@ -112,8 +113,6 @@
 
         self.app = BeremizApp(redirect=self.debug)
         self.app.SetAppName('beremiz')
-        if wx.VERSION < (3, 0, 0):
-            wx.InitAllImageHandlers()
 
     def ShowSplashScreen(self):
         class Splash(AdvancedSplash):
@@ -154,7 +153,7 @@
             sys.path.append(extension_folder)
             AddCatalog(os.path.join(extension_folder, "locale"))
             AddBitmapFolder(os.path.join(extension_folder, "images"))
-            execfile(extfilename, self.globals())
+            exec(compile(open(extfilename, "rb").read(), extfilename, 'exec'), self.globals())
 
     def CheckUpdates(self):
         if self.updateinfo_url is not None:
@@ -162,8 +161,8 @@
 
             def updateinfoproc():
                 try:
-                    import urllib2
-                    self.updateinfo = urllib2.urlopen(self.updateinfo_url, None).read()
+                    import urllib.request, urllib.error, urllib.parse
+                    self.updateinfo = urllib.request.urlopen(self.updateinfo_url, None).read()
                 except Exception:
                     self.updateinfo = _("update info unavailable.")
 
@@ -182,10 +181,10 @@
     def InstallExceptionHandler(self):
         import version
         import util.ExceptionHandler
-        self.handle_exception = util.ExceptionHandler.AddExceptHook(version.app_version)
+        self.handle_exception = util.ExceptionHandler.AddExceptHook(version.app_version, logf=self.logf)
 
     def CreateUI(self):
-        self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath)
+        self.frame = self.BeremizIDE.Beremiz(None, self.projectOpen, self.buildpath, logf=self.logf)
 
     def CloseSplash(self):
         if self.splash:
--- a/BeremizIDE.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/BeremizIDE.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,21 +24,19 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
+import pickle
 import sys
-import tempfile
 import shutil
-import random
 import time
+import signal
 from time import time as gettime
-from threading import Lock, Timer, currentThread
-
-from six.moves import cPickle, xrange
+from threading import Lock, Timer, current_thread
+
 import wx.lib.buttons
 import wx.lib.statbmp
 import wx.stc
+import wx.adv
 
 
 import version
@@ -47,9 +45,8 @@
 from editors.TextViewer import TextViewer
 from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
 from editors.DataTypeEditor import DataTypeEditor
-from util import paths as paths
+from util.paths import Bpath
 from util.MiniTextControler import MiniTextControler
-from util.ProcessLogger import ProcessLogger
 from util.BitmapLibrary import GetBitmap
 from controls.LogViewer import LogViewer
 from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
@@ -84,15 +81,11 @@
     EncodeFileSystemPath, \
     DecodeFileSystemPath
 
-
-beremiz_dir = paths.AbsDir(__file__)
-
-
-def Bpath(*args):
-    return os.path.join(beremiz_dir, *args)
+from LocalRuntimeMixin import LocalRuntimeMixin
+
 
 def AppendMenu(parent, help, id, kind, text):
-    return parent.Append(help=help, id=id, kind=kind, text=text)
+    return parent.Append(wx.MenuItem(helpString=help, id=id, kind=kind, text=text))
 
 MAX_RECENT_PROJECTS = 9
 
@@ -109,13 +102,13 @@
     }
 
 
-MainThread = currentThread().ident
+MainThread = current_thread().ident
 REFRESH_PERIOD = 0.1
 
 
 class LogPseudoFile(object):
     """ Base class for file like objects to facilitate StdOut for the Shell."""
-    def __init__(self, output, risecall):
+    def __init__(self, output, risecall, logf):
         self.red_white = 1
         self.red_yellow = 2
         self.black_white = wx.stc.STC_STYLE_DEFAULT
@@ -131,8 +124,12 @@
         self.LastRefreshTime = gettime()
         self.LastRefreshTimer = None
         self.refreshPending = False
+        self.logf = logf
 
     def write(self, s, style=None):
+        if self.logf is not None:
+            self.logf.write(s)
+            self.logf.flush()
         self.StackLock.acquire()
         self.stack.append((s, style))
         self.StackLock.release()
@@ -156,7 +153,7 @@
             self.LastRefreshTimer = None
 
     def _should_write(self):
-        if MainThread == currentThread().ident:
+        if MainThread == current_thread().ident:
             app = wx.GetApp()
             if app is not None:
                 self._write()
@@ -179,7 +176,7 @@
                     if style is None:
                         style = self.black_white
                     if style != self.black_white:
-                        self.output.StartStyling(self.output.GetLength(), 0xff)
+                        self.output.StartStyling(self.output.GetLength())
 
                     # Temporary deactivate read only mode on StyledTextCtrl for
                     # adding text. It seems that text modifications, even
@@ -233,10 +230,10 @@
             self.YieldLock.release()
 
 
-ID_FILEMENURECENTPROJECTS = wx.NewId()
-
-
-class Beremiz(IDEFrame):
+ID_FILEMENURECENTPROJECTS = wx.NewIdRef()
+
+
+class Beremiz(IDEFrame, LocalRuntimeMixin):
 
     def _init_utils(self):
         self.ConfNodeMenu = wx.Menu(title='')
@@ -247,18 +244,18 @@
 
     def _init_coll_FileMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_NEW,
-                   kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
+                   kind=wx.ITEM_NORMAL, text=_('New') + '\tCTRL+N')
         AppendMenu(parent, help='', id=wx.ID_OPEN,
-                   kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
-        parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
+                   kind=wx.ITEM_NORMAL, text=_('Open') + '\tCTRL+O')
+        self.RecentProjectsMenuItem = parent.AppendSubMenu(self.RecentProjectsMenu, _("&Recent Projects"))
         parent.AppendSeparator()
-        parent.AppendMenu(wx.ID_ANY, _("&Tutorials and Examples"), self.TutorialsProjectsMenu)
+        parent.AppendSubMenu(self.TutorialsProjectsMenu, _("&Tutorials and Examples"))
 
         exemples_dir = Bpath("exemples")
         project_list = sorted(os.listdir(exemples_dir))
 
         for idx, dirname  in enumerate(project_list):
-            text = u'&%d: %s' % (idx + 1, dirname)
+            text = '&%d: %s' % (idx + 1, dirname)
 
             item = self.TutorialsProjectsMenu.Append(wx.ID_ANY, text, '')
 
@@ -275,23 +272,23 @@
             self.Bind(wx.EVT_MENU, OpenExemple, item)
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_SAVE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
+                   kind=wx.ITEM_NORMAL, text=_('Save') + '\tCTRL+S')
         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
-                   kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S')
+                   kind=wx.ITEM_NORMAL, text=_('Save as') + '\tCTRL+SHIFT+S')
         AppendMenu(parent, help='', id=wx.ID_CLOSE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
+                   kind=wx.ITEM_NORMAL, text=_('Close Tab') + '\tCTRL+W')
         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
-                   kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
+                   kind=wx.ITEM_NORMAL, text=_('Close Project') + '\tCTRL+SHIFT+W')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
-                   kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
+                   kind=wx.ITEM_NORMAL, text=_('Page Setup') + '\tCTRL+ALT+P')
         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
-                   kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
+                   kind=wx.ITEM_NORMAL, text=_('Preview') + '\tCTRL+SHIFT+P')
         AppendMenu(parent, help='', id=wx.ID_PRINT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
+                   kind=wx.ITEM_NORMAL, text=_('Print') + '\tCTRL+P')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_EXIT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
+                   kind=wx.ITEM_NORMAL, text=_('Quit') + '\tCTRL+Q')
 
         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
@@ -304,20 +301,20 @@
         self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
 
-        self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
-                               (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)])
+        self.AddToMenuToolBar([(wx.ID_NEW, "new", _('New'), None),
+                               (wx.ID_OPEN, "open", _('Open'), None),
+                               (wx.ID_SAVE, "save", _('Save'), None),
+                               (wx.ID_SAVEAS, "saveas", _('Save As...'), None),
+                               (wx.ID_PRINT, "print", _('Print'), None)])
 
     def _RecursiveAddMenuItems(self, menu, items):
         for name, text, helpstr, children in items:
             if len(children) > 0:
                 new_menu = wx.Menu(title='')
-                menu.AppendMenu(wx.ID_ANY, text, new_menu)
+                menu.AppendSubMenu(new_menu, text)
                 self._RecursiveAddMenuItems(new_menu, children)
             else:
-                item = menu.Append(wx.ID_ANY, text, helpstr)
+                item = menu.Append(wx.MenuItem(text=text, helpString=helpstr, kind=wx.ITEM_NORMAL, id=wx.ID_ANY))
                 self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name), item)
 
     def _init_coll_AddMenu_Items(self, parent):
@@ -328,22 +325,22 @@
         def handler(event):
             return wx.MessageBox(
                 version.GetCommunityHelpMsg(),
-                _(u'Community support'),
+                _('Community support'),
                 wx.OK | wx.ICON_INFORMATION)
 
-        item = parent.Append(wx.ID_ANY, _(u'Community support'), '')
+        item = parent.Append(wx.ID_ANY, _('Community support'), '')
         self.Bind(wx.EVT_MENU, handler, item)
 
-        parent.Append(help='', id=wx.ID_ABOUT,
-                      kind=wx.ITEM_NORMAL, text=_(u'About'))
+        parent.Append(wx.MenuItem(helpString='', id=wx.ID_ABOUT,
+                      kind=wx.ITEM_NORMAL, text=_('About')))
         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
 
     def _init_coll_ConnectionStatusBar_Fields(self, parent):
         parent.SetFieldsCount(3)
 
-        parent.SetStatusText(number=0, text='')
-        parent.SetStatusText(number=1, text='')
-        parent.SetStatusText(number=2, text='')
+        parent.SetStatusText(i=0, text='')
+        parent.SetStatusText(i=1, text='')
+        parent.SetStatusText(i=2, text='')
 
         parent.SetStatusWidths([-1, 300, 200])
 
@@ -352,10 +349,12 @@
 
         self.EditMenuSize = self.EditMenu.GetMenuItemCount()
 
-        inspectorID = wx.NewId()
+        inspectorID = wx.NewIdRef()
         self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID)
         accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord('I'), inspectorID)]
 
+        self.methodLock = Lock()
+
         for method, shortcut in [("Stop",     wx.WXK_F4),
                                  ("Run",      wx.WXK_F5),
                                  ("Transfer", wx.WXK_F6),
@@ -365,10 +364,17 @@
             def OnMethodGen(obj, meth):
                 def OnMethod(evt):
                     if obj.CTR is not None:
-                        obj.CTR.CallMethod('_'+meth)
-                    wx.CallAfter(self.RefreshStatusToolBar)
+                        if obj.methodLock.acquire(False):
+                            obj.CTR.CallMethod('_'+meth)
+                            obj.methodLock.release()
+                            wx.CallAfter(obj.RefreshStatusToolBar)
+                        else:
+                            # Postpone call if one of method already running
+                            # can happen because of long method using log, 
+                            # itself calling wx.Yield
+                            wx.CallLater(50, OnMethod, evt)
                 return OnMethod
-            newid = wx.NewId()
+            newid = wx.NewIdRef()
             self.Bind(wx.EVT_MENU, OnMethodGen(self, method), id=newid)
             accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut, newid)]
 
@@ -395,6 +401,7 @@
         self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")
 
         self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)
+        self.LogConsole.SetCaretPeriod(0)
 
         self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
         self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)
@@ -409,7 +416,7 @@
         # self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)
 
         StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
-                                   wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
+                                   wx.TB_FLAT | wx.TB_HORIZONTAL | wx.NO_BORDER)
         StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
         StatusToolBar.Realize()
         self.Panes["StatusToolBar"] = StatusToolBar
@@ -420,7 +427,7 @@
 
         self.AUIManager.Update()
 
-        self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP)
+        self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.STB_SIZEGRIP)
         self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
         self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range=100)
         self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2)
@@ -436,17 +443,16 @@
             # found here.
             os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"]
 
-    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
+    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True, logf=None):
+
         # Add beremiz's icon in top left corner of the frame
         self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO)
         self.__init_execute_path()
 
         IDEFrame.__init__(self, parent, debug)
-        self.Log = LogPseudoFile(self.LogConsole, self.SelectTab)
-
-        self.local_runtime = None
-        self.runtime_port = None
-        self.local_runtime_tmpdir = None
+        self.Log = LogPseudoFile(self.LogConsole, self.SelectTab, logf)
+
+        LocalRuntimeMixin.__init__(self, self.Log)
 
         self.LastPanelSelected = None
 
@@ -501,6 +507,8 @@
         self.RefreshAll()
         self.LogConsole.SetFocus()
 
+        signal.signal(signal.SIGTERM,self.signalTERM_handler)
+
     def RefreshTitle(self):
         name = _("Beremiz")
         if self.CTR is not None:
@@ -511,37 +519,6 @@
         else:
             self.SetTitle(name)
 
-    def StartLocalRuntime(self, taskbaricon=True):
-        if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
-            # create temporary directory for runtime working directory
-            self.local_runtime_tmpdir = tempfile.mkdtemp()
-            # choose an arbitrary random port for runtime
-            self.runtime_port = int(random.random() * 1000) + 61131
-            self.Log.write(_("Starting local runtime...\n"))
-            # launch local runtime
-            self.local_runtime = ProcessLogger(
-                self.Log,
-                "\"%s\" \"%s\" -p %s -i localhost %s %s" % (
-                    sys.executable,
-                    Bpath("Beremiz_service.py"),
-                    self.runtime_port,
-                    {False: "-x 0", True: "-x 1"}[taskbaricon],
-                    self.local_runtime_tmpdir),
-                no_gui=False,
-                timeout=500, keyword=self.local_runtime_tmpdir,
-                cwd=self.local_runtime_tmpdir)
-            self.local_runtime.spin()
-        return self.runtime_port
-
-    def KillLocalRuntime(self):
-        if self.local_runtime is not None:
-            # shutdown local runtime
-            self.local_runtime.kill(gently=False)
-            # clear temp dir
-            shutil.rmtree(self.local_runtime_tmpdir)
-
-            self.local_runtime = None
-
     def OnOpenWidgetInspector(self, evt):
         # Activate the widget inspection tool
         from wx.lib.inspection import InspectionTool
@@ -560,7 +537,8 @@
         event.Skip()
 
     def OnLogConsoleUpdateUI(self, event):
-        self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
+        if event.GetUpdated()==wx.stc.STC_UPDATE_SELECTION:
+            self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
         event.Skip()
 
     def OnLogConsoleMarginClick(self, event):
@@ -603,7 +581,7 @@
             elif answer == wx.ID_CANCEL:
                 return False
 
-        for idx in xrange(self.TabsOpened.GetPageCount()):
+        for idx in range(self.TabsOpened.GetPageCount()):
             window = self.TabsOpened.GetPage(idx)
             if not window.CheckSaveBeforeClosing():
                 return False
@@ -667,6 +645,11 @@
             # prevent event to continue, i.e. cancel closing
             event.Veto()
 
+    def signalTERM_handler(self, sig, frame):
+        print ("Signal TERM caught: kill local runtime and quit, no save")
+        self.KillLocalRuntime()
+        sys.exit()
+
     def RefreshFileMenu(self):
         self.RefreshRecentProjectsMenu()
 
@@ -715,18 +698,18 @@
 
     def RefreshRecentProjectsMenu(self):
         try:
-            recent_projects = map(DecodeFileSystemPath,
-                                  self.GetConfigEntry("RecentProjects", []))
+            recent_projects = list(map(DecodeFileSystemPath,
+                                  self.GetConfigEntry("RecentProjects", [])))
         except Exception:
             recent_projects = []
 
         while self.RecentProjectsMenu.GetMenuItemCount() > 0:
             item = self.RecentProjectsMenu.FindItemByPosition(0)
-            self.RecentProjectsMenu.RemoveItem(item)
-
-        self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0)
+            self.RecentProjectsMenu.Remove(item)
+
+        self.RecentProjectsMenuItem.Enable(len(recent_projects) > 0)
         for idx, projectpath in enumerate(recent_projects):
-            text = u'&%d: %s' % (idx + 1, projectpath)
+            text = '&%d: %s' % (idx + 1, projectpath)
 
             item = self.RecentProjectsMenu.Append(wx.ID_ANY, text, '')
             self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), item)
@@ -769,8 +752,9 @@
 
             for confnode_method in self.CTR.StatusMethods:
                 if "method" in confnode_method and confnode_method.get("shown", True):
-                    tool = StatusToolBar.AddSimpleTool(
-                        wx.ID_ANY, GetBitmap(confnode_method.get("bitmap", "Unknown")),
+                    tool = StatusToolBar.AddTool(
+                        wx.ID_ANY, confnode_method["name"],
+                        GetBitmap(confnode_method.get("bitmap", "Unknown")),
                         confnode_method["tooltip"])
                     self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), tool)
 
@@ -795,7 +779,7 @@
             else:
                 panel = None
             if panel != self.LastPanelSelected:
-                for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
+                for i in range(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
                     item = self.EditMenu.FindItemByPosition(self.EditMenuSize)
                     if item is not None:
                         if item.IsSeparator():
@@ -813,7 +797,7 @@
             if panel is not None:
                 panel.RefreshConfNodeMenu(self.EditMenu)
         else:
-            for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
+            for i in range(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
                 item = self.EditMenu.FindItemByPosition(i)
                 if item is not None:
                     if item.IsSeparator():
@@ -821,7 +805,7 @@
                     else:
                         self.EditMenu.Delete(item.GetId())
             self.LastPanelSelected = None
-        self.MenuBar.UpdateMenus()
+        self.MenuBar.Refresh()
 
     def RefreshAll(self):
         self.RefreshStatusToolBar()
@@ -838,10 +822,12 @@
         return OnMenu
 
     def GetConfigEntry(self, entry_name, default):
-        return cPickle.loads(str(self.Config.Read(entry_name, cPickle.dumps(default))))
+        return pickle.loads(self.Config.Read(entry_name,
+                                             pickle.dumps(default,
+                                                          0).decode()).encode())
 
     def ResetConnectionStatusBar(self):
-        for field in xrange(self.ConnectionStatusBar.GetFieldsCount()):
+        for field in range(self.ConnectionStatusBar.GetFieldsCount()):
             self.ConnectionStatusBar.SetStatusText('', field)
 
     def ResetView(self):
@@ -856,16 +842,19 @@
 
     def RefreshConfigRecentProjects(self, projectpath, err=False):
         try:
-            recent_projects = map(DecodeFileSystemPath,
-                                  self.GetConfigEntry("RecentProjects", []))
+            recent_projects = list(map(DecodeFileSystemPath,
+                                  self.GetConfigEntry("RecentProjects", [])))
         except Exception:
             recent_projects = []
         if projectpath in recent_projects:
             recent_projects.remove(projectpath)
         if not err:
             recent_projects.insert(0, projectpath)
-        self.Config.Write("RecentProjects", cPickle.dumps(
-            map(EncodeFileSystemPath, recent_projects[:MAX_RECENT_PROJECTS])))
+        self.Config.Write("RecentProjects",
+                          pickle.dumps(
+                              list(map(EncodeFileSystemPath,
+                                       recent_projects[:MAX_RECENT_PROJECTS])),
+                              0))
         self.Config.Flush()
 
     def ResetPerspective(self):
@@ -877,7 +866,7 @@
             return
 
         try:
-            defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
+            defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder").encode())
         except Exception:
             defaultpath = os.path.expanduser("~")
 
@@ -988,19 +977,27 @@
         self.Close()
 
     def OnAboutMenu(self, event):
-        info = version.GetAboutDialogInfo()
+        info = wx.adv.AboutDialogInfo()
+        info = version.GetAboutDialogInfo(info)
+        info.Name = "Beremiz"
+        info.Description = _("Open Source framework for automation, "
+            "implementing IEC 61131 IDE with constantly growing set of extensions "
+            "and flexible PLC runtime.")
+
+        info.Icon = wx.Icon(Bpath("images", "about_brz_logo.png"), wx.BITMAP_TYPE_PNG)
+
         ShowAboutDialog(self, info)
 
     def OnProjectTreeItemBeginEdit(self, event):
         selected = event.GetItem()
-        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFNODE:
+        if self.ProjectTree.GetItemData(selected)["type"] == ITEM_CONFNODE:
             event.Veto()
         else:
             IDEFrame.OnProjectTreeItemBeginEdit(self, event)
 
     def OnProjectTreeRightUp(self, event):
         item = event.GetItem()
-        item_infos = self.ProjectTree.GetPyData(item)
+        item_infos = self.ProjectTree.GetItemData(item)
 
         if item_infos["type"] == ITEM_CONFNODE:
             confnode_menu = wx.Menu(title='')
@@ -1036,7 +1033,7 @@
 
     def OnProjectTreeItemActivated(self, event):
         selected = event.GetItem()
-        item_infos = self.ProjectTree.GetPyData(selected)
+        item_infos = self.ProjectTree.GetItemData(selected)
         if item_infos["type"] == ITEM_CONFNODE:
             item_infos["confnode"]._OpenView()
             event.Skip()
@@ -1047,7 +1044,7 @@
 
     def ProjectTreeItemSelect(self, select_item):
         if select_item is not None and select_item.IsOk():
-            item_infos = self.ProjectTree.GetPyData(select_item)
+            item_infos = self.ProjectTree.GetItemData(select_item)
             if item_infos["type"] == ITEM_CONFNODE:
                 item_infos["confnode"]._OpenView(onlyopened=True)
             elif item_infos["type"] == ITEM_PROJECT:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Beremiz_cli.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import posixpath
+import sys
+import time
+
+from functools import wraps
+from importlib import import_module
+
+import click
+
+class CLISession(object):
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+        self.controller = None
+
+pass_session = click.make_pass_decorator(CLISession)
+
+
+@click.group(chain=True)
+@click.option(
+    "--project-home",
+    envvar="PROJECT_HOME",
+    default=".",
+    metavar="PATH",
+    help="Changes the project folder location.",
+)
+@click.option(
+    "--config",
+    nargs=2,
+    multiple=True,
+    metavar="KEY VALUE",
+    help="Overrides a config key/value pair.",
+)
+@click.option(
+    "--keep", "-k", is_flag=True,
+    help="Keep local runtime, do not kill it after executing commands.",
+)
+@click.option("--verbose", "-v", is_flag=True, help="Enables verbose mode.")
+@click.option(
+    "--buildpath", "-b", help="Where to store files created during build."
+)
+@click.option(
+    "--uri", "-u", help="URI to reach remote PLC."
+)
+@click.version_option("0.1")
+@click.pass_context
+def cli(ctx, **kwargs):
+    """Beremiz CLI manipulates beremiz projects and runtimes. """
+
+    ctx.obj = CLISession(**kwargs)
+
+def ensure_controller(func):
+    @wraps(func)
+    def func_wrapper(session, *args, **kwargs):
+        if session.controller is None:
+            session.controller = import_module("CLIController").CLIController(session)
+        ret = func(session, *args, **kwargs)
+        return ret
+
+    return func_wrapper
+
+@cli.command()
+@click.option(
+    "--target", "-t", help="Target system triplet."
+)
+@pass_session
+@ensure_controller
+def build(session, target):
+    """Builds project. """
+    def processor():
+        return session.controller.build_project(target)
+    return processor
+
+@cli.command()
+@pass_session
+@ensure_controller
+def transfer(session):
+    """Transfer program to PLC runtim."""
+    def processor():
+        return session.controller.transfer_project()
+    return processor
+
+@cli.command()
+@pass_session
+@ensure_controller
+def run(session):
+    """Run program already present in PLC. """
+    def processor():
+        return session.controller.run_project()
+    return processor
+
+@cli.command()
+@pass_session
+@ensure_controller
+def stop(session):
+    """Stop program running in PLC. """
+    def processor():
+        return session.controller.stop_project()
+    return processor
+
+
+@cli.result_callback()
+@pass_session
+def process_pipeline(session, processors, **kwargs):
+    ret = 0
+    for processor in processors:
+        ret = processor()
+        if ret != 0:
+            if len(processors) > 1 :
+                click.echo("Command sequence aborted")
+            break
+
+    if session.keep:
+        click.echo("Press Ctrl+C to quit")
+        try:
+            while True:
+                session.controller.UpdateMethodsFromPLCStatus()
+                time.sleep(0.5)
+        except KeyboardInterrupt:
+            pass
+
+    session.controller.finish()
+
+    return ret
+
+if __name__ == '__main__':
+    cli()
+
--- a/Beremiz_service.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/Beremiz_service.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,8 +24,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
 import sys
 import getopt
@@ -33,10 +31,9 @@
 import shlex
 import traceback
 import threading
-from threading import Thread, Semaphore, Lock, currentThread
-from builtins import str as text
-from past.builtins import execfile
-from six.moves import builtins
+from threading import Thread, Semaphore, Lock, current_thread
+import builtins
+from functools import partial
 
 import runtime
 from runtime.PyroServer import PyroServer
@@ -211,19 +208,7 @@
     # Define locale domain
     loc.AddCatalog(domain)
 
-    global default_locale
-    default_locale = locale.getdefaultlocale()[1]
-
-    # sys.stdout.encoding = default_locale
-    # if Beremiz_service is started from Beremiz IDE
-    # sys.stdout.encoding is None (that means 'ascii' encoding').
-    # And unicode string returned by wx.GetTranslation() are
-    # automatically converted to 'ascii' string.
-    def unicode_translation(message):
-        return wx.GetTranslation(message).encode(default_locale)
-
-    builtins.__dict__['_'] = unicode_translation
-    # builtins.__dict__['_'] = wx.GetTranslation
+    builtins.__dict__['_'] = wx.GetTranslation
 
 
 # Life is hard... have a candy.
@@ -238,11 +223,9 @@
 
     if havewx:
         import re
-
-        if wx.VERSION >= (3, 0, 0):
-            app = wx.App(redirect=False)
-        else:
-            app = wx.PySimpleApp(redirect=False)
+        import wx.adv
+
+        app = wx.App(redirect=False)
         app.SetTopWindow(wx.Frame(None, -1))
 
         default_locale = None
@@ -279,19 +262,19 @@
             def SetTests(self, tests):
                 self.Tests = tests
 
-        class BeremizTaskBarIcon(wx.TaskBarIcon):
-            TBMENU_START = wx.NewId()
-            TBMENU_STOP = wx.NewId()
-            TBMENU_CHANGE_NAME = wx.NewId()
-            TBMENU_CHANGE_PORT = wx.NewId()
-            TBMENU_CHANGE_INTERFACE = wx.NewId()
-            TBMENU_LIVE_SHELL = wx.NewId()
-            TBMENU_WXINSPECTOR = wx.NewId()
-            TBMENU_CHANGE_WD = wx.NewId()
-            TBMENU_QUIT = wx.NewId()
+        class BeremizTaskBarIcon(wx.adv.TaskBarIcon):
+            TBMENU_START = wx.NewIdRef()
+            TBMENU_STOP = wx.NewIdRef()
+            TBMENU_CHANGE_NAME = wx.NewIdRef()
+            TBMENU_CHANGE_PORT = wx.NewIdRef()
+            TBMENU_CHANGE_INTERFACE = wx.NewIdRef()
+            TBMENU_LIVE_SHELL = wx.NewIdRef()
+            TBMENU_WXINSPECTOR = wx.NewIdRef()
+            TBMENU_CHANGE_WD = wx.NewIdRef()
+            TBMENU_QUIT = wx.NewIdRef()
 
             def __init__(self, pyroserver):
-                wx.TaskBarIcon.__init__(self)
+                wx.adv.TaskBarIcon.__init__(self)
                 self.pyroserver = pyroserver
                 # Set the image
                 self.UpdateIcon(None)
@@ -339,7 +322,7 @@
                 elif "wxGTK" in wx.PlatformInfo:
                     img = img.Scale(22, 22)
                 # wxMac can be any size upto 128x128, so leave the source img alone....
-                icon = wx.IconFromBitmap(img.ConvertToBitmap())
+                icon = wx.Icon(img.ConvertToBitmap())
                 return icon
 
             def OnTaskBarStartPLC(self, evt):
@@ -361,7 +344,7 @@
 
             def OnTaskBarChangePort(self, evt):
                 dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
-                dlg.SetTests([(text.isdigit, _("Port number must be an integer!")), (lambda port: 0 <= int(port) <= 65535, _("Port number must be 0 <= port <= 65535!"))])
+                dlg.SetTests([(str.isdigit, _("Port number must be an integer!")), (lambda port: 0 <= int(port) <= 65535, _("Port number must be 0 <= port <= 65535!"))])
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.port = int(dlg.GetValue())
                     self.pyroserver.Restart()
@@ -376,7 +359,7 @@
                 _servicename = self.pyroserver.servicename
                 _servicename = '' if _servicename is None else _servicename
                 dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=_servicename)
-                dlg.SetTests([(lambda name: len(name) is not 0, _("Name must not be null!"))])
+                dlg.SetTests([(lambda name: len(name) != 0, _("Name must not be null!"))])
                 if dlg.ShowModal() == wx.ID_OK:
                     self.pyroserver.servicename = dlg.GetValue()
                     self.pyroserver.Restart()
@@ -426,7 +409,12 @@
             if havewx:
                 from twisted.internet import wxreactor
                 wxreactor.install()
-            from twisted.internet import reactor
+                from twisted.internet import reactor
+                reactor.registerWxApp(app)
+            else:
+                # from twisted.internet import pollreactor
+                # pollreactor.install()
+                from twisted.internet import reactor
 
             havetwisted = True
         except ImportError:
@@ -435,13 +423,6 @@
 
 pyruntimevars = {}
 
-if havetwisted:
-    if havewx:
-        reactor.registerWxApp(app)
-
-twisted_reactor_thread_id = None
-ui_thread = None
-
 if havewx:
     wx_eval_lock = Semaphore(0)
 
@@ -455,24 +436,18 @@
         obj.res = default_evaluator(tocall, *args, **kwargs)
         wx_eval_lock.release()
 
+    main_thread_id = current_thread().ident
     def evaluator(tocall, *args, **kwargs):
-        # To prevent deadlocks, check if current thread is not one of the UI
-        # UI threads can be either the one from WX main loop or
-        # worker thread from twisted "threadselect" reactor
-        current_id = currentThread().ident
-
-        if ui_thread is not None \
-            and ui_thread.ident != current_id \
-            and (not havetwisted or (
-                    twisted_reactor_thread_id is not None
-                    and twisted_reactor_thread_id != current_id)):
-
+        # To prevent deadlocks, check if current thread is not one already main
+        current_id = current_thread().ident
+
+        if main_thread_id != current_id:
             o = type('', (object,), dict(call=(tocall, args, kwargs), res=None))
             wx.CallAfter(wx_evaluator, o)
             wx_eval_lock.acquire()
             return o.res
         else:
-            # avoid dead lock if called from the wx mainloop
+            # avoid dead lock if called from main : do job immediately
             return default_evaluator(tocall, *args, **kwargs)
 else:
     evaluator = default_evaluator
@@ -527,7 +502,7 @@
 # Load extensions
 for extention_file, extension_folder in extensions:
     sys.path.append(extension_folder)
-    execfile(os.path.join(extension_folder, extention_file), locals())
+    exec(compile(open(os.path.join(extension_folder, extention_file), "rb").read(), os.path.join(extension_folder, extention_file), 'exec'), locals())
 
 # Service name is used as an ID for stunnel's PSK
 # Some extension may set 'servicename' to a computed ID or Serial Number
@@ -548,7 +523,6 @@
         try:
             website = NS.RegisterWebsite(interface, webport)
             pyruntimevars["website"] = website
-            statuschange.append(NS.website_statuslistener_factory(website))
         except Exception:
             LogMessageAndException(_("Nevow Web service failed. "))
 
@@ -559,13 +533,15 @@
         except Exception:
             LogMessageAndException(_("WAMP client startup failed. "))
 
+pyro_thread = None
+
 def FirstWorkerJob():
     """
     RPC through pyro/wamp/UI may lead to delegation to Worker,
     then this function ensures that Worker is already
     created when pyro starts
     """
-    global pyro_thread, pyroserver, ui_thread, reactor, twisted_reactor_thread_id
+    global pyro_thread, pyroserver
 
     pyro_thread_started = Lock()
     pyro_thread_started.acquire()
@@ -582,46 +558,63 @@
 
     # Beremiz IDE detects LOCAL:// runtime is ready by looking
     # for self.workdir in the daemon's stdout.
-    sys.stdout.write(_("Current working directory :") + WorkingDir + "\n")
-    sys.stdout.flush()
-
-    if not (havetwisted or havewx):
-        return
+    if sys.stdout:
+        sys.stdout.write(_("Current working directory :") + WorkingDir + "\n")
+        sys.stdout.flush()
+
+    runtime.GetPLCObjectSingleton().AutoLoad(autostart)
+
+if havetwisted and havewx:
+
+    waker_func = wx.CallAfter
+
+    # This orders ui loop to signal when ready on Stdout
+    waker_func(print,"UI thread started successfully.")
+
+    # interleaved worker copes with wxreactor by delegating all asynchronous
+    # calls to wx's mainloop
+    runtime.MainWorker.interleave(waker_func, reactor.stop, FirstWorkerJob)
+
+    try:
+        reactor.run(installSignalHandlers=False)
+    except KeyboardInterrupt:
+        pass
+
+    runtime.MainWorker.stop()
+
+elif havewx:
+
+    try:
+        app.MainLoop
+    except KeyboardInterrupt:
+        pass
+
+elif havetwisted:
 
     ui_thread_started = Lock()
     ui_thread_started.acquire()
-    if havetwisted:
-        # reactor._installSignalHandlersAgain()
-        def ui_thread_target():
-            # FIXME: had to disable SignaHandlers install because
-            # signal not working in non-main thread
-            reactor.run(installSignalHandlers=False)
-    else:
-        ui_thread_target = app.MainLoop
-
-    ui_thread = Thread(target=ui_thread_target, name="UIThread")
+
+    reactor.callLater(0, ui_thread_started.release)
+
+    ui_thread = Thread(
+        target=partial(reactor.run, installSignalHandlers=False),
+        name="UIThread")
     ui_thread.start()
 
-    # This order ui loop to unblock main thread when ready.
-    if havetwisted:
-        def signal_uithread_started():
-            global twisted_reactor_thread_id
-            twisted_reactor_thread_id = currentThread().ident
-            ui_thread_started.release()
-        reactor.callLater(0, signal_uithread_started)
-    else:
-        wx.CallAfter(ui_thread_started.release)
-
-    # Wait for ui thread to be effective
     ui_thread_started.acquire()
     print("UI thread started successfully.")
-
-    runtime.GetPLCObjectSingleton().AutoLoad(autostart)
-
-try:
-    runtime.MainWorker.runloop(FirstWorkerJob)
-except KeyboardInterrupt:
-    pass
+    try:
+        # blocking worker loop
+        runtime.MainWorker.runloop(FirstWorkerJob)
+    except KeyboardInterrupt:
+        pass
+else:
+    try:
+        # blocking worker loop
+        runtime.MainWorker.runloop(FirstWorkerJob)
+    except KeyboardInterrupt:
+        pass
+
 
 pyroserver.Quit()
 pyro_thread.join()
@@ -635,9 +628,9 @@
 
 if havetwisted:
     reactor.stop()
-    ui_thread.join()
+    if not havewx:
+        ui_thread.join()
 elif havewx:
     app.ExitMainLoop()
-    ui_thread.join()
 
 sys.exit(0)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CLIController.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+from functools import wraps
+from threading import Timer
+from datetime import datetime
+
+import click
+
+import fake_wx
+
+from ProjectController import ProjectController
+from LocalRuntimeMixin import LocalRuntimeMixin
+from runtime.loglevels import LogLevelsCount, LogLevels
+
+
+class Log:
+
+    def __init__(self):
+        self.crlfpending = False
+
+    def write(self, s):
+        if s:
+            if self.crlfpending:
+                sys.stdout.write("\n")
+            sys.stdout.write(s)
+            sys.stdout.flush()
+            self.crlfpending = 0
+
+    def write_error(self, s):
+        if s:
+            self.write("Error: "+s)
+
+    def write_warning(self, s):
+        if s:
+            self.write("Warning: "+s)
+
+    def flush(self):
+        sys.stdout.flush()
+        
+    def isatty(self):
+        return False
+
+    def progress(self, s):
+        if s:
+            sys.stdout.write(s+"\r")
+            self.crlfpending = True
+
+
+def with_project_loaded(func):
+    @wraps(func)
+    def func_wrapper(self, *args, **kwargs):
+        if not self.HasOpenedProject():
+            if self.check_and_load_project():
+                return 1 
+            self.apply_config()
+        return func(self, *args, **kwargs)
+
+    return func_wrapper
+
+def connected(func):
+    @wraps(func)
+    def func_wrapper(self, *args, **kwargs):
+        if self._connector is None:
+            if self.session.uri:
+                self.BeremizRoot.setURI_location(self.session.uri)
+            if not self._Connect():
+                return 1
+        return func(self, *args, **kwargs)
+
+    return func_wrapper
+
+class CLIController(LocalRuntimeMixin, ProjectController):
+    def __init__(self, session):
+        self.session = session
+        log = Log()
+        LocalRuntimeMixin.__init__(self, log, use_gui=False)
+        ProjectController.__init__(self, None, log)
+
+    def _SetConnector(self, connector, update_status=True):
+        self._connector = connector
+        self.previous_log_count = [None]*LogLevelsCount
+        if connector is None and update_status:
+                self.UpdateMethodsFromPLCStatus()
+
+    def UpdatePLCLog(self, log_count):
+        connector = self._connector
+        new_messages = []
+        if connector:
+            for level, count, prev in zip(
+                range(LogLevelsCount), log_count, self.previous_log_count):
+                if count is not None and prev != count:
+                    if prev is None:
+                        dump_end = max(-1, count - 10)
+                    else:
+                        dump_end = prev - 1
+                    for msgidx in range(count-1, dump_end, -1):
+                        message = connector.GetLogMessage(level, msgidx)
+                        if message is not None:
+                            msg, _tick, tv_sec, tv_nsec = message
+                            date = datetime.utcfromtimestamp(tv_sec + tv_nsec * 1e-9)
+                            txt = "%s at %s: %s\n" % (LogLevels[level], date.isoformat(' '), msg)
+                            new_messages.append((date,txt))
+                        else:
+                            break
+                self.previous_log_count[level] = count
+            new_messages.sort()
+            for date, txt in new_messages:
+                self.logger.write(txt)
+
+    def check_and_load_project(self):
+        if not os.path.isdir(self.session.project_home):
+            self.logger.write_error(
+                _("\"%s\" is not a valid Beremiz project\n") % self.session.project_home)
+            return True
+
+        if not os.path.isabs(self.session.project_home):
+            self.session.project_home = os.path.join(os.getcwd(), self.session.project_home)
+
+        errmsg, error = self.LoadProject(self.session.project_home, self.session.buildpath)
+        if error:
+            self.logger.write_error(errmsg)
+            return True
+
+    def apply_config(self):
+        for k,v in self.session.config:
+            self.SetParamsAttribute("BeremizRoot."+k, v)
+
+    @with_project_loaded
+    def build_project(self, target):
+
+        if target:
+            self.SetParamsAttribute("BeremizRoot.TargetType", target)
+            
+        return 0 if self._Build() else 1
+
+    @with_project_loaded
+    @connected
+    def transfer_project(self):
+
+        return 0 if self._Transfer() else 1
+
+    @with_project_loaded
+    @connected
+    def run_project(self):
+
+        return 0 if self._Run() else 1
+        
+    @with_project_loaded
+    @connected
+    def stop_project(self):
+
+        return 0 if self._Stop() else 1
+        
+
+    def finish(self):
+
+        self._Disconnect()
+
+        if not self.session.keep:
+            self.KillLocalRuntime()
+
+
--- a/CodeFileTreeNode.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/CodeFileTreeNode.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,11 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import os
 import re
 import traceback
-from builtins import str as text
 from copy import deepcopy
 
 from lxml import etree
@@ -128,7 +126,7 @@
                     self.GetCTRoot().logger.write_warning(XSDSchemaErrorMessage.format(a1=fname, a2=lnum, a3=src))
                 self.CreateCodeFileBuffer(True)
             except Exception as exc:
-                msg = _("Couldn't load confnode parameters {a1} :\n {a2}").format(a1=self.CTNName(), a2=text(exc))
+                msg = _("Couldn't load confnode parameters {a1} :\n {a2}").format(a1=self.CTNName(), a2=str(exc))
                 self.GetCTRoot().logger.write_error(msg)
                 self.GetCTRoot().logger.write_error(traceback.format_exc())
                 raise Exception
@@ -195,7 +193,7 @@
             self.CodeFile,
             pretty_print=True,
             xml_declaration=True,
-            encoding='utf-8'))
+            encoding='utf-8').decode())
         xmlfile.close()
 
         self.MarkCodeFileAsSaved()
--- a/ConfigTreeNode.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/ConfigTreeNode.py	Wed Oct 04 16:24:00 2023 +0200
@@ -31,15 +31,13 @@
 - ... TODO : document
 """
 
-from __future__ import absolute_import
+
 import os
 import traceback
 import types
 import shutil
 from operator import add
 from functools import reduce
-from builtins import str as text
-from past.builtins import execfile
 
 from lxml import etree
 
@@ -133,6 +131,15 @@
     def CTNTestModified(self):
         return self.ChangesToSave
 
+    def CTNMarkModified(self):
+        oldChangesToSave = self.ChangesToSave
+        self.ChangesToSave = True
+        if not oldChangesToSave:
+            appframe = self.GetCTRoot().AppFrame
+            if appframe is not None:
+                appframe.RefreshTitle()
+                appframe.RefreshPageTitles()
+
     def ProjectTestModified(self):
         """
         recursively check modified status
@@ -203,22 +210,22 @@
 
             # generate XML for base XML parameters controller of the confnode
             if self.MandatoryParams:
-                BaseXMLFile = open(self.ConfNodeBaseXmlFilePath(), 'w')
+                BaseXMLFile = open(self.ConfNodeBaseXmlFilePath(), 'w', encoding='utf-8')
                 BaseXMLFile.write(etree.tostring(
                     self.MandatoryParams[1],
                     pretty_print=True,
                     xml_declaration=True,
-                    encoding='utf-8'))
+                    encoding='utf-8').decode())
                 BaseXMLFile.close()
 
             # generate XML for XML parameters controller of the confnode
             if self.CTNParams:
-                XMLFile = open(self.ConfNodeXmlFilePath(), 'w')
+                XMLFile = open(self.ConfNodeXmlFilePath(), 'w', encoding='utf-8')
                 XMLFile.write(etree.tostring(
                     self.CTNParams[1],
                     pretty_print=True,
                     xml_declaration=True,
-                    encoding='utf-8'))
+                    encoding='utf-8').decode())
                 XMLFile.close()
 
             # Call the confnode specific OnCTNSave method
@@ -285,7 +292,7 @@
         LDFLAGS = []
         if CTNLDFLAGS is not None:
             # LDFLAGS can be either string
-            if isinstance(CTNLDFLAGS, (str, text)):
+            if isinstance(CTNLDFLAGS, str):
                 LDFLAGS += [CTNLDFLAGS]
             # or list of strings
             elif isinstance(CTNLDFLAGS, list):
@@ -310,7 +317,7 @@
         return LocationCFilesAndCFLAGS, LDFLAGS, extra_files
 
     def IterChildren(self):
-        for _CTNType, Children in self.Children.items():
+        for _CTNType, Children in list(self.Children.items()):
             for CTNInstance in Children:
                 yield CTNInstance
 
@@ -319,7 +326,7 @@
         ordered = [(chld.BaseParams.getIEC_Channel(), chld) for chld in self.IterChildren()]
         if ordered:
             ordered.sort()
-            return zip(*ordered)[1]
+            return list(zip(*ordered))[1]
         else:
             return []
 
@@ -471,7 +478,7 @@
         return None
 
     def GetView(self, onlyopened=False):
-        if self._View is None and not onlyopened and self.EditorType is not None:
+        if not self._View and not onlyopened and self.EditorType is not None:
             app_frame = self.GetCTRoot().AppFrame
             self._View = self.EditorType(app_frame.TabsOpened, self, app_frame)
 
@@ -534,8 +541,8 @@
         """
         # reorganize self.CTNChildrenTypes tuples from (name, CTNClass, Help)
         # to ( name, (CTNClass, Help)), an make a dict
-        transpose = zip(*self.CTNChildrenTypes)
-        CTNChildrenTypes = dict(zip(transpose[0], zip(transpose[1], transpose[2])))
+        transpose = list(zip(*self.CTNChildrenTypes))
+        CTNChildrenTypes = dict(list(zip(transpose[0], list(zip(transpose[1], transpose[2])))))
         # Check that adding this confnode is allowed
         try:
             CTNClass, CTNHelp = CTNChildrenTypes[CTNType]
@@ -551,9 +558,12 @@
         ChildrenWithSameClass = self.Children.setdefault(CTNType, list())
         # Check count
         if getattr(CTNClass, "CTNMaxCount", None) and len(ChildrenWithSameClass) >= CTNClass.CTNMaxCount:
-            raise Exception(
-                _("Max count ({a1}) reached for this confnode of type {a2} ").
-                format(a1=CTNClass.CTNMaxCount, a2=CTNType))
+
+            msg = _("Max count ({a1}) reached for this confnode of type {a2} ").format(
+                    a1=CTNClass.CTNMaxCount, a2=CTNType)
+            self.GetCTRoot().logger.write_warning(msg)
+
+            return None
 
         # create the final class, derived of provided confnode and template
         class FinalCTNClass(CTNClass, ConfigTreeNode):
@@ -623,7 +633,7 @@
     def LoadXMLParams(self, CTNName=None):
         methode_name = os.path.join(self.CTNPath(CTNName), "methods.py")
         if os.path.isfile(methode_name):
-            execfile(methode_name)
+            exec(compile(open(methode_name, "rb").read(), methode_name, 'exec'))
 
         ConfNodeName = CTNName if CTNName is not None else self.CTNName()
 
@@ -638,7 +648,7 @@
                 self.MandatoryParams = ("BaseParams", self.BaseParams)
                 basexmlfile.close()
             except Exception as exc:
-                msg = _("Couldn't load confnode base parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=text(exc))
+                msg = _("Couldn't load confnode base parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=str(exc))
                 self.GetCTRoot().logger.write_error(msg)
                 self.GetCTRoot().logger.write_error(traceback.format_exc())
 
@@ -655,7 +665,7 @@
                 self.CTNParams = (name, obj)
                 xmlfile.close()
             except Exception as exc:
-                msg = _("Couldn't load confnode parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=text(exc))
+                msg = _("Couldn't load confnode parameters {a1} :\n {a2}").format(a1=ConfNodeName, a2=str(exc))
                 self.GetCTRoot().logger.write_error(msg)
                 self.GetCTRoot().logger.write_error(traceback.format_exc())
 
@@ -668,7 +678,7 @@
                 try:
                     self.CTNAddChild(pname, ptype)
                 except Exception as exc:
-                    msg = _("Could not add child \"{a1}\", type {a2} :\n{a3}\n").format(a1=pname, a2=ptype, a3=text(exc))
+                    msg = _("Could not add child \"{a1}\", type {a2} :\n{a3}\n").format(a1=pname, a2=ptype, a3=str(exc))
                     self.GetCTRoot().logger.write_error(msg)
                     self.GetCTRoot().logger.write_error(traceback.format_exc())
 
--- a/IDEFrame.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/IDEFrame.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,18 +22,15 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
-from __future__ import division
-import sys
+
+from functools import cmp_to_key
+import pickle
+from operator import eq
 import base64
-from future.builtins import \
-    round, \
-    str as text
 
 import wx
 import wx.grid
 import wx.aui
-from six.moves import cPickle, xrange
 
 from editors.EditorPanel import EditorPanel
 from editors.SFCViewer import SFC_Viewer
@@ -60,7 +57,7 @@
     ID_PLCOPENEDITORTABSOPENED, ID_PLCOPENEDITORTABSOPENED,
     ID_PLCOPENEDITOREDITORMENUTOOLBAR, ID_PLCOPENEDITOREDITORTOOLBAR,
     ID_PLCOPENEDITORPROJECTPANEL,
-] = [wx.NewId() for _init_ctrls in range(17)]
+] = [wx.NewIdRef() for _init_ctrls in range(17)]
 
 # Define PLCOpenEditor EditMenu extra items id
 [
@@ -69,14 +66,14 @@
     ID_PLCOPENEDITOREDITMENUADDPROGRAM, ID_PLCOPENEDITOREDITMENUADDCONFIGURATION,
     ID_PLCOPENEDITOREDITMENUFINDNEXT, ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
     ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, ID_PLCOPENEDITOREDITMENUADDRESOURCE
-] = [wx.NewId() for _init_coll_EditMenu_Items in range(10)]
+] = [wx.NewIdRef() for _init_coll_EditMenu_Items in range(10)]
 
 # Define PLCOpenEditor DisplayMenu extra items id
 [
     ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE,
     ID_PLCOPENEDITORDISPLAYMENUSWITCHPERSPECTIVE,
     ID_PLCOPENEDITORDISPLAYMENUFULLSCREEN,
-] = [wx.NewId() for _init_coll_DisplayMenu_Items in range(3)]
+] = [wx.NewIdRef() for _init_coll_DisplayMenu_Items in range(3)]
 
 # -------------------------------------------------------------------------------
 #                            EditorToolBar definitions
@@ -93,7 +90,7 @@
     ID_PLCOPENEDITOREDITORTOOLBARSTEP, ID_PLCOPENEDITOREDITORTOOLBARTRANSITION,
     ID_PLCOPENEDITOREDITORTOOLBARACTIONBLOCK, ID_PLCOPENEDITOREDITORTOOLBARDIVERGENCE,
     ID_PLCOPENEDITOREDITORTOOLBARJUMP, ID_PLCOPENEDITOREDITORTOOLBARMOTION,
-] = [wx.NewId() for _init_coll_DefaultEditorToolBar_Items in range(18)]
+] = [wx.NewIdRef() for _init_coll_DefaultEditorToolBar_Items in range(18)]
 
 
 # -------------------------------------------------------------------------------
@@ -102,26 +99,25 @@
 
 
 def EncodeFileSystemPath(path, use_base64=True):
-    path = path.encode(sys.getfilesystemencoding())
     if use_base64:
-        return base64.encodestring(path)
+        path = base64.b64encode(path.encode()).decode()
     return path
 
 
 def DecodeFileSystemPath(path, is_base64=True):
     if is_base64:
-        path = base64.decodestring(path)
-    return text(path, sys.getfilesystemencoding())
+        path = base64.b64decode(path.encode()).decode()
+    return path
 
 
 def AppendMenu(parent, help, kind, text, id=wx.ID_ANY):
-    return parent.Append(help=help, kind=kind, text=text, id=id)
+    return parent.Append(wx.MenuItem(helpString=help, kind=kind, text=text, id=id))
 
 
 [
     TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
     POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-] = range(10)
+] = list(range(10))
 
 
 def GetShortcutKeyCallbackFunction(viewer_function):
@@ -141,7 +137,7 @@
         name = self.ProjectTree.GetItemText(selected)
         if check_function is None or check_function(name):
             if parent_type is not None:
-                item_infos = self.ProjectTree.GetPyData(selected)
+                item_infos = self.ProjectTree.GetItemData(selected)
                 parent_name = item_infos["tagname"].split("::")[1]
                 remove_function(self.Controler, parent_name, name)
             else:
@@ -161,7 +157,8 @@
     for tab in tabs:
         if tab["pos"][0] == rect.x:
             others = [t for t in tabs if t != tab]
-            others.sort(lambda x, y: cmp(x["pos"][0], y["pos"][0]))
+            others.sort(key=cmp_to_key(lambda x, y: eq(x["pos"][0],
+                                                       y["pos"][0])))
             for other in others:
                 if other["pos"][1] == tab["pos"][1] and \
                    other["size"][1] == tab["size"][1] and \
@@ -176,7 +173,8 @@
 
         elif tab["pos"][1] == rect.y:
             others = [t for t in tabs if t != tab]
-            others.sort(lambda x, y: cmp(x["pos"][1], y["pos"][1]))
+            others.sort(key=cmp_to_key(lambda x, y: eq(x["pos"][1],
+                                                       y["pos"][1])))
             for other in others:
                 if other["pos"][0] == tab["pos"][0] and \
                    other["size"][0] == tab["size"][0] and \
@@ -343,59 +341,59 @@
         }
 
     def _init_coll_MenuBar_Menus(self, parent):
-        parent.Append(menu=self.FileMenu, title=_(u'&File'))
-        parent.Append(menu=self.EditMenu, title=_(u'&Edit'))
-        parent.Append(menu=self.DisplayMenu, title=_(u'&Display'))
-        parent.Append(menu=self.HelpMenu, title=_(u'&Help'))
+        parent.Append(menu=self.FileMenu, title=_('&File'))
+        parent.Append(menu=self.EditMenu, title=_('&Edit'))
+        parent.Append(menu=self.DisplayMenu, title=_('&Display'))
+        parent.Append(menu=self.HelpMenu, title=_('&Help'))
 
     def _init_coll_FileMenu_Items(self, parent):
         pass
 
     def _init_coll_AddMenu_Items(self, parent, add_config=True):
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDDATATYPE,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Data Type'))
+                   kind=wx.ITEM_NORMAL, text=_('&Data Type'))
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDFUNCTION,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Function'))
+                   kind=wx.ITEM_NORMAL, text=_('&Function'))
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDFUNCTIONBLOCK,
-                   kind=wx.ITEM_NORMAL, text=_(u'Function &Block'))
+                   kind=wx.ITEM_NORMAL, text=_('Function &Block'))
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDPROGRAM,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Program'))
+                   kind=wx.ITEM_NORMAL, text=_('&Program'))
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDRESOURCE,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Resource'))
+                   kind=wx.ITEM_NORMAL, text=_('&Resource'))
         if add_config:
             AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUADDCONFIGURATION,
-                       kind=wx.ITEM_NORMAL, text=_(u'&Configuration'))
+                       kind=wx.ITEM_NORMAL, text=_('&Configuration'))
 
     def _init_coll_EditMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_UNDO,
-                   kind=wx.ITEM_NORMAL, text=_(u'Undo') + '\tCTRL+Z')
+                   kind=wx.ITEM_NORMAL, text=_('Undo') + '\tCTRL+Z')
         AppendMenu(parent, help='', id=wx.ID_REDO,
-                   kind=wx.ITEM_NORMAL, text=_(u'Redo') + '\tCTRL+Y')
+                   kind=wx.ITEM_NORMAL, text=_('Redo') + '\tCTRL+Y')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_CUT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Cut') + '\tCTRL+X')
+                   kind=wx.ITEM_NORMAL, text=_('Cut') + '\tCTRL+X')
         AppendMenu(parent, help='', id=wx.ID_COPY,
-                   kind=wx.ITEM_NORMAL, text=_(u'Copy') + '\tCTRL+C')
+                   kind=wx.ITEM_NORMAL, text=_('Copy') + '\tCTRL+C')
         AppendMenu(parent, help='', id=wx.ID_PASTE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Paste') + '\tCTRL+V')
+                   kind=wx.ITEM_NORMAL, text=_('Paste') + '\tCTRL+V')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_FIND,
-                   kind=wx.ITEM_NORMAL, text=_(u'Find') + '\tCTRL+F')
+                   kind=wx.ITEM_NORMAL, text=_('Find') + '\tCTRL+F')
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDNEXT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Find Next') + '\tCTRL+K')
+                   kind=wx.ITEM_NORMAL, text=_('Find Next') + '\tCTRL+K')
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUFINDPREVIOUS,
-                   kind=wx.ITEM_NORMAL, text=_(u'Find Previous') + '\tCTRL+SHIFT+K')
+                   kind=wx.ITEM_NORMAL, text=_('Find Previous') + '\tCTRL+SHIFT+K')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Search in Project') + '\tCTRL+SHIFT+F')
+                   kind=wx.ITEM_NORMAL, text=_('Search in Project') + '\tCTRL+SHIFT+F')
         parent.AppendSeparator()
         add_menu = wx.Menu(title='')
         self._init_coll_AddMenu_Items(add_menu)
-        parent.AppendMenu(wx.ID_ADD, _(u"&Add Element"), add_menu)
+        self.AddMenuItem = parent.AppendSubMenu(add_menu, _("&Add Element"))
         AppendMenu(parent, help='', id=wx.ID_SELECTALL,
-                   kind=wx.ITEM_NORMAL, text=_(u'Select All') + '\tCTRL+A')
+                   kind=wx.ITEM_NORMAL, text=_('Select All') + '\tCTRL+A')
         AppendMenu(parent, help='', id=wx.ID_DELETE,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Delete'))
+                   kind=wx.ITEM_NORMAL, text=_('&Delete'))
         self.Bind(wx.EVT_MENU, self.OnUndoMenu, id=wx.ID_UNDO)
         self.Bind(wx.EVT_MENU, self.OnRedoMenu, id=wx.ID_REDO)
         # self.Bind(wx.EVT_MENU, self.OnEnableUndoRedoMenu, id=ID_PLCOPENEDITOREDITMENUENABLEUNDOREDO)
@@ -424,41 +422,42 @@
         self.Bind(wx.EVT_MENU, self.OnSelectAllMenu, id=wx.ID_SELECTALL)
         self.Bind(wx.EVT_MENU, self.OnDeleteMenu, id=wx.ID_DELETE)
 
-        self.AddToMenuToolBar([(wx.ID_UNDO, "undo", _(u'Undo'), None),
-                               (wx.ID_REDO, "redo", _(u'Redo'), None),
+        self.AddToMenuToolBar([(wx.ID_UNDO, "undo", _('Undo'), None),
+                               (wx.ID_REDO, "redo", _('Redo'), None),
                                None,
-                               (wx.ID_CUT, "cut", _(u'Cut'), None),
-                               (wx.ID_COPY, "copy", _(u'Copy'), None),
-                               (wx.ID_PASTE, "paste", _(u'Paste'), None),
+                               (wx.ID_CUT, "cut", _('Cut'), None),
+                               (wx.ID_COPY, "copy", _('Copy'), None),
+                               (wx.ID_PASTE, "paste", _('Paste'), None),
                                None,
-                               (ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, "find", _(u'Search in Project'), None),
-                               (ID_PLCOPENEDITORDISPLAYMENUFULLSCREEN, "fullscreen", _(u'Toggle fullscreen mode'), None)])
+                               (ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, "find", _('Search in Project'), None),
+                               (ID_PLCOPENEDITORDISPLAYMENUFULLSCREEN, "fullscreen", _('Toggle fullscreen mode'), None)])
 
     def _init_coll_DisplayMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_REFRESH,
-                   kind=wx.ITEM_NORMAL, text=_(u'Refresh') + '\tCTRL+R')
+                   kind=wx.ITEM_NORMAL, text=_('Refresh') + '\tCTRL+R')
         if self.EnableDebug:
             AppendMenu(parent, help='', id=wx.ID_CLEAR,
-                       kind=wx.ITEM_NORMAL, text=_(u'Clear Errors') + '\tCTRL+K')
+                       kind=wx.ITEM_NORMAL, text=_('Clear Errors') + '\tCTRL+K')
         parent.AppendSeparator()
         zoommenu = wx.Menu(title='')
-        parent.AppendMenu(wx.ID_ZOOM_FIT, _("Zoom"), zoommenu)
+        self.ZoomMenuItem = parent.AppendSubMenu(zoommenu, _("Zoom"))
         for idx, value in enumerate(ZOOM_FACTORS):
             new_item = AppendMenu(zoommenu, help='',
                        kind=wx.ITEM_RADIO, text=str(int(round(value * 100))) + "%")
             self.Bind(wx.EVT_MENU, self.GenerateZoomFunction(idx), new_item)
 
         parent.AppendSeparator()
-        AppendMenu(parent, help='', id=ID_PLCOPENEDITORDISPLAYMENUSWITCHPERSPECTIVE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Switch perspective') + '\tF12')
-        self.Bind(wx.EVT_MENU, self.SwitchPerspective, id=ID_PLCOPENEDITORDISPLAYMENUSWITCHPERSPECTIVE)
+        if wx.VERSION >= (4, 1, 0):
+            AppendMenu(parent, help='', id=ID_PLCOPENEDITORDISPLAYMENUSWITCHPERSPECTIVE,
+                       kind=wx.ITEM_NORMAL, text=_('Switch perspective') + '\tF12')
+            self.Bind(wx.EVT_MENU, self.SwitchPerspective, id=ID_PLCOPENEDITORDISPLAYMENUSWITCHPERSPECTIVE)
 
         AppendMenu(parent, help='', id=ID_PLCOPENEDITORDISPLAYMENUFULLSCREEN,
-                   kind=wx.ITEM_NORMAL, text=_(u'Full screen') + '\tShift-F12')
+                   kind=wx.ITEM_NORMAL, text=_('Full screen') + '\tShift-F12')
         self.Bind(wx.EVT_MENU, self.SwitchFullScrMode, id=ID_PLCOPENEDITORDISPLAYMENUFULLSCREEN)
 
         AppendMenu(parent, help='', id=ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Reset Perspective'))
+                   kind=wx.ITEM_NORMAL, text=_('Reset Perspective'))
         self.Bind(wx.EVT_MENU, self.OnResetPerspective, id=ID_PLCOPENEDITORDISPLAYMENURESETPERSPECTIVE)
 
         self.Bind(wx.EVT_MENU, self.OnRefreshMenu, id=wx.ID_REFRESH)
@@ -569,8 +568,8 @@
 
         self.ProjectPanel = wx.SplitterWindow(
             id=ID_PLCOPENEDITORPROJECTPANEL,
-            name='ProjectPanel', parent=self.LeftNoteBook, point=wx.Point(0, 0),
-            size=wx.Size(0, 0), style=wx.SP_3D)
+            name='ProjectPanel', parent=self.LeftNoteBook,
+            size=wx.Size(0, 0))
 
         self.ProjectTree = CustomTree(id=ID_PLCOPENEDITORPROJECTTREE,
                                       name='ProjectTree',
@@ -617,7 +616,7 @@
 
         MenuToolBar = wx.ToolBar(self, ID_PLCOPENEDITOREDITORMENUTOOLBAR,
                                  wx.DefaultPosition, wx.DefaultSize,
-                                 wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
+                                 wx.TB_FLAT | wx.TB_HORIZONTAL | wx.NO_BORDER)
         MenuToolBar.SetToolBitmapSize(wx.Size(25, 25))
         MenuToolBar.Realize()
         self.Panes["MenuToolBar"] = MenuToolBar
@@ -628,9 +627,10 @@
 
         EditorToolBar = wx.ToolBar(self, ID_PLCOPENEDITOREDITORTOOLBAR,
                                    wx.DefaultPosition, wx.DefaultSize,
-                                   wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
+                                   wx.TB_FLAT | wx.TB_HORIZONTAL | wx.NO_BORDER)
         EditorToolBar.SetToolBitmapSize(wx.Size(25, 25))
         EditorToolBar.AddRadioTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION,
+                                   _("Select"),
                                    GetBitmap("select"),
                                    wx.NullBitmap,
                                    _("Select an object"))
@@ -763,9 +763,6 @@
 
         wx.CallAfter(self.InitFindDialog)
 
-    def __del__(self):
-        self.FindDialog.Destroy()
-
     def InitFindDialog(self):
         self.FindDialog = FindInPouDialog(self)
         self.FindDialog.Hide()
@@ -791,7 +788,7 @@
     # -------------------------------------------------------------------------------
 
     def GetTabInfos(self, tab):
-        for page_name, (page_ref, _page_title) in self.MainTabs.iteritems():
+        for page_name, (page_ref, _page_title) in self.MainTabs.items():
             if page_ref == tab:
                 return ("main", page_name)
         return None
@@ -804,7 +801,7 @@
                     pos = child.GetPosition()
                     tab = {"pos": (pos.x, pos.y), "pages": []}
                     tab_size = child.GetSize()
-                    for page_idx in xrange(child.GetPageCount()):
+                    for page_idx in range(child.GetPageCount()):
                         page = child.GetWindowFromIdx(page_idx)
                         if "size" not in tab:
                             tab["size"] = (tab_size[0], tab_size[1] + page.GetSize()[1])
@@ -812,7 +809,7 @@
                         if tab_infos is not None:
                             tab["pages"].append((tab_infos, page_idx == child.GetActivePage()))
                     tabs.append(tab)
-        tabs.sort(lambda x, y: cmp(x["pos"], y["pos"]))
+        tabs.sort(key=cmp_to_key(lambda x, y: eq(x["pos"], y["pos"])))
         size = notebook.GetSize()
         return ComputeTabsLayout(tabs, wx.Rect(1, 1, size[0] - NOTEBOOK_BORDER, size[1] - NOTEBOOK_BORDER))
 
@@ -870,7 +867,7 @@
             self.AUIManager.LoadPerspective(self.DefaultPerspective["perspective"])
 
             for notebook in [self.LeftNoteBook, self.BottomNoteBook, self.RightNoteBook]:
-                for dummy in xrange(notebook.GetPageCount()):
+                for dummy in range(notebook.GetPageCount()):
                     notebook.RemovePage(0)
 
             notebooks = self.DefaultPerspective["notebooks"]
@@ -884,7 +881,7 @@
     def RestoreLastState(self):
         frame_size = None
         if self.Config.HasEntry("framesize"):
-            frame_size = cPickle.loads(str(self.Config.Read("framesize")))
+            frame_size = pickle.loads(self.Config.Read("framesize").encode())
 
         if frame_size is None:
             self.Maximize()
@@ -893,7 +890,7 @@
 
     def SaveLastState(self):
         if not self.IsMaximized():
-            self.Config.Write("framesize", cPickle.dumps(self.GetClientSize()))
+            self.Config.Write("framesize", pickle.dumps(self.GetClientSize(), 0))
         elif self.Config.HasEntry("framesize"):
             self.Config.DeleteEntry("framesize")
 
@@ -921,12 +918,8 @@
 
         :param elements: List of elements to refresh.
         """
-        try:
-            for element in elements:
-                self.RefreshFunctions[element]()
-        except wx.PyDeadObjectError:
-            # ignore exceptions caused by refresh while quitting
-            pass
+        for element in elements:
+            self.RefreshFunctions[element]()
 
     def OnPageClose(self, event):
         """Callback function when AUINotebook Page closed with CloseButton
@@ -985,7 +978,7 @@
         return self.DrawingMode
 
     def RefreshScaling(self):
-        for i in xrange(self.TabsOpened.GetPageCount()):
+        for i in range(self.TabsOpened.GetPageCount()):
             editor = self.TabsOpened.GetPage(i)
             editor.RefreshScaling()
 
@@ -1017,7 +1010,7 @@
         self.RefreshTabCtrlEvent()
 
     def DeletePage(self, window):
-        for idx in xrange(self.TabsOpened.GetPageCount()):
+        for idx in range(self.TabsOpened.GetPageCount()):
             if self.TabsOpened.GetPage(idx) == window:
                 self.TabsOpened.DeletePage(idx)
                 self.RefreshTabCtrlEvent()
@@ -1027,7 +1020,7 @@
         """Function that fix difference in deleting all tabs between
         wx.Notebook and wx.aui.AUINotebook.
         """
-        for dummy in xrange(self.TabsOpened.GetPageCount()):
+        for dummy in range(self.TabsOpened.GetPageCount()):
             self.TabsOpened.DeletePage(0)
         self.RefreshTabCtrlEvent()
 
@@ -1068,7 +1061,7 @@
             elif answer == wx.ID_CANCEL:
                 return False
 
-        for idx in xrange(self.TabsOpened.GetPageCount()):
+        for idx in range(self.TabsOpened.GetPageCount()):
             window = self.TabsOpened.GetPage(idx)
             if not window.CheckSaveBeforeClosing():
                 return False
@@ -1117,7 +1110,7 @@
             window = self.TabsOpened.GetPage(selected)
             data = wx.PrintDialogData(self.PrintData)
             properties = self.Controler.GetProjectProperties(window.IsDebugging())
-            page_size = map(int, properties["pageSize"])
+            page_size = list(map(int, properties["pageSize"]))
             margins = (self.PageSetupData.GetMarginTopLeft(), self.PageSetupData.GetMarginBottomRight())
             printout = GraphicPrintout(window, page_size, margins, True)
             printout2 = GraphicPrintout(window, page_size, margins, True)
@@ -1141,7 +1134,7 @@
             dialog_data = wx.PrintDialogData(self.PrintData)
             dialog_data.SetToPage(1)
             properties = self.Controler.GetProjectProperties(window.IsDebugging())
-            page_size = map(int, properties["pageSize"])
+            page_size = list(map(int, properties["pageSize"]))
             margins = (self.PageSetupData.GetMarginTopLeft(), self.PageSetupData.GetMarginBottomRight())
             printer = wx.Printer(dialog_data)
             printout = GraphicPrintout(window, page_size, margins)
@@ -1186,7 +1179,7 @@
                                  selected > -1 and self.SearchParams is not None)
             self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
             MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, True)
-            self.EditMenu.Enable(wx.ID_ADD, True)
+            self.AddMenuItem.Enable(True)
             self.EditMenu.Enable(wx.ID_DELETE, True)
             if self.TabsOpened.GetPageCount() > 0:
                 self.EditMenu.Enable(wx.ID_CUT, True)
@@ -1226,11 +1219,11 @@
             self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUFINDPREVIOUS, False)
             self.EditMenu.Enable(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
             MenuToolBar.EnableTool(ID_PLCOPENEDITOREDITMENUSEARCHINPROJECT, False)
-            self.EditMenu.Enable(wx.ID_ADD, False)
+            self.AddMenuItem.Enable( False)
             self.EditMenu.Enable(wx.ID_DELETE, False)
 
     def CloseTabsWithoutModel(self, refresh=True):
-        idxs = range(self.TabsOpened.GetPageCount())
+        idxs = list(range(self.TabsOpened.GetPageCount()))
         idxs.reverse()
         for idx in idxs:
             window = self.TabsOpened.GetPage(idx)
@@ -1301,7 +1294,7 @@
         if window == self.ProjectTree or window is None:
             selected = self.ProjectTree.GetSelection()
             if selected is not None and selected.IsOk():
-                function = self.DeleteFunctions.get(self.ProjectTree.GetPyData(selected)["type"], None)
+                function = self.DeleteFunctions.get(self.ProjectTree.GetItemData(selected)["type"], None)
                 if function is not None:
                     function(self, selected)
                     self.CloseTabsWithoutModel()
@@ -1358,24 +1351,24 @@
                 if selected != -1:
                     window = self.TabsOpened.GetPage(selected)
                     if isinstance(window, Viewer):
-                        self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, True)
-                        zoommenu = self.DisplayMenu.FindItemById(wx.ID_ZOOM_FIT).GetSubMenu()
+                        self.ZoomMenuItem.Enable(True)
+                        zoommenu = self.ZoomMenuItem.GetSubMenu()
                         zoomitem = zoommenu.FindItemByPosition(window.GetScale())
                         zoomitem.Check(True)
                     else:
-                        self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+                        self.ZoomMenuItem.Enable(False)
                 else:
-                    self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+                    self.ZoomMenuItem.Enable(False)
             else:
                 self.DisplayMenu.Enable(wx.ID_REFRESH, False)
-                self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+                self.ZoomMenuItem.Enable(False)
             if self.EnableDebug:
                 self.DisplayMenu.Enable(wx.ID_CLEAR, True)
         else:
             self.DisplayMenu.Enable(wx.ID_REFRESH, False)
             if self.EnableDebug:
                 self.DisplayMenu.Enable(wx.ID_CLEAR, False)
-            self.DisplayMenu.Enable(wx.ID_ZOOM_FIT, False)
+            self.ZoomMenuItem.Enable(False)
 
     def OnRefreshMenu(self, event):
         self.RefreshEditor()
@@ -1413,12 +1406,14 @@
         for child in self.TabsOpened.GetChildren():
             if isinstance(child, wx.aui.AuiTabCtrl):
                 auitabctrl.append(child)
-                if child not in self.AuiTabCtrl:
+                if wx.VERSION >= (4, 1, 0) and child not in self.AuiTabCtrl:
                     child.Bind(wx.EVT_LEFT_DCLICK, self.GetTabsOpenedDClickFunction(child))
         self.AuiTabCtrl = auitabctrl
-        if self.TabsOpened.GetPageCount() == 0:
+        # on wxPython 4.0.7, AuiManager has no "RestorePane" method...
+        if wx.VERSION >= (4, 1, 0) and self.TabsOpened.GetPageCount() == 0:
             pane = self.AUIManager.GetPane(self.TabsOpened)
-            if pane.IsMaximized():
+            # on wxPython 4.1.0, AuiPaneInfo has no "IsMaximized" attribute...
+            if (not hasattr(pane, "IsMaximized")) or pane.IsMaximized():
                 self.AUIManager.RestorePane(pane)
             self.AUIManager.Update()
 
@@ -1477,19 +1472,19 @@
             self._Refresh(FILEMENU, EDITMENU, DISPLAYMENU, EDITORTOOLBAR)
 
     def RefreshEditorNames(self, old_tagname, new_tagname):
-        for i in xrange(self.TabsOpened.GetPageCount()):
+        for i in range(self.TabsOpened.GetPageCount()):
             editor = self.TabsOpened.GetPage(i)
             if editor.GetTagName() == old_tagname:
                 editor.SetTagName(new_tagname)
 
     def IsOpened(self, tagname):
-        for idx in xrange(self.TabsOpened.GetPageCount()):
+        for idx in range(self.TabsOpened.GetPageCount()):
             if self.TabsOpened.GetPage(idx).IsViewing(tagname):
                 return idx
         return None
 
     def RefreshPageTitles(self):
-        for idx in xrange(self.TabsOpened.GetPageCount()):
+        for idx in range(self.TabsOpened.GetPageCount()):
             window = self.TabsOpened.GetPage(idx)
             icon = window.GetIcon()
             if icon is not None:
@@ -1499,17 +1494,25 @@
     def GetTabsOpenedDClickFunction(self, tabctrl):
         def OnTabsOpenedDClick(event):
             pos = event.GetPosition()
-            if tabctrl.TabHitTest(pos.x, pos.y, None):
+            if tabctrl.TabHitTest(pos.x, pos.y):
                 self.SwitchPerspective(event)
             event.Skip()
         return OnTabsOpenedDClick
 
     def SwitchPerspective(self, evt):
+        if not hasattr(self.AUIManager, "MaximizePane"):
+            return
         pane = self.AUIManager.GetPane(self.TabsOpened)
-        if pane.IsMaximized():
+        # on wxPython 4.1.0, AuiPaneInfo has no "IsMaximized" attribute...
+        IsMaximized = pane.IsMaximized() if hasattr(pane, "IsMaximized") \
+            else (self.TabBookIsMaximized if hasattr(self, "TabBookIsMaximized") \
+                else False)
+        if IsMaximized:
             self.AUIManager.RestorePane(pane)
+            self.TabBookIsMaximized = False
         else:
             self.AUIManager.MaximizePane(pane)
+            self.TabBookIsMaximized = True
         self.AUIManager.Update()
 
     def SwitchFullScrMode(self, evt):
@@ -1524,7 +1527,7 @@
         # Extract current selected item tagname
         selected = self.ProjectTree.GetSelection()
         if selected is not None and selected.IsOk():
-            item_infos = self.ProjectTree.GetPyData(selected)
+            item_infos = self.ProjectTree.GetItemData(selected)
             tagname = item_infos.get("tagname", None)
         else:
             tagname = None
@@ -1597,14 +1600,14 @@
             if root is not None and root.IsOk():
                 words = tagname.split("::")
                 result = self.RecursiveProjectTreeItemSelection(
-                    root, zip(words[1:], self.TagNamePartsItemTypes.get(words[0], [])))
+                    root, list(zip(words[1:], self.TagNamePartsItemTypes.get(words[0], []))))
         return result
 
     def RecursiveProjectTreeItemSelection(self, root, items):
         found = False
         item, root_cookie = self.ProjectTree.GetFirstChild(root)
         while item is not None and item.IsOk() and not found:
-            item_infos = self.ProjectTree.GetPyData(item)
+            item_infos = self.ProjectTree.GetItemData(item)
             if (item_infos["name"].split(":")[-1].strip(), item_infos["type"]) == items[0]:
                 if len(items) == 1:
                     self.SelectedItem = item
@@ -1625,7 +1628,7 @@
         selected_item = (self.SelectedItem
                          if self.SelectedItem is not None
                          else event.GetItem())
-        if selected_item.IsOk() and self.ProjectTree.GetPyData(selected_item)["type"] == ITEM_POU:
+        if selected_item.IsOk() and self.ProjectTree.GetItemData(selected_item)["type"] == ITEM_POU:
             block_name = self.ProjectTree.GetItemText(selected_item)
             block_type = self.Controler.GetPouType(block_name)
             if block_type != "program":
@@ -1637,7 +1640,7 @@
 
     def OnProjectTreeItemBeginEdit(self, event):
         selected = event.GetItem()
-        if self.ProjectTree.GetPyData(selected)["type"] in ITEMS_UNEDITABLE:
+        if self.ProjectTree.GetItemData(selected)["type"] in ITEMS_UNEDITABLE:
             event.Veto()
         else:
             event.Skip()
@@ -1654,7 +1657,7 @@
             else:
                 item = event.GetItem()
                 old_name = self.ProjectTree.GetItemText(item)
-                item_infos = self.ProjectTree.GetPyData(item)
+                item_infos = self.ProjectTree.GetItemData(item)
                 if item_infos["type"] == ITEM_PROJECT:
                     self.Controler.SetProjectProperties(name=new_name)
                 elif item_infos["type"] == ITEM_DATATYPE:
@@ -1758,7 +1761,7 @@
 
     def OnProjectTreeItemActivated(self, event):
         selected = event.GetItem()
-        item_infos = self.ProjectTree.GetPyData(selected)
+        item_infos = self.ProjectTree.GetItemData(selected)
         if item_infos["type"] == ITEM_PROJECT:
             self.EditProjectSettings()
         else:
@@ -1770,7 +1773,7 @@
 
     def ProjectTreeItemSelect(self, select_item):
         if select_item is not None and select_item.IsOk():
-            item_infos = self.ProjectTree.GetPyData(select_item)
+            item_infos = self.ProjectTree.GetItemData(select_item)
             if item_infos["type"] in [ITEM_DATATYPE, ITEM_POU,
                                       ITEM_CONFIGURATION, ITEM_RESOURCE,
                                       ITEM_TRANSITION, ITEM_ACTION]:
@@ -1789,7 +1792,7 @@
             pt = wx.Point(event.GetX(), event.GetY())
             item, flags = self.ProjectTree.HitTest(pt)
             if item is not None and item.IsOk() and flags & wx.TREE_HITTEST_ONITEMLABEL:
-                item_infos = self.ProjectTree.GetPyData(item)
+                item_infos = self.ProjectTree.GetItemData(item)
                 if item != self.LastToolTipItem and self.LastToolTipItem is not None:
                     self.ProjectTree.SetToolTip(None)
                     self.LastToolTipItem = None
@@ -1808,7 +1811,7 @@
                     else:
                         block_type = "Action"
                     self.LastToolTipItem = item
-                    wx.CallAfter(self.ProjectTree.SetToolTipString,
+                    wx.CallAfter(self.ProjectTree.SetToolTip,
                                  "%s : %s : %s" % (
                                      block_type, bodytype, item_infos["name"]))
             elif self.LastToolTipItem is not None:
@@ -1817,7 +1820,7 @@
         event.Skip()
 
     def OnProjectTreeItemChanging(self, event):
-        if self.ProjectTree.GetPyData(event.GetItem())["type"] not in ITEMS_UNEDITABLE and self.SelectedItem is None:
+        if self.ProjectTree.GetItemData(event.GetItem())["type"] not in ITEMS_UNEDITABLE and self.SelectedItem is None:
             self.SelectedItem = event.GetItem()
             event.Veto()
         else:
@@ -1886,7 +1889,7 @@
                 if old_selected != openedidx:
                     if old_selected >= 0:
                         self.TabsOpened.GetPage(old_selected).ResetBuffer()
-                for i in xrange(self.TabsOpened.GetPageCount()):
+                for i in range(self.TabsOpened.GetPageCount()):
                     window = self.TabsOpened.GetPage(i)
                     if window == new_window:
                         self.TabsOpened.SetSelection(i)
@@ -1899,7 +1902,7 @@
         self.ProjectTree.SelectItem(item)
         self.ProjectTreeItemSelect(item)
         name = self.ProjectTree.GetItemText(item)
-        item_infos = self.ProjectTree.GetPyData(item)
+        item_infos = self.ProjectTree.GetItemData(item)
 
         menu = None
         if item_infos["type"] in ITEMS_UNEDITABLE + [ITEM_PROJECT]:
@@ -1923,7 +1926,7 @@
                 new_item = AppendMenu(menu, help='', kind=wx.ITEM_NORMAL, text=_("Paste POU"))
                 self.Bind(wx.EVT_MENU, self.OnPastePou, new_item)
                 if self.GetCopyBuffer() is None:
-                    menu.Enable(new_item, False)
+                    new_item.Enable(False)
 
             elif name == "Configurations":
                 menu = wx.Menu(title='')
@@ -1934,30 +1937,30 @@
                 menu = wx.Menu(title='')
                 new_item = AppendMenu(menu, help='', kind=wx.ITEM_NORMAL, text=_("Add Transition"))
                 parent = self.ProjectTree.GetItemParent(item)
-                parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 while parent_type != ITEM_POU:
                     parent = self.ProjectTree.GetItemParent(parent)
-                    parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                    parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 self.Bind(wx.EVT_MENU, self.GenerateAddTransitionFunction(self.ProjectTree.GetItemText(parent)), new_item)
 
             elif name == "Actions":
                 menu = wx.Menu(title='')
                 new_item = AppendMenu(menu, help='', kind=wx.ITEM_NORMAL, text=_("Add Action"))
                 parent = self.ProjectTree.GetItemParent(item)
-                parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 while parent_type != ITEM_POU:
                     parent = self.ProjectTree.GetItemParent(parent)
-                    parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                    parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 self.Bind(wx.EVT_MENU, self.GenerateAddActionFunction(self.ProjectTree.GetItemText(parent)), new_item)
 
             elif name == "Resources":
                 menu = wx.Menu(title='')
                 new_item = AppendMenu(menu, help='', kind=wx.ITEM_NORMAL, text=_("Add Resource"))
                 parent = self.ProjectTree.GetItemParent(item)
-                parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 while parent_type not in [ITEM_CONFIGURATION, ITEM_PROJECT]:
                     parent = self.ProjectTree.GetItemParent(parent)
-                    parent_type = self.ProjectTree.GetPyData(parent)["type"]
+                    parent_type = self.ProjectTree.GetItemData(parent)["type"]
                 parent_name = None
                 if parent_type == ITEM_PROJECT:
                     config_names = self.Controler.GetProjectConfigNames()
@@ -2079,7 +2082,7 @@
 
     def CloseObsoleteDebugTabs(self):
         if self.EnableDebug:
-            idxs = range(self.TabsOpened.GetPageCount())
+            idxs = list(range(self.TabsOpened.GetPageCount()))
             idxs.reverse()
             for idx in idxs:
                 editor = self.TabsOpened.GetPage(idx)
@@ -2118,7 +2121,7 @@
                 MenuToolBar.AddSeparator()
             else:
                 id, bitmap, help, callback = toolbar_item
-                MenuToolBar.AddSimpleTool(id=id, shortHelpString=help, bitmap=GetBitmap(bitmap))
+                MenuToolBar.AddTool(id, help, GetBitmap(bitmap), help)
                 if callback is not None:
                     self.Bind(wx.EVT_TOOL, callback, id=id)
         MenuToolBar.Realize()
@@ -2155,13 +2158,13 @@
             self.CurrentEditorToolBar = []
             EditorToolBar = self.Panes["EditorToolBar"]
             if EditorToolBar:
-                for radio, modes, id, method, picture, help in self.EditorToolBarItems[menu]:
+                for radio, modes, id, method_name, picture, help in self.EditorToolBarItems[menu]:
                     if modes & self.DrawingMode:
                         if radio or self.DrawingMode == FREEDRAWING_MODE:
-                            EditorToolBar.AddRadioTool(id, GetBitmap(picture), wx.NullBitmap, help)
+                            EditorToolBar.AddRadioTool(id, method_name, GetBitmap(picture), wx.NullBitmap, help)
                         else:
-                            EditorToolBar.AddSimpleTool(id, GetBitmap(picture), help)
-                        self.Bind(wx.EVT_MENU, getattr(self, method), id=id)
+                            EditorToolBar.AddTool(id, method_name, GetBitmap(picture), help)
+                        self.Bind(wx.EVT_MENU, getattr(self, method_name), id=id)
                         self.CurrentEditorToolBar.append(id)
                 EditorToolBar.Realize()
                 self.AUIManager.GetPane("EditorToolBar").Show()
@@ -2188,8 +2191,8 @@
             EditorToolBar.ToggleTool(ID_PLCOPENEDITOREDITORTOOLBARSELECTION, True)
 
     def ResetToolToggle(self, id):
-        tool = self.Panes["EditorToolBar"].FindById(id)
-        tool.SetToggle(False)
+        tool = self.Panes["EditorToolBar"]
+        tool.ToggleTool(toolId=id, toggle=False)
 
     def OnSelectionTool(self, event):
         selected = self.TabsOpened.GetSelection()
@@ -2402,7 +2405,7 @@
     def GenerateChangePouTypeFunction(self, name, new_type):
         def OnChangePouTypeMenu(event):
             selected = self.ProjectTree.GetSelection()
-            if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+            if self.ProjectTree.GetItemData(selected)["type"] == ITEM_POU:
                 self.Controler.ProjectChangePouType(name, new_type)
                 self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, PROJECTTREE, LIBRARYTREE)
         return OnChangePouTypeMenu
@@ -2419,7 +2422,7 @@
     def OnPastePou(self, event):
         selected = self.ProjectTree.GetSelection()
 
-        if self.ProjectTree.GetPyData(selected)["type"] != ITEM_PROJECT:
+        if self.ProjectTree.GetItemData(selected)["type"] != ITEM_PROJECT:
             pou_type = self.ProjectTree.GetItemText(selected)
             pou_type = self.UNEDITABLE_NAMES_DICT[pou_type]  # one of 'Functions', 'Function Blocks' or 'Programs'
             pou_type = {'Functions': 'function', 'Function Blocks': 'functionBlock', 'Programs': 'program'}[pou_type]
@@ -2464,7 +2467,7 @@
 
     def OnRemoveDataTypeMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_DATATYPE:
+        if self.ProjectTree.GetItemData(selected)["type"] == ITEM_DATATYPE:
             name = self.ProjectTree.GetItemText(selected)
             if self.CheckDataTypeIsUsedBeforeDeletion(name):
                 self.Controler.ProjectRemoveDataType(name)
@@ -2476,12 +2479,12 @@
 
     def OnRenamePouMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+        if self.ProjectTree.GetItemData(selected)["type"] == ITEM_POU:
             wx.CallAfter(self.ProjectTree.EditLabel, selected)
 
     def OnRemovePouMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_POU:
+        if self.ProjectTree.GetItemData(selected)["type"] == ITEM_POU:
             name = self.ProjectTree.GetItemText(selected)
             if self.CheckPouIsUsedBeforeDeletion(name):
                 self.Controler.ProjectRemovePou(name)
@@ -2493,7 +2496,7 @@
 
     def OnRemoveTransitionMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        item_infos = self.ProjectTree.GetPyData(selected)
+        item_infos = self.ProjectTree.GetItemData(selected)
         if item_infos["type"] == ITEM_TRANSITION:
             transition = self.ProjectTree.GetItemText(selected)
             pou_name = item_infos["tagname"].split("::")[1]
@@ -2506,7 +2509,7 @@
 
     def OnRemoveActionMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        item_infos = self.ProjectTree.GetPyData(selected)
+        item_infos = self.ProjectTree.GetItemData(selected)
         if item_infos["type"] == ITEM_ACTION:
             action = self.ProjectTree.GetItemText(selected)
             pou_name = item_infos["tagname"].split("::")[1]
@@ -2519,7 +2522,7 @@
 
     def OnRemoveConfigurationMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFIGURATION:
+        if self.ProjectTree.GetItemData(selected)["type"] == ITEM_CONFIGURATION:
             name = self.ProjectTree.GetItemText(selected)
             self.Controler.ProjectRemoveConfiguration(name)
             tagname = ComputeConfigurationName(name)
@@ -2530,7 +2533,7 @@
 
     def OnRemoveResourceMenu(self, event):
         selected = self.ProjectTree.GetSelection()
-        item_infos = self.ProjectTree.GetPyData(selected)
+        item_infos = self.ProjectTree.GetItemData(selected)
         if item_infos["type"] == ITEM_RESOURCE:
             resource = self.ProjectTree.GetItemText(selected)
             config_name = item_infos["tagname"].split("::")[1]
@@ -2568,9 +2571,9 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            self.Highlights = dict([(name, highlight) for name, highlight in self.Highlights.iteritems() if highlight != highlight_type])
+            self.Highlights = dict([(name, highlight) for name, highlight in self.Highlights.items() if highlight != highlight_type])
         self.RefreshProjectTree()
-        for i in xrange(self.TabsOpened.GetPageCount()):
+        for i in range(self.TabsOpened.GetPageCount()):
             viewer = self.TabsOpened.GetPage(i)
             viewer.ClearHighlights(highlight_type)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LocalRuntimeMixin.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import tempfile
+import random
+import shutil
+
+from util.ProcessLogger import ProcessLogger
+from util.paths import Bpath
+
+_exec = sys.executable if "darwin" not in sys.platform else sys.executable + 'w'
+LocalRuntimeInterpreterPath = os.environ.get("BEREMIZPYTHONPATH", _exec)
+
+LocalHost = os.environ.get("BEREMIZ_LOCAL_HOST", "127.0.0.1")
+
+class LocalRuntimeMixin():
+
+    def __init__(self, log, use_gui=True):
+        self.local_runtime_log = log
+        self.local_runtime = None
+        self.runtime_port = None
+        self.local_runtime_tmpdir = None
+        self.use_gui = use_gui
+
+    def StartLocalRuntime(self):
+        if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
+            # create temporary directory for runtime working directory
+            self.local_runtime_tmpdir = tempfile.mkdtemp()
+            # choose an arbitrary random port for runtime
+            self.runtime_port = int(random.random() * 1000) + 61131
+            self.local_runtime_log.write(_("Starting local runtime...\n"))
+            # launch local runtime
+            self.local_runtime = ProcessLogger(
+                self.local_runtime_log,
+                ("\"%s\" \"%s\" -p %s -i "+LocalHost+" %s %s") % (
+                    LocalRuntimeInterpreterPath,
+                    Bpath("Beremiz_service.py"),
+                    self.runtime_port,
+                    {False: "-x 0", True: "-x 1"}[self.use_gui],
+                    self.local_runtime_tmpdir),
+                no_gui=False,
+                timeout=500, keyword=self.local_runtime_tmpdir,
+                cwd=self.local_runtime_tmpdir)
+            self.local_runtime.spin()
+        return self.runtime_port
+
+    def KillLocalRuntime(self):
+        if self.local_runtime is not None:
+            # shutdown local runtime
+            self.local_runtime.kill(gently=False)
+            # clear temp dir
+            shutil.rmtree(self.local_runtime_tmpdir)
+
+            self.local_runtime = None
+
--- a/NativeLib.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/NativeLib.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import util.paths as paths
 from POULibrary import SimplePOULibraryFactory
 
--- a/PLCControler.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/PLCControler.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,15 +24,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 from copy import deepcopy
 import os
 import re
 import datetime
 from time import localtime
 from functools import reduce
-from future.builtins import round
 
 import util.paths as paths
 from plcopen import *
@@ -74,7 +71,7 @@
             self.MinIndex = 0
             self.MaxIndex = 0
         # Initialising buffer with currentstate at the first place
-        for i in xrange(UNDO_BUFFER_LENGTH):
+        for i in range(UNDO_BUFFER_LENGTH):
             if i == 0:
                 self.Buffer.append(currentstate)
             else:
@@ -457,8 +454,8 @@
                 self.NextCompiledProject = self.Copy(self.Project)
                 program_text = "".join([item[0] for item in self.ProgramChunks])
                 if filepath is not None:
-                    programfile = open(filepath, "w")
-                    programfile.write(program_text.encode("utf-8"))
+                    programfile = open(filepath, "w", encoding='utf-8')
+                    programfile.write(program_text)
                     programfile.close()
                     self.ProgramFilePath = filepath
                 return program_text, errors, warnings
@@ -1184,7 +1181,7 @@
         for _sectioname, blocktype in self.TotalTypesDict.get(typename, []):
             if inputs is not None and inputs != "undefined":
                 block_inputs = tuple([var_type for _name, var_type, _modifier in blocktype["inputs"]])
-                if reduce(lambda x, y: x and y, map(lambda x: x[0] == "ANY" or self.IsOfType(*x), zip(inputs, block_inputs)), True):
+                if reduce(lambda x, y: x and y, [x[0] == "ANY" or self.IsOfType(*x) for x in zip(inputs, block_inputs)], True):
                     return blocktype
             else:
                 if result_blocktype:
@@ -1247,7 +1244,7 @@
         if project is not None and words[0] in ["P", "T", "A"]:
             name = words[1]
         blocktypes = []
-        for blocks in self.TotalTypesDict.itervalues():
+        for blocks in self.TotalTypesDict.values():
             for _sectioname, block in blocks:
                 if block["type"] == "functionBlock":
                     blocktypes.append(block["name"])
@@ -1302,7 +1299,7 @@
             result = project.getpou(typename)
             if result is not None:
                 return result
-        for standardlibrary in StdBlckLibs.values():
+        for standardlibrary in list(StdBlckLibs.values()):
             result = standardlibrary.getpou(typename)
             if result is not None:
                 return result
@@ -1455,7 +1452,7 @@
 
     # Return Subrange types
     def GetSubrangeBaseTypes(self, exclude, debug=False):
-        subrange_basetypes = DataTypeRange.keys()
+        subrange_basetypes = list(DataTypeRange.keys())
         project = self.GetProject(debug)
         if project is not None:
             subrange_basetypes.extend(
@@ -1970,9 +1967,9 @@
                 new_pos[0] -= width // 2
                 new_pos[1] -= height // 2
             else:
-                new_pos = map(lambda x: x + 30, new_pos)
+                new_pos = [x + 30 for x in new_pos]
             if scaling[0] != 0 and scaling[1] != 0:
-                min_pos = map(lambda x: 30 / x, scaling)
+                min_pos = [30 / x for x in scaling]
                 minx = round(min_pos[0])
                 if int(min_pos[0]) == round(min_pos[0]):
                     minx += 1
@@ -2118,7 +2115,7 @@
                     self.ChangeEditedElementPouVar(tagname, old_type, old_name, new_type, new_name)
             elif new_name != old_name:
                 self.ChangeEditedElementPouVar(tagname, old_type, old_name, new_type, new_name)
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     if value != "":
                         block.setinstanceName(value)
@@ -2179,7 +2176,7 @@
             variable = element.getinstance(id)
             if variable is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     variable.setexpression(value)
                 elif param == "executionOrder" and variable.getexecutionOrderId() != value:
@@ -2232,7 +2229,7 @@
             connection = element.getinstance(id)
             if connection is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     connection.setname(value)
                 elif param == "height":
@@ -2264,7 +2261,7 @@
         element = self.GetEditedElement(tagname)
         if element is not None:
             comment = element.getinstance(id)
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "content":
                     comment.setcontentText(value)
                 elif param == "height":
@@ -2291,7 +2288,7 @@
             powerrail = element.getinstance(id)
             if powerrail is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "height":
                     powerrail.setheight(value)
                 elif param == "width":
@@ -2330,7 +2327,7 @@
             contact = element.getinstance(id)
             if contact is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     contact.setvariable(value)
                 elif param == "type":
@@ -2373,7 +2370,7 @@
             coil = element.getinstance(id)
             if coil is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     coil.setvariable(value)
                 elif param == "type":
@@ -2419,7 +2416,7 @@
             step = element.getinstance(id)
             if step is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "name":
                     step.setname(value)
                 elif param == "initial":
@@ -2469,7 +2466,7 @@
             transition = element.getinstance(id)
             if transition is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "type" and value != "connection":
                     transition.setconditionContent(value, infos["condition"])
                 elif param == "height":
@@ -2529,7 +2526,7 @@
             divergence = element.getinstance(id)
             if divergence is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "height":
                     divergence.setheight(value)
                 elif param == "width":
@@ -2580,7 +2577,7 @@
             jump = element.getinstance(id)
             if jump is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "target":
                     jump.settargetName(value)
                 elif param == "height":
@@ -2610,7 +2607,7 @@
             actionBlock = element.getinstance(id)
             if actionBlock is None:
                 return
-            for param, value in infos.items():
+            for param, value in list(infos.items()):
                 if param == "actions":
                     actionBlock.setactions(value)
                 elif param == "height":
--- a/PLCGenerator.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/PLCGenerator.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,10 +23,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+from functools import cmp_to_key
+from operator import eq
 import re
 from functools import reduce
-from six.moves import xrange
 
 from plcopen import PLCOpenParser
 from plcopen.structures import *
@@ -65,7 +65,7 @@
             while lines[line_num][spaces] == " ":
                 spaces += 1
             indent = ""
-            for dummy in xrange(spaces, nb_spaces):
+            for dummy in range(spaces, nb_spaces):
                 indent += " "
             for line in lines:
                 if line != "":
@@ -79,9 +79,9 @@
     ax, ay = int(a.getx()), int(a.gety())
     bx, by = int(b.getx()), int(b.gety())
     if abs(ay - by) < 10:
-        return cmp(ax, bx)
+        return eq(ax, bx)
     else:
-        return cmp(ay, by)
+        return eq(ay, by)
 
 
 def JoinList(separator, mylist):
@@ -264,7 +264,7 @@
 
     # Generate a POU defined and used in text
     def GeneratePouProgramInText(self, text):
-        for pou_name in self.PouComputed.keys():
+        for pou_name in list(self.PouComputed.keys()):
             model = re.compile("(?:^|[^0-9^A-Z])%s(?:$|[^0-9^A-Z])" % pou_name.upper())
             if model.search(text) is not None:
                 self.GeneratePouProgram(pou_name)
@@ -472,12 +472,12 @@
         if len(self.DatatypeComputed) > 0:
             self.Program += [("TYPE\n", ())]
             # Generate every data types defined
-            for datatype_name in self.DatatypeComputed.keys():
+            for datatype_name in list(self.DatatypeComputed.keys()):
                 log("Generate Data Type %s"%datatype_name)
                 self.GenerateDataType(datatype_name)
             self.Program += [("END_TYPE\n\n", ())]
         # Generate every POUs defined
-        for pou_name in self.PouComputed.keys():
+        for pou_name in list(self.PouComputed.keys()):
             log("Generate POU %s"%pou_name)
             self.GeneratePouProgram(pou_name)
         if noconfig:
@@ -895,7 +895,7 @@
                             if connected is not None and connected not in self.ConnectionTypes:
                                 for connection in self.ExtractRelatedConnections(connected):
                                     self.ConnectionTypes[connection] = itype
-        for var_type, connections in undefined.items():
+        for var_type, connections in list(undefined.items()):
             related = []
             for connection in connections:
                 connection_type = self.ConnectionTypes.get(connection)
@@ -984,7 +984,7 @@
             orderedInstances = []
             for instance in body.getcontentInstances():
                 if isinstance(instance, (OutVariableClass, InOutVariableClass, BlockClass)):
-                    executionOrderId = instance.getexecutionOrderId()
+                    executionOrderId = instance.getexecutionOrderId() or 0  # 0 if None
                     if executionOrderId > 0:
                         orderedInstances.append((executionOrderId, instance))
                     elif isinstance(instance, (OutVariableClass, InOutVariableClass)):
@@ -995,9 +995,9 @@
                     otherInstances["connectors"].append(instance)
                 elif isinstance(instance, CoilClass):
                     otherInstances["outVariables&coils"].append(instance)
-            orderedInstances.sort()
-            otherInstances["outVariables&coils"].sort(SortInstances)
-            otherInstances["blocks"].sort(SortInstances)
+            orderedInstances.sort(key=lambda n: n[0])
+            otherInstances["outVariables&coils"].sort(key=cmp_to_key(SortInstances))
+            otherInstances["blocks"].sort(key=cmp_to_key(SortInstances))
             instances = [instance for (executionOrderId, instance) in orderedInstances]
             instances.extend(otherInstances["outVariables&coils"] + otherInstances["blocks"] + otherInstances["connectors"])
             for instance in instances:
@@ -1055,7 +1055,7 @@
 
     def FactorizePaths(self, paths):
         same_paths = {}
-        uncomputed_index = range(len(paths))
+        uncomputed_index = list(range(len(paths)))
         factorized_paths = []
         for num, path in enumerate(paths):
             if isinstance(path, list):
@@ -1066,7 +1066,7 @@
             else:
                 factorized_paths.append(path)
                 uncomputed_index.remove(num)
-        for same_path, elements in same_paths.items():
+        for same_path, elements in list(same_paths.items()):
             if len(elements) > 1:
                 elements_paths = self.FactorizePaths([path for path, num in elements])
                 if len(elements_paths) > 1:
@@ -1100,7 +1100,7 @@
 
         name = block.getinstanceName()
         type = block.gettypeName()
-        executionOrderId = block.getexecutionOrderId()
+        executionOrderId = block.getexecutionOrderId() or 0     # 0 if None
         input_variables = block.inputVariables.getvariable()
         output_variables = block.outputVariables.getvariable()
         inout_variables = {}
@@ -1452,7 +1452,7 @@
 
     def GenerateSFCStep(self, step, pou):
         step_name = step.getname()
-        if step_name not in self.SFCNetworks["Steps"].keys():
+        if step_name not in list(self.SFCNetworks["Steps"].keys()):
             if step.getinitialStep():
                 self.InitialSteps.append(step_name)
             step_infos = {"id":          step.getlocalId(),
@@ -1482,7 +1482,7 @@
                                 instances.extend(self.ExtractConvergenceInputs(transition, pou))
                 for instance in instances:
                     self.GenerateSFCTransition(instance, pou)
-                    if instance in self.SFCNetworks["Transitions"].keys():
+                    if instance in list(self.SFCNetworks["Transitions"].keys()):
                         target_info = (self.TagName, "transition", instance.getlocalId(), "to", step_infos["id"])
                         self.SFCNetworks["Transitions"][instance]["to"].append([(step_name, target_info)])
 
@@ -1516,7 +1516,7 @@
                             instances.extend(self.ExtractConvergenceInputs(transition, pou))
             for instance in instances:
                 self.GenerateSFCTransition(instance, pou)
-                if instance in self.SFCNetworks["Transitions"].keys():
+                if instance in list(self.SFCNetworks["Transitions"].keys()):
                     target_info = (self.TagName, "jump", jump.getlocalId(), "target")
                     self.SFCNetworks["Transitions"][instance]["to"].append([(jump_target, target_info)])
 
@@ -1530,7 +1530,7 @@
             step = body.getcontentInstance(stepLocalId)
             self.GenerateSFCStep(step, pou)
             step_name = step.getname()
-            if step_name in self.SFCNetworks["Steps"].keys():
+            if step_name in list(self.SFCNetworks["Steps"].keys()):
                 actions = actionBlock.getactions()
                 for i, action in enumerate(actions):
                     action_infos = {"id":        actionBlock.getlocalId(),
@@ -1555,7 +1555,7 @@
                     self.SFCNetworks["Steps"][step_name]["actions"].append(action_infos)
 
     def GenerateSFCAction(self, action_name, pou):
-        if action_name not in self.SFCNetworks["Actions"].keys():
+        if action_name not in list(self.SFCNetworks["Actions"].keys()):
             actionContent = pou.getaction(action_name)
             if actionContent is not None:
                 previous_tagname = self.TagName
@@ -1566,7 +1566,7 @@
                 self.TagName = previous_tagname
 
     def GenerateSFCTransition(self, transition, pou):
-        if transition not in self.SFCNetworks["Transitions"].keys():
+        if transition not in list(self.SFCNetworks["Transitions"].keys()):
             steps = []
             connections = transition.connectionPointIn.getconnections()
             if connections is not None and len(connections) == 1:
@@ -1639,12 +1639,12 @@
             for step in steps:
                 self.GenerateSFCStep(step, pou)
                 step_name = step.getname()
-                if step_name in self.SFCNetworks["Steps"].keys():
+                if step_name in list(self.SFCNetworks["Steps"].keys()):
                     transition_infos["from"].append([(step_name, (self.TagName, "transition", transition.getlocalId(), "from", step.getlocalId()))])
                     self.SFCNetworks["Steps"][step_name]["transitions"].append(transition)
 
     def ComputeSFCStep(self, step_name):
-        if step_name in self.SFCNetworks["Steps"].keys():
+        if step_name in list(self.SFCNetworks["Steps"].keys()):
             step_infos = self.SFCNetworks["Steps"].pop(step_name)
             self.Program += [(self.CurrentIndent, ())]
             if step_infos["initial"]:
@@ -1679,7 +1679,7 @@
                 self.ComputeSFCTransition(transition)
 
     def ComputeSFCAction(self, action_name):
-        if action_name in self.SFCNetworks["Actions"].keys():
+        if action_name in list(self.SFCNetworks["Actions"].keys()):
             action_content, action_info = self.SFCNetworks["Actions"].pop(action_name)
             self.Program += [("%sACTION " % self.CurrentIndent, ()),
                              (action_name, action_info),
@@ -1688,7 +1688,7 @@
             self.Program += [("%sEND_ACTION\n\n" % self.CurrentIndent, ())]
 
     def ComputeSFCTransition(self, transition):
-        if transition in self.SFCNetworks["Transitions"].keys():
+        if transition in list(self.SFCNetworks["Transitions"].keys()):
             transition_infos = self.SFCNetworks["Transitions"].pop(transition)
             self.Program += [("%sTRANSITION" % self.CurrentIndent, ())]
             if transition_infos["priority"] is not None:
--- a/PLCOpenEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/PLCOpenEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,13 +24,14 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import os
 import sys
 import getopt
 
 import wx
+import wx.adv
 
 import version
 import util.paths as paths
@@ -63,7 +64,7 @@
 [
     ID_PLCOPENEDITORFILEMENUGENERATE,
     ID_PLCOPENEDITORFILEMENUGENERATEAS,
-] = [wx.NewId() for _init_coll_FileMenu_Items in range(2)]
+] = [wx.NewIdRef() for _init_coll_FileMenu_Items in range(2)]
 
 
 beremiz_dir = paths.AbsDir(__file__)
@@ -73,35 +74,35 @@
 
     def _init_coll_FileMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_NEW,
-                   kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
+                   kind=wx.ITEM_NORMAL, text=_('New') + '\tCTRL+N')
         AppendMenu(parent, help='', id=wx.ID_OPEN,
-                   kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
+                   kind=wx.ITEM_NORMAL, text=_('Open') + '\tCTRL+O')
         AppendMenu(parent, help='', id=wx.ID_CLOSE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
+                   kind=wx.ITEM_NORMAL, text=_('Close Tab') + '\tCTRL+W')
         AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
-                   kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
+                   kind=wx.ITEM_NORMAL, text=_('Close Project') + '\tCTRL+SHIFT+W')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_SAVE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
+                   kind=wx.ITEM_NORMAL, text=_('Save') + '\tCTRL+S')
         AppendMenu(parent, help='', id=wx.ID_SAVEAS,
-                   kind=wx.ITEM_NORMAL, text=_(u'Save As...') + '\tCTRL+SHIFT+S')
+                   kind=wx.ITEM_NORMAL, text=_('Save As...') + '\tCTRL+SHIFT+S')
         AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATE,
-                   kind=wx.ITEM_NORMAL, text=_(u'Generate Program') + '\tCTRL+G')
+                   kind=wx.ITEM_NORMAL, text=_('Generate Program') + '\tCTRL+G')
         AppendMenu(parent, help='', id=ID_PLCOPENEDITORFILEMENUGENERATEAS,
-                   kind=wx.ITEM_NORMAL, text=_(u'Generate Program As...') + '\tCTRL+SHIFT+G')
+                   kind=wx.ITEM_NORMAL, text=_('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')
+                   kind=wx.ITEM_NORMAL, text=_('Page Setup') + '\tCTRL+ALT+P')
         AppendMenu(parent, help='', id=wx.ID_PREVIEW,
-                   kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
+                   kind=wx.ITEM_NORMAL, text=_('Preview') + '\tCTRL+SHIFT+P')
         AppendMenu(parent, help='', id=wx.ID_PRINT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
+                   kind=wx.ITEM_NORMAL, text=_('Print') + '\tCTRL+P')
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_PROPERTIES,
-                   kind=wx.ITEM_NORMAL, text=_(u'&Properties'))
+                   kind=wx.ITEM_NORMAL, text=_('&Properties'))
         parent.AppendSeparator()
         AppendMenu(parent, help='', id=wx.ID_EXIT,
-                   kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')
+                   kind=wx.ITEM_NORMAL, text=_('Quit') + '\tCTRL+Q')
 
         self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
         self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
@@ -119,16 +120,16 @@
         self.Bind(wx.EVT_MENU, self.OnPropertiesMenu, id=wx.ID_PROPERTIES)
         self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)
 
-        self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
-                               (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),
-                               (ID_PLCOPENEDITORFILEMENUGENERATE, "Build", _(u'Generate Program'), None)])
+        self.AddToMenuToolBar([(wx.ID_NEW, "new", _('New'), None),
+                               (wx.ID_OPEN, "open", _('Open'), None),
+                               (wx.ID_SAVE, "save", _('Save'), None),
+                               (wx.ID_SAVEAS, "saveas", _('Save As...'), None),
+                               (wx.ID_PRINT, "print", _('Print'), None),
+                               (ID_PLCOPENEDITORFILEMENUGENERATE, "Build", _('Generate Program'), None)])
 
     def _init_coll_HelpMenu_Items(self, parent):
         AppendMenu(parent, help='', id=wx.ID_HELP,
-                   kind=wx.ITEM_NORMAL, text=_(u'PLCOpenEditor') + '\tF1')
+                   kind=wx.ITEM_NORMAL, text=_('PLCOpenEditor') + '\tF1')
         # AppendMenu(parent, help='', id=wx.ID_HELP_CONTENTS,
         #      kind=wx.ITEM_NORMAL, text=u'PLCOpen\tF2')
         # AppendMenu(parent, help='', id=wx.ID_HELP_CONTEXT,
@@ -137,14 +138,14 @@
         def handler(event):
             return wx.MessageBox(
                 version.GetCommunityHelpMsg(),
-                _(u'Community support'),
+                _('Community support'),
                 wx.OK | wx.ICON_INFORMATION)
 
-        menu_entry = parent.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=_(u'Community support'))
+        menu_entry = parent.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=_('Community support'))
         self.Bind(wx.EVT_MENU, handler, menu_entry)
 
         AppendMenu(parent, help='', id=wx.ID_ABOUT,
-                   kind=wx.ITEM_NORMAL, text=_(u'About'))
+                   kind=wx.ITEM_NORMAL, text=_('About'))
         self.Bind(wx.EVT_MENU, self.OnPLCOpenEditorMenu, id=wx.ID_HELP)
         # self.Bind(wx.EVT_MENU, self.OnPLCOpenMenu, id=wx.ID_HELP_CONTENTS)
         self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)
@@ -355,6 +356,7 @@
         open_pdf(os.path.join(beremiz_dir, "plcopen", "TC6_XML_V101.pdf"))
 
     def OnAboutMenu(self, event):
+        info = wx.adv.AboutDialogInfo()
         info = version.GetAboutDialogInfo()
         info.Name = "PLCOpenEditor"
         info.Description = _("PLCOpenEditor is part of Beremiz project.\n\n"
@@ -422,8 +424,6 @@
         self.SetAppName('plcopeneditor')
         self.ParseCommandLine()
         InstallLocalRessources(beremiz_dir)
-        if wx.VERSION < (3, 0, 0):
-            wx.InitAllImageHandlers()
         util.ExceptionHandler.AddExceptHook(version.app_version)
         self.frame = PLCOpenEditor(None, fileOpen=self.fileOpen)
         return True
--- a/POULibrary.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/POULibrary.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from weakref import ref
 
 # Exception type for problems that user has to take action in order to fix
--- a/PSKManagement.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/PSKManagement.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 import time
 import json
@@ -11,8 +11,8 @@
 
 # PSK Management Data model :
 # [[ID,Desc, LastKnownURI, LastConnect]]
-COL_ID, COL_URI, COL_DESC, COL_LAST = range(4)
-REPLACE, REPLACE_ALL, KEEP, KEEP_ALL, CANCEL = range(5)
+COL_ID, COL_URI, COL_DESC, COL_LAST = list(range(4))
+REPLACE, REPLACE_ALL, KEEP, KEEP_ALL, CANCEL = list(range(5))
 
 
 def _pskpath(project_path):
--- a/ProjectController.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/ProjectController.py	Wed Oct 04 16:24:00 2023 +0200
@@ -27,8 +27,7 @@
 Beremiz Project Controller
 """
 
-
-from __future__ import absolute_import
+import sys
 import os
 import traceback
 import time
@@ -37,12 +36,10 @@
 import re
 import tempfile
 import hashlib
+import shutil
 from datetime import datetime
 from weakref import WeakKeyDictionary
 from functools import reduce
-from itertools import izip
-from distutils.dir_util import copy_tree
-from six.moves import xrange
 
 import wx
 
@@ -116,7 +113,7 @@
         return path
 
     def findCmd(self):
-        cmd = "iec2c" + (".exe" if wx.Platform == '__WXMSW__' else "")
+        cmd = "iec2c" + (".exe" if os.name == 'nt' else "")
         paths = [
             os.path.join(base_folder, "matiec")
         ]
@@ -282,9 +279,6 @@
         self.IECcodeDigest = None
         self.LastBuiltIECcodeDigest = None
 
-    def __del__(self):
-        self.KillDebugThread()
-
     def LoadLibraries(self):
         self.Libraries = []
         TypeStack = []
@@ -373,10 +367,16 @@
         return "PROJECT"
 
     def GetDefaultTargetName(self):
-        if wx.Platform == '__WXMSW__':
+        if sys.platform.startswith('linux'):
+            return "Linux"
+        elif sys.platform.startswith('darwin'):
+            return "OSX"
+        elif sys.platform.startswith('win32'):
             return "Win32"
-        else:
-            return "Linux"
+        
+        # Fall back to Linux as default target
+        return "Linux"
+        
 
     def GetTarget(self):
         target = self.BeremizRoot.getTargetType()
@@ -587,7 +587,7 @@
                 old_projectfiles_path = self._getProjectFilesPath(
                     from_project_path)
                 if os.path.isdir(old_projectfiles_path):
-                    copy_tree(old_projectfiles_path,
+                    shutil.copytree(old_projectfiles_path,
                               self._getProjectFilesPath(self.ProjectPath))
             self.SaveXMLFile(os.path.join(self.ProjectPath, 'plc.xml'))
             result = self.CTNRequestSave(from_project_path)
@@ -634,7 +634,7 @@
             LocatedCCodeAndFlags.append(res[:2])
             if len(res) > 2:
                 Extras.extend(res[2:])
-        return map(list, zip(*LocatedCCodeAndFlags)) + [tuple(Extras)]
+        return list(map(list, list(zip(*LocatedCCodeAndFlags)))) + [tuple(Extras)]
 
     # Update PLCOpenEditor ConfNode Block types from loaded confnodes
     def RefreshConfNodesBlockLists(self):
@@ -808,8 +808,8 @@
             plc_file.write(POUsIECCodeContent)
 
         hasher = hashlib.md5()
-        hasher.update(IECCodeContent)
-        hasher.update(POUsIECCodeContent)
+        hasher.update(IECCodeContent.encode())
+        hasher.update(POUsIECCodeContent.encode())
         self.IECcodeDigest = hasher.hexdigest()
 
         return True
@@ -888,15 +888,13 @@
                 _("Error : At least one configuration and one resource must be declared in PLC !\n"))
             return False
         # transform those base names to full names with path
-        C_files = map(
-            lambda filename: os.path.join(buildpath, filename), C_files)
+        C_files = [os.path.join(buildpath, filename) for filename in C_files]
 
         # prepend beremiz include to configuration header
         H_files = [fname for fname in result.splitlines() if fname[
             -2:] == ".h" or fname[-2:] == ".H"]
         H_files.remove("LOCATED_VARIABLES.h")
-        H_files = map(
-            lambda filename: os.path.join(buildpath, filename), H_files)
+        H_files = [os.path.join(buildpath, filename) for filename in H_files]
         for H_file in H_files:
             with open(H_file, 'r') as original:
                 data = original.read()
@@ -995,7 +993,7 @@
                 for line in ListGroup[0]:
                     # Split and Maps each field to dictionnary entries
                     attrs = dict(
-                        zip(ProgramsListAttributeName, line.strip().split(';')))
+                        list(zip(ProgramsListAttributeName, line.strip().split(';'))))
                     # Truncate "C_path" to remove conf an resources names
                     attrs["C_path"] = '__'.join(
                         attrs["C_path"].split(".", 2)[1:])
@@ -1008,7 +1006,7 @@
                 for line in ListGroup[1]:
                     # Split and Maps each field to dictionnary entries
                     attrs = dict(
-                        zip(VariablesListAttributeName, line.strip().split(';')))
+                        list(zip(VariablesListAttributeName, line.strip().split(';'))))
                     # Truncate "C_path" to remove conf an resources names
                     parts = attrs["C_path"].split(".", 2)
                     if len(parts) > 2:
@@ -1098,9 +1096,8 @@
         """
         # filter location that are related to code that will be called
         # in retreive, publish, init, cleanup
-        locstrs = map(lambda x: "_".join(map(str, x)),
-                      [loc for loc, _Cfiles, DoCalls in
-                       self.LocationCFilesAndCFLAGS if loc and DoCalls])
+        locstrs = ["_".join(map(str, x)) for x in [loc for loc, _Cfiles, DoCalls in
+                       self.LocationCFilesAndCFLAGS if loc and DoCalls]]
 
         # Generate main, based on template
         if not self.BeremizRoot.getDisable_Extensions():
@@ -1113,7 +1110,7 @@
                 "retrieve_calls": "\n    ".join([
                     "__retrieve_%s();" % locstr for locstr in locstrs]),
                 "publish_calls": "\n    ".join([  # Call publish in reverse order
-                    "__publish_%s();" % locstrs[i - 1] for i in xrange(len(locstrs), 0, -1)]),
+                    "__publish_%s();" % locstrs[i - 1] for i in range(len(locstrs), 0, -1)]),
                 "init_calls": "\n    ".join([
                     "init_level=%d; " % (i + 1) +
                     "if((res = __init_%s(argc,argv))){" % locstr +
@@ -1121,7 +1118,7 @@
                     "return res;}" for i, locstr in enumerate(locstrs)]),
                 "cleanup_calls": "\n    ".join([
                     "if(init_level >= %d) " % i +
-                    "__cleanup_%s();" % locstrs[i - 1] for i in xrange(len(locstrs), 0, -1)])
+                    "__cleanup_%s();" % locstrs[i - 1] for i in range(len(locstrs), 0, -1)])
             }
         else:
             plc_main_code = targets.GetCode("plc_main_head.c") % {
@@ -1204,7 +1201,7 @@
             LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(
                 buildpath)
         except UserAddressedException as e:
-            self.logger.write_error(e.message)
+            self.logger.write_error(str(e))
             return False
         except Exception as e:
             self.logger.write_error(
@@ -1218,7 +1215,7 @@
                 buildpath,
                 self.PLCGeneratedLocatedVars)
         except UserAddressedException as e:
-            self.logger.write_error(e.message)
+            self.logger.write_error(str(e))
             return False
         except Exception:
             self.logger.write_error(
@@ -1226,6 +1223,13 @@
             self.logger.write_error(traceback.format_exc())
             return False
 
+        # Extensions also need plcCFLAGS in case they include beremiz.h
+        CTNLocationCFilesAndCFLAGS = [
+            (loc, [
+                (code, self.plcCFLAGS+" "+cflags)
+                for code,cflags in code_and_cflags], do_calls)
+            for loc, code_and_cflags, do_calls in CTNLocationCFilesAndCFLAGS]
+
         self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \
             CTNLocationCFilesAndCFLAGS
         self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
@@ -1322,7 +1326,7 @@
 
     def _OpenView(self, name=None, onlyopened=False):
         if name == "IEC code":
-            if self._IECCodeView is None:
+            if not self._IECCodeView:
                 plc_file = self._getIECcodepath()
 
                 self._IECCodeView = IECCodeViewer(
@@ -1344,7 +1348,7 @@
             return self._IECCodeView
 
         elif name == "IEC raw code":
-            if self._IECRawCodeView is None:
+            if not self._IECRawCodeView:
                 controler = MiniTextControler(self._getIECrawcodepath(), self)
 
                 self._IECRawCodeView = IECCodeViewer(
@@ -1361,7 +1365,7 @@
             return self._IECRawCodeView
 
         elif name == "Project Files":
-            if self._ProjectFilesView is None:
+            if not self._ProjectFilesView:
                 self._ProjectFilesView = FileManagementPanel(
                     self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
 
@@ -1388,9 +1392,9 @@
 
                     if editor_name == "":
                         if len(editors) == 1:
-                            editor_name = editors.keys()[0]
+                            editor_name = list(editors.keys())[0]
                         elif len(editors) > 0:
-                            names = editors.keys()
+                            names = list(editors.keys())
                             dialog = wx.SingleChoiceDialog(
                                 self.AppFrame,
                                 _("Select an editor:"),
@@ -1427,7 +1431,7 @@
             self._IECRawCodeView = None
         if self._ProjectFilesView == view:
             self._ProjectFilesView = None
-        if view in self._FileEditors.values():
+        if view in list(self._FileEditors.values()):
             self._FileEditors.pop(view.GetFilePath())
 
     def _Clean(self):
@@ -1499,7 +1503,7 @@
             allmethods = self.DefaultMethods.copy()
             allmethods.update(
                 self.MethodsFromStatus.get(status, {}))
-            for method, active in allmethods.items():
+            for method, active in list(allmethods.items()):
                 self.ShowMethod(method, active)
             self.previous_plcstate = status
             if self.AppFrame is not None:
@@ -1526,7 +1530,8 @@
         # clear previous_plcstate to restore status
         # in UpdateMethodsFromPLCStatus()
         self.previous_plcstate = ""
-        self.AppFrame.ProgressStatusBar.Hide()
+        if self.AppFrame is not None:
+            self.AppFrame.ProgressStatusBar.Hide()
         self.UpdateMethodsFromPLCStatus()
 
     def PullPLCStatusProc(self, event):
@@ -1544,7 +1549,7 @@
                         debug_vars = UnpackDebugBuffer(
                             debug_buff, self.TracedIECTypes)
                         if debug_vars is not None:
-                            for IECPath, values_buffer, value in izip(
+                            for IECPath, values_buffer, value in zip(
                                     self.TracedIECPath,
                                     self.DebugValuesBuffers,
                                     debug_vars):
@@ -1570,7 +1575,7 @@
 
 
         buffers, self.DebugValuesBuffers = (self.DebugValuesBuffers,
-                                            [list() for dummy in xrange(len(self.TracedIECPath))])
+                                            [list() for dummy in range(len(self.TracedIECPath))])
 
         ticks, self.DebugTicks = self.DebugTicks, []
 
@@ -1595,7 +1600,7 @@
         self.TracedIECTypes = []
         if self._connector is not None and self.debug_status != PlcStatus.Broken:
             IECPathsToPop = []
-            for IECPath, data_tuple in self.IECdebug_datas.iteritems():
+            for IECPath, data_tuple in self.IECdebug_datas.items():
                 WeakCallableDict, _data_log, _status, fvalue, _buffer_list = data_tuple
                 if len(WeakCallableDict) == 0:
                     # Callable Dict is empty.
@@ -1619,10 +1624,10 @@
 
             if Idxs:
                 Idxs.sort()
-                IdxsT = zip(*Idxs)
+                IdxsT = list(zip(*Idxs))
                 self.TracedIECPath = IdxsT[3]
                 self.TracedIECTypes = IdxsT[1]
-                res = self._connector.SetTraceVariablesList(zip(*IdxsT[0:3]))
+                res = self._connector.SetTraceVariablesList(list(zip(*IdxsT[0:3])))
                 if res is not None and res > 0:
                     self.DebugToken = res
                 else:
@@ -1687,7 +1692,7 @@
             else:
                 IECdebug_data[4] = reduce(
                     lambda x, y: x | y,
-                    IECdebug_data[0].itervalues(),
+                    iter(IECdebug_data[0].values()),
                     False)
 
         self.AppendDebugUpdate()
@@ -1724,7 +1729,7 @@
         if data_tuple is not None:
             WeakCallableDict, _data_log, _status, _fvalue, buffer_list = data_tuple
             # data_log.append((debug_tick, value))
-            for weakcallable, buffer_list in WeakCallableDict.iteritems():
+            for weakcallable, buffer_list in WeakCallableDict.items():
                 function = getattr(weakcallable, function_name, None)
                 if function is not None:
                     # FIXME: apparently, despite of weak ref objects,
@@ -1790,13 +1795,16 @@
         """
         Start PLC
         """
+        success = False
         if self.GetIECProgramsAndVariables():
             self._connector.StartPLC()
             self.logger.write(_("Starting PLC\n"))
             self._connect_debug()
+            success = True
         else:
             self.logger.write_error(_("Couldn't start PLC !\n"))
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+        return success
 
     def _Stop(self):
         """
@@ -1810,6 +1818,10 @@
 
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
+    def StartLocalRuntime(self):
+        if self.AppFrame:
+            return self.AppFrame.StartLocalRuntime()
+
     def _SetConnector(self, connector, update_status=True):
         self._connector = connector
         if self.AppFrame is not None:
@@ -1826,6 +1838,7 @@
                 wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
     def _Connect(self):
+        success = False
         # don't accept re-connetion if already connected
         if self._connector is not None:
             self.logger.write_error(
@@ -1887,6 +1900,8 @@
                 else:
                     self.logger.write_warning(
                         _("Debug does not match PLC - stop/transfert/start to re-enable\n"))
+            success = True
+        return success
 
     def CompareLocalAndRemotePLC(self):
         if self._connector is None:
@@ -1910,6 +1925,7 @@
         self._SetConnector(None)
 
     def _Transfer(self):
+        success = False
         if self.IsPLCStarted():
             dialog = wx.MessageDialog(
                 self.AppFrame,
@@ -1972,16 +1988,19 @@
                 if self.GetIECProgramsAndVariables():
                     self.UnsubscribeAllDebugIECVariable()
                     self.ProgramTransferred()
-                    self.AppFrame.CloseObsoleteDebugTabs()
-                    self.AppFrame.RefreshPouInstanceVariablesPanel()
-                    self.AppFrame.LogViewer.ResetLogCounters()
+                    if self.AppFrame is not None:
+                        self.AppFrame.CloseObsoleteDebugTabs()
+                        self.AppFrame.RefreshPouInstanceVariablesPanel()
+                        self.AppFrame.LogViewer.ResetLogCounters()
                     self.logger.write(_("PLC installed successfully.\n"))
+                    success = True
                 else:
                     self.logger.write_error(_("Missing debug data\n"))
             else:
                 self.logger.write_error(_("PLC couldn't be installed\n"))
 
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
+        return success
 
     def _Repair(self):
         dialog = wx.MessageDialog(
--- a/README.md	Fri Aug 18 11:38:20 2023 +0200
+++ b/README.md	Wed Oct 04 16:24:00 2023 +0200
@@ -1,4 +1,7 @@
+<!---
 [![docs](https://readthedocs.org/projects/beremiz/badge/?version=latest)](https://beremiz.readthedocs.io)
+-->
+[![CI Automated testing](https://github.com/beremiz/beremiz/actions/workflows/run_tests_in_docker.yml/badge.svg?branch=python3)](https://github.com/beremiz/beremiz/actions/workflows/run_tests_in_docker.yml)
 
 # Beremiz #
 
@@ -10,109 +13,200 @@
 
 Beremiz consists of two components:
 
-* Integrated Development Environment (IDE), [Beremiz.py](https://bitbucket.org/automforge/beremiz/src/tip/Beremiz.py?at=default). It's running on user's computer and is used to write/compile/debug PLC programs and control PLC runtime.
-* Reference runtime implementation in python, [Beremiz_service.py](https://bitbucket.org/automforge/beremiz/src/tip/Beremiz_service.py?at=default). It's running on target platform, communicates with I/O and executes PLC program.
+* Integrated Development Environment (IDE), Beremiz.py. It is running on user's computer and is used to write/compile/debug PLC programs and control PLC runtime.
+* Reference runtime implementation in python, Beremiz_service.py. It's running on target platform, communicates with I/O and executes PLC program.
 
 See official [Beremiz website](http://www.beremiz.org/) for more information.
 
-## Build on Linux ##
-
-* Prerequisites
-
-		# Ubuntu/Debian :
-		sudo apt-get install build-essential bison flex autoconf
-		sudo apt-get install python-wxgtk3.0 pyro mercurial
-		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 --user sslpsk posix_spawn
-
-* Prepare
-
-		mkdir ~/Beremiz
-		cd ~/Beremiz
-
-* Get Source Code
-
-		cd ~/Beremiz
-		hg clone https://bitbucket.org/automforge/beremiz
-		hg clone https://bitbucket.org/automforge/matiec
-
-* Build MatIEC compiler
-
-		cd ~/Beremiz/matiec
-		autoreconf -i
-		./configure
-		make
-
-* Build CanFestival (optional)  
-  Only needed for CANopen support. Please read CanFestival manual to choose CAN interface other than 'virtual'.
-
-		cd ~/Beremiz
-		hg clone http://dev.automforge.net/CanFestival-3
-		cd ~/Beremiz/CanFestival-3
-		./configure --can=virtual
-		make
-
-* Build Modbus library (optional)
-  Only needed for Modbus support.
-
-		cd ~/Beremiz
-		hg clone https://bitbucket.org/mjsousa/modbus Modbus
-		cd ~/Beremiz/Modbus
-		make
-
-* Build BACnet (optional)
-  Only needed for BACnet support.
-
-		cd ~/Beremiz
-		svn checkout https://svn.code.sf.net/p/bacnet/code/trunk/bacnet-stack/ BACnet
-		cd BACnet
-		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
-
-		cd ~/Beremiz/beremiz
-		python Beremiz.py
+## Install latest release ##
+
+Windows installer and Snap package for Linux are available in [Github releases](https://github.com/beremiz/beremiz/releases) and [Snapcraft's store](https://snapcraft.io/beremiz)
+
+## Tutorials and examples ##
+
+In IDE, find menu "File>Tutorials and examples" to quickly open examples that should run as-is.
+
+There are more examples in `tests/projects` and `exemples` directories.
+
+Some example and test are shown on [Beremiz youtube channel](https://www.youtube.com/channel/UCcE4KYI0p1f6CmSwtzyg-ZA).
+
+## Development with Beremiz ##
+
+Developers are invited to subscribe to [mailing list](https://sourceforge.net/p/beremiz/mailman/beremiz-devel/) (beremiz-devel@lists.sourceforge.net).
+
+The list is moderated and requires subscription before posting.
+
+To subscribe to the mailing list go [here](https://sourceforge.net/p/beremiz/mailman/beremiz-devel/).
+
+Searchable archive using search engine of your choice is available [here](http://beremiz-devel.2374573.n4.nabble.com/).
+
+## Build on Linux (developer setup) ##
+
+### System prerequisites (Ubuntu 22.04) :
+```
+# install required system packages as root
+sudo apt-get install \
+  build-essential automake flex bison mercurial \
+  libgtk-3-dev libgl1-mesa-dev libglu1-mesa-dev \
+  libpython3.10-dev libssl-dev \
+  python3.10 virtualenv cmake git mercurial
+```
+
+
+### Prepare build directory
+
+All commands hereafter assume that selected directory to contain all downloaded source code and build results is `~/Beremiz`
+
+```
+mkdir ~/Beremiz
+cd ~/Beremiz
+```
+
+### Get Source Code (Mercurial)
+
+```
+cd ~/Beremiz
+hg clone https://hg.beremiz.org/beremiz
+hg clone https://hg.beremiz.org/matiec
+```
+
+### Get Source Code (Git)
+
+```
+cd ~/Beremiz
+git clone https://github.com/beremiz/beremiz
+git clone https://github.com/beremiz/matiec
+```
+
+### Python prerequisites (virtualenv) :
+```
+# setup isolated python environment
+virtualenv ~/Beremiz/venv
+
+# install required python packages
+~/Beremiz/venv/bin/pip install -r ~/Beremiz/beremiz/requirements.txt
+
+```
+
+### Build MatIEC compiler
+
+```
+cd ~/Beremiz/matiec
+autoreconf -i
+./configure
+make
+```
+
+### Build CanFestival (optional)
+
+Only needed for CANopen support. Please read CanFestival manual to choose CAN interface other than `virtual`.
+
+```
+cd ~/Beremiz
+
+hg clone http://hg.beremiz.org/canfestival
+#  -- or --
+git clone https://github.com/beremiz/beremiz
+
+cd ~/Beremiz/canfestival
+./configure --can=virtual
+make
+```
+
+### Build Modbus library (optional)
+
+Only needed for Modbus support.
+
+```
+cd ~/Beremiz
+
+hg clone http://hg.beremiz.org/Modbus
+#  -- or --
+git clone https://github.com/beremiz/Modbus
+
+cd ~/Beremiz/Modbus
+make
+```
+
+### Build BACnet (optional)
+
+Only needed for BACnet support.
+
+```
+cd ~/Beremiz
+svn checkout https://svn.code.sf.net/p/bacnet/code/trunk/bacnet-stack/ BACnet
+cd BACnet
+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
+
+```
+~/Beremiz/venv/python ~/Beremiz/beremiz/Beremiz.py
+```
 
 ## Run standalone Beremiz runtime ##
 
-Runtime implementation can be different on different platforms.
-For example, PLC used Cortex-M most likely would have C-based runtime. Beremiz project contains reference implementation in python, that can be easily run on GNU/Linux, Windows and Mac OS X.
-This section will describe how to run it.
-
-If project's URL is 'LOCAL://', then IDE launches temprorary instance of Beremiz python runtime (Beremiz_service.py) localy as user tries to connect to PLC. This allows to debug programs localy without PLC.
-
-If you want to run Beremiz_service.py as standalone service, then follow these instructions:
-
 * Start standalone Beremiz service
 
-		cd ~/Beremiz
-		mkdir beremiz_workdir
-		cd ~/beremiz
-		python Beremiz_service.py -p 61194 -i localhost -x 0 -a 1 ~/Beremiz/beremiz_workdir
-
-* Launch Beremiz IDE
-
-		cd ~/Beremiz/beremiz
-		python Beremiz.py
-
-* Open/Create PLC project in Beremiz IDE.  
-  Enter target location URI in project's settings (project->Config->BeremizRoot/URI_location) pointed to your running Beremiz service (For example, PYRO://127.0.0.1:61194).
-  Save project and connect to running Beremiz service.
-
-## Examples ##
-
-Almost for all functionality exists example in ['tests'](https://bitbucket.org/automforge/beremiz/src/tip/tests/?at=default) directory.
-Most of examples are shown on [Beremiz youtube channel](https://www.youtube.com/channel/UCcE4KYI0p1f6CmSwtzyg-ZA).
+```
+mkdir ~/beremiz_runtime_workdir
+~/Beremiz/venv/python ~/Beremiz/beremiz/Beremiz_service.py -p 61194 -i localhost -x 0 -a 1 ~/beremiz_runtime_workdir
+```
+
+To connect IDE with runtime, enter target location URI in project's settings (project->Config->BeremizRoot/URI_location) pointed to your running Beremiz service in this case :
+
+```
+PYRO://127.0.0.1:61194
+```
+
+If project's URL is 'LOCAL://', then IDE launches on demand a local instance of Beremiz python runtime working on a temporary directory.
+
+## Build documentation
+
+Source code for documentation is stored in `doc` directory in project's source tree.
+It's written in reStructuredText (ReST) and uses Sphinx to generate documentation in different formats.
+
+To build documentation you need following packages on Ubuntu/Debian:
+
+```
+sudo apt-get install build-essential python-sphynx
+```
+
+### Documentation in HTML
+
+Build documentation
+
+```
+cd ~/Beremiz/doc
+make all
+```
+
+Result documentation is stored in directories `doc/_build/dirhtml*`.
+
+### Documentation in PDF
+
+To build pdf documentation you have to install additional packages on Ubuntu/Debian:
+
+```
+sudo apt-get install textlive-latex-base texlive-latex-recommended \
+     texlive-fonts-recommended texlive-latex-extra
+```
+
+Build documentation
+
+```
+cd ~/Beremiz/doc
+make latexpdf
+```
+
+Result documentation is stored in `doc/_build/latex/Beremiz.pdf`.
 
 ## Documentation ##
 
  * See [Beremiz youtube channel](https://www.youtube.com/channel/UCcE4KYI0p1f6CmSwtzyg-ZA) to get quick information how to use Beremiz IDE.
- 
- * [Official user manual](http://beremiz.readthedocs.io/) is built from sources in doc directory.
+
+ * [Official documentation](http://beremiz.readthedocs.io/) is built from sources in doc directory.
    Documentation does not cover all aspects of Beremiz use yet.
    Contribution are very welcome!
    
@@ -126,12 +220,3 @@
 
  * See official [Beremiz website](http://www.beremiz.org/) for more information.
 
-## Support and development ##
-
-Main community support channel is [mailing list](https://sourceforge.net/p/beremiz/mailman/beremiz-devel/) (beremiz-devel@lists.sourceforge.net).
-
-The list is moderated and requires subscription for posting to it.
-
-To subscribe to the mailing list go [here](https://sourceforge.net/p/beremiz/mailman/beremiz-devel/).
-
-Searchable archive using search engine of your choice is available [here](http://beremiz-devel.2374573.n4.nabble.com/).
\ No newline at end of file
--- a/XSLTransform.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/XSLTransform.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 from lxml import etree
 
 class XSLTransform(object):
@@ -18,7 +18,7 @@
             extensions={("beremiz", name): call for name, call in xsltext})
 
     def transform(self, root, profile_run=False, **kwargs):
-        res = self.xslt(root, profile_run=profile_run, **{k: etree.XSLT.strparam(v) for k, v in kwargs.iteritems()})
+        res = self.xslt(root, profile_run=profile_run, **{k: etree.XSLT.strparam(v) for k, v in kwargs.items()})
         # print(self.xslt.error_log)
         return res
 
--- a/bacnet/BacnetSlaveEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/bacnet/BacnetSlaveEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -367,8 +367,7 @@
         "Engineering Units": {"GridCellEditor": wx.grid.GridCellChoiceEditor,
                               # use string renderer with choice editor!
                               "GridCellRenderer": wx.grid.GridCellStringRenderer,
-                              # syntax for GridCellChoiceEditor -> comma separated values
-                              "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits])}
+                              "GridCellEditorConstructorArgs": [x[0] for x in BACnetEngineeringUnits]}
     }
 
     # obj_properties should be a dictionary, with keys "Object Identifier",
@@ -576,7 +575,10 @@
                 PropertyName = self.BACnetObjectType.PropertyNames[col]
                 PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName]
                 grid.SetReadOnly(row, col, False)
-                grid.SetCellEditor(row, col, PropertyConfig["GridCellEditor"]())
+                GridCellEditorConstructorArgs = \
+                    PropertyConfig["GridCellEditorConstructorArgs"] \
+                    if "GridCellEditorConstructorArgs" in PropertyConfig else []
+                grid.SetCellEditor(row, col, PropertyConfig["GridCellEditor"](*GridCellEditorConstructorArgs))
                 grid.SetCellRenderer(row, col, PropertyConfig["GridCellRenderer"]())
                 grid.SetCellBackgroundColour(row, col, wx.WHITE)
                 grid.SetCellTextColour(row, col, wx.BLACK)
@@ -816,7 +818,7 @@
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28),
                 style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             controls_sizer.Add(button)
 
@@ -826,7 +828,7 @@
         # use only to enable drag'n'drop
         # self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
         self.VariablesGrid.Bind(
-            wx.grid.EVT_GRID_CELL_CHANGE,     self.OnVariablesGridCellChange)
+            wx.grid.EVT_GRID_CELL_CHANGING,     self.OnVariablesGridCellChange)
         # self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
         # self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,    self.OnVariablesGridEditorShown)
         self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
--- a/bacnet/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/bacnet/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,6 +23,6 @@
 # used in safety-critical situations without a full and competent review.
 
 
-from __future__ import absolute_import
+
 
 from bacnet.bacnet import *
--- a/bacnet/bacnet.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/bacnet/bacnet.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # This code is made available on the understanding that it will not be
 # used in safety-critical situations without a full and competent review.
 
-from __future__ import absolute_import
+
 
 import os
 from collections import Counter
--- a/bitbucket-pipelines.yml	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-image: skvorl/beremiz-requirements
-
-pipelines:
-  default:
-    - parallel:
-        - step:
-            name: Codestyle checks
-            script:
-              - ln -s /CanFestival-3 $BITBUCKET_CLONE_DIR/../CanFestival-3
-              - /usr/bin/python --version
-              - ./tests/tools/check_source.sh
-        - step:
-            name: Application tests
-            max-time: 10
-            script:
-              - ln -s /CanFestival-3 $BITBUCKET_CLONE_DIR/../CanFestival-3
-              - ./tests/tools/run_python_tests.sh
-    
\ No newline at end of file
--- a/c_ext/CFileEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/c_ext/CFileEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx.stc as stc
 
 from controls.CustomStyledTextCtrl import faces
--- a/c_ext/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/c_ext/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,5 +22,5 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from c_ext.c_ext import *
--- a/c_ext/c_ext.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/c_ext/c_ext.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import os
 
 from c_ext.CFileEditor import CFileEditor
--- a/canfestival/NetworkEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/canfestival/NetworkEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from networkeditortemplate import NetworkEditorTemplate
@@ -68,7 +68,7 @@
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(0)
 
-        main_sizer.AddWindow(self.NetworkNodes, 0, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(self.NetworkNodes, 0, border=5, flag=wx.GROW | wx.ALL)
 
         self.NetworkEditor.SetSizer(main_sizer)
 
--- a/canfestival/SlaveEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/canfestival/SlaveEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from subindextable import EditingPanel
--- a/canfestival/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/canfestival/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,5 +22,5 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from canfestival.canfestival import *
--- a/canfestival/canfestival.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/canfestival/canfestival.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import os
 import sys
 import shutil
@@ -352,7 +352,7 @@
 
     def GetVariableLocationTree(self):
         current_location = self.GetCurrentLocation()
-        nodeindexes = self.SlaveNodes.keys()
+        nodeindexes = list(self.SlaveNodes.keys())
         nodeindexes.sort()
         children = []
         children += [GetSlaveLocationTree(self.Manager.GetCurrentNodeCopy(),
--- a/canfestival/config_utils.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/canfestival/config_utils.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,12 +23,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
 import sys
 import getopt
-from past.builtins import long
+from functools import reduce
 
 # Translation between IEC types and Can Open types
 IECToCOType = {
@@ -63,7 +61,7 @@
 VariableIncrement = 0x100
 VariableStartIndex = {TPDO: 0x2000, RPDO: 0x4000}
 VariableDirText = {TPDO: "__I", RPDO: "__Q"}
-VariableTypeOffset = dict(zip(["", "X", "B", "W", "D", "L"], range(6)))
+VariableTypeOffset = dict(list(zip(["", "X", "B", "W", "D", "L"], list(range(6)))))
 
 TrashVariables = [(1, 0x01), (8, 0x05), (16, 0x06), (32, 0x07), (64, 0x1B)]
 
@@ -85,7 +83,7 @@
     """
 
     data = ("%" + str(size * 2) + "." + str(size * 2) + "X") % value
-    list_car = [data[i:i+2] for i in xrange(0, len(data), 2)]
+    list_car = [data[i:i+2] for i in range(0, len(data), 2)]
     list_car.reverse()
     return "".join([chr(int(car, 16)) for car in list_car])
 
@@ -170,7 +168,7 @@
         # Dictionary of location informations classed by name
         self.MasterMapping = {}
         # List of COB IDs available
-        self.ListCobIDAvailable = range(0x180, 0x580)
+        self.ListCobIDAvailable = list(range(0x180, 0x580))
         # Dictionary of mapping value where unexpected variables are stored
         self.TrashVariables = {}
         # Dictionary of pointed variables
@@ -303,7 +301,7 @@
             values = self.NodeList.GetSlaveNodeEntry(nodeid, index + 0x200)
             if values is not None and values[0] > 0:
                 # Check that all subindex upper than 0 equal 0 => configurable PDO
-                if reduce(lambda x, y: x and y, map(lambda x: x == 0, values[1:]), True):
+                if reduce(lambda x, y: x and y, [x == 0 for x in values[1:]], True):
                     cobid = self.NodeList.GetSlaveNodeEntry(nodeid, index, 1)
                     # If no COB ID defined in PDO, generate a new one (not used)
                     if cobid == 0:
@@ -374,7 +372,7 @@
                 nodeid, index, subindex = loc[:3]
 
                 # Check Id is in slave node list
-                if nodeid not in self.NodeList.SlaveNodes.keys():
+                if nodeid not in list(self.NodeList.SlaveNodes.keys()):
                     raise PDOmappingException(
                         _("Non existing node ID : {a1} (variable {a2})").
                         format(a1=nodeid, a2=name))
@@ -430,7 +428,7 @@
         #                         Search for locations already mapped
         # -------------------------------------------------------------------------------
 
-        for name, locationinfos in self.IECLocations.items():
+        for name, locationinfos in list(self.IECLocations.items()):
             node = self.NodeList.SlaveNodes[locationinfos["nodeid"]]["Node"]
 
             # Search if slave has a PDO mapping this locations
@@ -441,7 +439,7 @@
                 cobid = self.NodeList.GetSlaveNodeEntry(locationinfos["nodeid"], index - 0x200, 1)
 
                 # Add PDO to MasterMapping
-                if cobid not in self.MasterMapping.keys():
+                if cobid not in list(self.MasterMapping.keys()):
                     # Verify that PDO transmit type is conform to sync_TPDOs
                     transmittype = self.NodeList.GetSlaveNodeEntry(locationinfos["nodeid"], index - 0x200, 2)
                     if sync_TPDOs and transmittype != 0x01 or transmittype != 0xFF:
@@ -474,7 +472,7 @@
 
             else:
                 # Add location to those that haven't been mapped yet
-                if locationinfos["nodeid"] not in self.LocationsNotMapped.keys():
+                if locationinfos["nodeid"] not in list(self.LocationsNotMapped.keys()):
                     self.LocationsNotMapped[locationinfos["nodeid"]] = {TPDO: [], RPDO: []}
                 self.LocationsNotMapped[locationinfos["nodeid"]][locationinfos["pdotype"]].append((name, locationinfos))
 
@@ -482,7 +480,7 @@
         #                         Build concise DCF for the others locations
         # -------------------------------------------------------------------------------
 
-        for nodeid, locations in self.LocationsNotMapped.items():
+        for nodeid, locations in list(self.LocationsNotMapped.items()):
             node = self.NodeList.SlaveNodes[nodeid]["Node"]
 
             # Initialize number of params and data to add to node DCF
@@ -531,7 +529,7 @@
         # -------------------------------------------------------------------------------
 
         # Generate Master's Configuration from informations stored in MasterMapping
-        for cobid, pdo_infos in self.MasterMapping.items():
+        for cobid, pdo_infos in list(self.MasterMapping.items()):
             # Get next PDO index in MasterNode for this PDO type
             current_idx = self.CurrentPDOParamsIdx[pdo_infos["type"]]
 
@@ -572,7 +570,7 @@
                     continue
                 new_index = False
 
-                if isinstance(variable, (int, long)):
+                if isinstance(variable, int):
                     # If variable is an integer then variable is unexpected
                     self.MasterNode.SetEntry(current_idx + 0x200, subindex, self.TrashVariables[variable])
                 else:
@@ -735,7 +733,7 @@
 
     # Extract workspace base folder
     base_folder = sys.path[0]
-    for i in xrange(3):
+    for i in range(3):
         base_folder = os.path.split(base_folder)[0]
     # Add CanFestival folder to search pathes
     sys.path.append(os.path.join(base_folder, "CanFestival-3", "objdictgen"))
--- a/connectors/ConnectorBase.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/ConnectorBase.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import hashlib
 
 
@@ -13,8 +13,8 @@
 
     def BlobFromFile(self, filepath, seed):
         s = hashlib.new('md5')
-        s.update(seed)
-        blobID = self.SeedBlob(seed)
+        s.update(seed.encode())
+        blobID = self.SeedBlob(seed.encode())
         with open(filepath, "rb") as f:
             while blobID == s.digest():
                 chunk = f.read(self.chuncksize)
--- a/connectors/PYRO/PSK_Adapter.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/PYRO/PSK_Adapter.py	Wed Oct 04 16:24:00 2023 +0200
@@ -28,8 +28,8 @@
 but using Pre Shared Keys instead of Certificates
 """
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 
 import socket
 import re
--- a/connectors/PYRO/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/PYRO/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,33 +23,22 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
-import traceback
 from time import sleep
 import copy
 import socket
 import os.path
 
-import Pyro
-import Pyro.core
-import Pyro.util
-from Pyro.errors import PyroError
+import Pyro5
+import Pyro5.client
+import Pyro5.errors
 
-import PSKManagement as PSK
-from connectors.PYRO.PSK_Adapter import setupPSKAdapter
+# TODO: PSK
+
 from runtime import PlcStatus
+import importlib
 
 
-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:
-        setupPSKAdapter()
+Pyro5.config.SERIALIZER = "msgpack"
 
 
 def PYRO_connector_factory(uri, confnodesroot):
@@ -59,34 +48,21 @@
     confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri)
 
     scheme, location = uri.split("://")
-    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
-        secpath = os.path.join(str(confnodesroot.ProjectPath), 'psk', ID+'.secret')
-        if not os.path.exists(secpath):
-            confnodesroot.logger.write_error(
-                'Error: Pre-Shared-Key Secret in %s is missing!\n' % secpath)
-            return None
-        secret = open(secpath).read().partition(':')[2].rstrip('\n\r')
-        Pyro.config.PYROPSK = (secret, ID)
-        # strip ID from URL, so that pyro can understand it.
-        location = url
-    else:
-        schemename = "PYROLOC"
+
+    # TODO: use ssl
+
+    schemename = "PYRO"
 
     # Try to get the proxy object
     try:
-        RemotePLCObjectProxy = Pyro.core.getAttrProxyForURI(schemename + "://" + location + "/PLCObject")
+        RemotePLCObjectProxy = Pyro5.client.Proxy(f"{schemename}:PLCObject@{location}")
     except Exception as e:
         confnodesroot.logger.write_error(
             _("Connection to {loc} failed with exception {ex}\n").format(
                 loc=location, ex=str(e)))
         return None
 
-    RemotePLCObjectProxy.adapter.setTimeout(60)
+    RemotePLCObjectProxy._pyroTimeout = 60
 
     def PyroCatcher(func, default=None):
         """
@@ -96,14 +72,13 @@
         def catcher_func(*args, **kwargs):
             try:
                 return func(*args, **kwargs)
-            except Pyro.errors.ConnectionClosedError as e:
+            except Pyro5.errors.ConnectionClosedError as e:
                 confnodesroot._SetConnector(None)
                 confnodesroot.logger.write_error(_("Connection lost!\n"))
-            except Pyro.errors.ProtocolError as e:
+            except Pyro5.errors.ProtocolError as e:
                 confnodesroot.logger.write_error(_("Pyro exception: %s\n") % e)
             except Exception as e:
-                # confnodesroot.logger.write_error(traceback.format_exc())
-                errmess = ''.join(Pyro.util.getPyroTraceback(e))
+                errmess = ''.join(Pyro5.errors.get_pyro_traceback())
                 confnodesroot.logger.write_error(errmess + "\n")
                 print(errmess)
                 confnodesroot._SetConnector(None)
--- a/connectors/PYRO_dialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/PYRO_dialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 
 from itertools import repeat, islice, chain
 
@@ -14,9 +14,9 @@
          ('port', _("Port:"))]
 
 # (scheme, model, secure)
-models = [("LOCAL", [], False), ("PYRO", model, False), ("PYROS", model, True)]
+models = [("LOCAL", [], False), ("PYRO", model, False)]
 
-Schemes = list(zip(*models)[0])
+Schemes = list(zip(*models))[0]
 
 _PerSchemeConf = {sch: (mod, sec) for sch, mod, sec in models}
 
--- a/connectors/SchemeEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/SchemeEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 
 from functools import partial
 import wx
@@ -28,19 +28,19 @@
                     (wx.StaticText(self, label=label),
                      wx.ALIGN_CENTER_VERTICAL),
                     (txtctrl, wx.GROW)]:
-                self.fieldsizer.AddWindow(win, flag=flag)
+                self.fieldsizer.Add(win, flag=flag)
 
         self.fieldsizer.AddSpacer(20)
 
         if self.EnableIDSelector:
             self.mainsizer = wx.FlexGridSizer(cols=2, hgap=10, vgap=10)
-            self.mainsizer.AddSizer(self.fieldsizer)
+            self.mainsizer.Add(self.fieldsizer)
             self.idselector = IDBrowser(
                 self, parent.ctr,
                 # use a callafter, as editor can be deleted by calling SetURI
                 partial(wx.CallAfter, parent.SetURI),
                 self.txtctrls["ID"].SetValue)
-            self.mainsizer.AddWindow(self.idselector)
+            self.mainsizer.Add(self.idselector)
             self.SetSizer(self.mainsizer)
         else:
             self.SetSizer(self.fieldsizer)
--- a/connectors/WAMP/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/WAMP/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,13 +23,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import sys
 import traceback
 from functools import partial
 from threading import Thread, Event
-from six import text_type as text
 
 from twisted.internet import reactor, threads
 from autobahn.twisted import wamp
@@ -85,7 +82,7 @@
 
         # create a WAMP application session factory
         component_config = types.ComponentConfig(
-            realm=text(realm),
+            realm=str(realm),
             extra={"ID": ID})
         session_factory = wamp.ApplicationSessionFactory(
             config=component_config)
@@ -111,7 +108,7 @@
         reactor.run(installSignalHandlers=False)
 
     def WampSessionProcMapper(funcname):
-        wampfuncname = text('.'.join((ID, funcname)))
+        wampfuncname = str('.'.join((ID, funcname)))
 
         def catcher_func(*args, **kwargs):
             if _WampSession is not None:
--- a/connectors/WAMP_dialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/WAMP_dialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 
 from itertools import repeat, islice, chain
 
--- a/connectors/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/connectors/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -26,15 +26,17 @@
 # Package initialisation
 
 
-from __future__ import absolute_import
+import os
+import importlib
 from os import listdir, path
 from connectors.ConnectorBase import ConnectorBase
 
-connectors_packages = ["PYRO", "WAMP"]
+connectors_packages = ["PYRO"]
 
 
 def _GetLocalConnectorClassFactory(name):
-    return lambda: getattr(__import__(name, globals(), locals()), name + "_connector_factory")
+    return lambda: getattr(importlib.import_module(f"connectors.{name}"),
+                           f"{name}_connector_factory")
 
 
 connectors = {name: _GetLocalConnectorClassFactory(name)
@@ -53,13 +55,15 @@
         per_URI_connectors = {}
         schemes = []
         for con_name in connectors_packages:
-            module = __import__(con_name + '_dialog', globals(), locals())
+            module = importlib.import_module(f"connectors.{con_name}_dialog")
 
             for scheme in module.Schemes:
                 per_URI_connectors[scheme] = getattr(module, con_name + '_dialog')
                 schemes += [scheme]
 
 
+LocalHost = os.environ.get("BEREMIZ_LOCAL_HOST", "127.0.0.1")
+
 def ConnectorFactory(uri, confnodesroot):
     """
     Return a connector corresponding to the URI
@@ -76,9 +80,8 @@
         # pyro connection to local runtime
         # started on demand, listening on random port
         scheme = "PYRO"
-        runtime_port = confnodesroot.AppFrame.StartLocalRuntime(
-            taskbaricon=True)
-        uri = "PYROLOC://127.0.0.1:" + str(runtime_port)
+        runtime_port = confnodesroot.StartLocalRuntime()
+        uri = f"PYRO://{LocalHost}:{runtime_port}"
 
     # commented code to enable for MDNS:// support
     # elif _scheme == "MDNS":
--- a/controls/CustomEditableListBox.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomEditableListBox.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,15 +23,15 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
-import wx.gizmos
+import wx.adv
 
 
-class CustomEditableListBox(wx.gizmos.EditableListBox):
+class CustomEditableListBox(wx.adv.EditableListBox):
 
     def __init__(self, *args, **kwargs):
-        wx.gizmos.EditableListBox.__init__(self, *args, **kwargs)
+        wx.adv.EditableListBox.__init__(self, *args, **kwargs)
 
         listbox = self.GetListCtrl()
         listbox.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
@@ -44,7 +44,7 @@
                 (self.GetDelButton(), _("Delete item"), "_OnDelButton"),
                 (self.GetUpButton(), _("Move up"), "_OnUpButton"),
                 (self.GetDownButton(), _("Move down"), "_OnDownButton")]:
-            button.SetToolTipString(tooltip)
+            button.SetToolTip(tooltip)
             button.Bind(wx.EVT_BUTTON, self.GetButtonPressedFunction(call_function))
 
         self.Editing = False
--- a/controls/CustomGrid.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomGrid.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 import wx.grid
 
--- a/controls/CustomIntCtrl.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomIntCtrl.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 import wx.lib.intctrl
 
--- a/controls/CustomStyledTextCtrl.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomStyledTextCtrl.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,11 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 from functools import reduce
 import wx
 import wx.stc
-from six.moves import xrange
 
 if wx.Platform == '__WXMSW__':
     faces = {
@@ -72,7 +70,7 @@
     new_length = len(new)
     common_length = min(old_length, new_length)
     i = 0
-    for i in xrange(common_length):
+    for i in range(common_length):
         if old[i] != new[i]:
             break
     if old_length < new_length:
@@ -102,12 +100,12 @@
                 x, _y = event.GetPosition()
                 margin_width = reduce(
                     lambda x, y: x + y,
-                    [self.GetMarginWidth(i) for i in xrange(3)],
+                    [self.GetMarginWidth(i) for i in range(3)],
                     0)
                 if x <= margin_width:
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
                 else:
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_IBEAM))
             else:
                 event.Skip()
         else:
@@ -116,3 +114,10 @@
     def AppendText(self, text):
         self.GotoPos(self.GetLength())
         self.AddText(text)
+
+    if wx.VERSION < (4, 1, 0):
+        def StartStyling(self, pos, mask=0xff):
+            wx.stc.StyledTextCtrl.StartStyling(self, pos, mask)
+    else:
+        def StartStyling(self, pos, *ignored):
+            wx.stc.StyledTextCtrl.StartStyling(self, pos)
--- a/controls/CustomTable.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomTable.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 import wx.grid
 
@@ -40,7 +40,7 @@
     """
     def __init__(self, parent, data, colnames):
         # The base class must be initialized *first*
-        wx.grid.PyGridTableBase.__init__(self)
+        wx.grid.GridTableBase.__init__(self)
         self.data = data
         self.colnames = colnames
         self.Highlights = {}
@@ -64,7 +64,7 @@
             return self.colnames[col]
 
     def GetRowLabelValue(self, row, translate=True):
-        return row
+        return str(row)
 
     def GetValue(self, row, col):
         if row < self.GetNumberRows():
@@ -204,8 +204,8 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            for _row, row_highlights in self.Highlights.iteritems():
-                row_items = row_highlights.items()
+            for _row, row_highlights in self.Highlights.items():
+                row_items = list(row_highlights.items())
                 for col, col_highlights in row_items:
                     if highlight_type in col_highlights:
                         col_highlights.remove(highlight_type)
--- a/controls/CustomToolTip.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomToolTip.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from controls.CustomStyledTextCtrl import faces
@@ -137,7 +137,7 @@
         max_width = max_height = 0
 
         # Create a memory DC for calculating text extent
-        dc = wx.MemoryDC(wx.EmptyBitmap(1, 1))
+        dc = wx.MemoryDC(wx.Bitmap(1, 1))
         dc.SetFont(self.Font)
 
         # Compute max tip text size
@@ -175,7 +175,6 @@
         dc.SetFont(self.Font)
 
         # Draw Tool tip
-        dc.BeginDrawing()
         tip_width, tip_height = self.GetToolTipSize()
 
         # Draw background rectangle
@@ -188,6 +187,5 @@
             _line_width, line_height = dc.GetTextExtent(line)
             line_offset += line_height
 
-        dc.EndDrawing()
 
         event.Skip()
--- a/controls/CustomTree.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/CustomTree.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import wx
 import wx.lib.agw.customtreectrl as CT
 
@@ -120,9 +120,9 @@
             _item, flags = self.HitTest(pos)
 
             bitmap_rect = self.GetBitmapRect()
-            if ((bitmap_rect.InsideXY(pos.x, pos.y) or
+            if ((bitmap_rect.Contains(pos.x, pos.y) or
                  flags & wx.TREE_HITTEST_NOWHERE) and self.AddMenu is not None):
-                wx.CallAfter(self.PopupMenuXY, self.AddMenu, pos.x, pos.y)
+                wx.CallAfter(self.PopupMenu, self.AddMenu, pos.x, pos.y)
         event.Skip()
 
     def OnEraseBackground(self, event):
--- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 from time import time as gettime
 from cycler import cycler
 
@@ -33,10 +31,8 @@
 import matplotlib
 import matplotlib.pyplot
 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
-from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
 from matplotlib.backends.backend_agg import FigureCanvasAgg
 from mpl_toolkits.mplot3d import Axes3D
-from six.moves import xrange
 
 from editors.DebugViewer import REFRESH_PERIOD
 from controls.DebugVariablePanel.DebugVariableViewer import *
@@ -44,7 +40,7 @@
 
 
 # Graph variable display type
-GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
+GRAPH_PARALLEL, GRAPH_ORTHOGONAL = list(range(2))
 
 # Canvas height
 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
@@ -121,15 +117,6 @@
         self.ParentControl = parent
         self.ParentWindow = window
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to Debug Variable Graphic Viewer and Debug Variable
-        # Panel
-        self.ParentControl = None
-        self.ParentWindow = None
-
     def OnDragOver(self, x, y, d):
         """
         Function called when mouse is dragged over Drop Target
@@ -166,6 +153,7 @@
         # Display message if data is invalid
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
 
         # Data contain a reference to a variable to debug
         elif values[1] == "debug":
@@ -174,7 +162,7 @@
             # If mouse is dropped in graph canvas bounding box and graph is
             # not 3D canvas, graphs will be merged
             rect = self.ParentControl.GetAxesBoundingBox()
-            if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
+            if not self.ParentControl.Is3DCanvas() and rect.Contains(x, y):
                 # Default merge type is parallel
                 merge_type = GRAPH_PARALLEL
 
@@ -182,7 +170,7 @@
                 # wall be merged orthogonally
                 merge_rect = wx.Rect(rect.x, rect.y,
                                      rect.width / 2., rect.height)
-                if merge_rect.InsideXY(x, y):
+                if merge_rect.Contains(x, y):
                     merge_type = GRAPH_ORTHOGONAL
 
                 # Merge graphs
@@ -209,6 +197,8 @@
                     self.ParentWindow.InsertValue(values[0],
                                                   target_idx,
                                                   force=True)
+            return True
+        return False
 
     def OnLeave(self):
         """
@@ -278,7 +268,6 @@
 
         FigureCanvas.__init__(self, parent, -1, self.Figure)
         self.SetWindowStyle(wx.WANTS_CHARS)
-        self.SetBackgroundColour(wx.WHITE)
 
         # Bind wx events
         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
@@ -490,11 +479,12 @@
         @param item: Item from which data to export, all items if None
         (default None)
         """
-        self.ParentWindow.CopyDataToClipboard(
-            [(item, [entry for entry in item.GetData()])
-             for item in (self.Items
-                          if item is None
-                          else [item])])
+        if item is not None and item.GetData():
+            self.ParentWindow.CopyDataToClipboard(
+                [(item, [entry for entry in item.GetData()])
+                 for item in (self.Items
+                              if item is None
+                              else [item])])
 
     def OnZoomFitButton(self):
         """
@@ -565,7 +555,7 @@
         """
         start_tick, end_tick = self.ParentWindow.GetRange()
         cursor_tick = None
-        items = self.ItemsDict.values()
+        items = list(self.ItemsDict.values())
 
         # Graph is orthogonal
         if self.GraphType == GRAPH_ORTHOGONAL:
@@ -591,7 +581,7 @@
 
             # Search for point that tick is the nearest from mouse X position
             # and set cursor tick to the tick of this point
-            if len(data) > 0:
+            if data is not None and len(data) > 0:
                 cursor_tick = data[numpy.argmin(
                     numpy.abs(data[:, 0] - event.xdata)), 0]
 
@@ -625,7 +615,7 @@
                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
                 # Check if mouse was over label
-                if rect.InsideXY(x, y):
+                if rect.Contains(x, y):
                     item_idx = i
                     break
 
@@ -639,7 +629,7 @@
                 # parent
                 xw, yw = self.GetPosition()
                 self.ParentWindow.StartDragNDrop(
-                    self, self.ItemsDict.values()[item_idx],
+                    self, list(self.ItemsDict.values())[item_idx],
                     x + xw, y + yw,  # Current mouse position
                     x + xw, y + yw)  # Mouse position when button was clicked
 
@@ -677,7 +667,7 @@
         if self.ParentWindow.IsDragging():
             _width, height = self.GetSize()
             xw, yw = self.GetPosition()
-            item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
+            item = list(self.ParentWindow.DraggingAxesPanel.ItemsDict.values())[0]
             # Give mouse position in wx coordinate of parent
             self.ParentWindow.StopDragNDrop(item.GetVariable(),
                                             xw + event.x, yw + height - event.y)
@@ -736,7 +726,7 @@
                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
                 # Check if mouse was over label
-                if rect.InsideXY(event.x, height - event.y):
+                if rect.Contains(event.x, height - event.y):
                     item_idx = i
                     menu_direction = dir
                     break
@@ -744,7 +734,7 @@
             # If mouse is over an item label,
             if item_idx is not None:
                 self.PopupContextualButtons(
-                    self.ItemsDict.values()[item_idx],
+                    list(self.ItemsDict.values())[item_idx],
                     rect, menu_direction)
                 return
 
@@ -756,7 +746,7 @@
             # Update resize highlight
             if event.y <= 5:
                 if self.SetHighlight(HIGHLIGHT_RESIZE):
-                    self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
+                    self.SetCursor(wx.Cursor(wx.CURSOR_SIZENS))
                     self.ParentWindow.ForceRefresh()
             else:
                 if self.SetHighlight(HIGHLIGHT_NONE):
@@ -782,7 +772,7 @@
                     xw, yw = self.GetPosition()
                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
                     self.ParentWindow.StartDragNDrop(
-                        self, self.ItemsDict.values()[0],
+                        self, list(self.ItemsDict.values())[0],
                         # Current mouse position
                         event.x + xw, height - event.y + yw,
                         # Mouse position when button was clicked
@@ -832,7 +822,7 @@
         # Check that double click was done inside figure
         pos = event.GetPosition()
         rect = self.GetAxesBoundingBox()
-        if rect.InsideXY(pos.x, pos.y):
+        if rect.Contains(pos.x, pos.y):
             # Reset Cursor tick to value before starting clicking
             self.ParentWindow.SetCursorTick(self.StartCursorTick)
             # Toggle to text Viewer(s)
@@ -926,10 +916,10 @@
 
         # Mouse is over Viewer figure and graph is not 3D
         bbox = self.GetAxesBoundingBox()
-        if bbox.InsideXY(x, y) and not self.Is3DCanvas():
+        if bbox.Contains(x, y) and not self.Is3DCanvas():
             rect = wx.Rect(bbox.x, bbox.y, bbox.width // 2, bbox.height)
             # Mouse is over Viewer left part of figure
-            if rect.InsideXY(x, y):
+            if rect.Contains(x, y):
                 self.SetHighlight(HIGHLIGHT_LEFT)
 
             # Mouse is over Viewer right part of figure
@@ -1027,7 +1017,7 @@
         # Graph type is parallel or orthogonal in 3D
         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
             num_item = len(self.Items)
-            for idx in xrange(num_item):
+            for idx in range(num_item):
 
                 # Get color from color cycle (black if only one item)
                 color = ('k' if num_item == 1 else
@@ -1091,7 +1081,7 @@
         # Update position of items labels
         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
             num_item = len(self.Items)
-            for idx in xrange(num_item):
+            for idx in range(num_item):
 
                 # In 3D graph items variable label are not displayed
                 if not self.Is3DCanvas():
@@ -1187,7 +1177,7 @@
                 # each variable
                 start_tick = max(start_tick, self.GetItemsMinCommonTick())
                 end_tick = max(end_tick, start_tick)
-                items = self.ItemsDict.values()
+                items = list(self.ItemsDict.values())
 
                 # Get data and range for first variable (X coordinate)
                 x_data, x_min, x_max = items[0].GetDataAndValueRange(
@@ -1329,7 +1319,7 @@
             item.GetValue(self.CursorTick)
             if self.CursorTick is not None
             else (item.GetValue(), item.IsForced())) for item in self.Items]
-        values, forced = zip(*args)
+        values, forced = list(zip(*args))
 
         # Get path of each variable displayed simplified using panel variable
         # name mask
@@ -1337,7 +1327,7 @@
                   for item in self.Items]
 
         # Get style for each variable according to
-        styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
+        styles = [{True: 'italic', False: 'normal'}[x] for x in forced]
 
         # Graph is orthogonal 3D, set variables path as 3D axis label
         if self.Is3DCanvas():
@@ -1369,9 +1359,9 @@
         FigureCanvasAgg.draw(self)
 
         # Get bitmap of figure rendered
-        self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
-        if wx.VERSION < (3, 0, 0):
-            self.bitmap.UseAlpha()
+        agg_bitmap = self.get_renderer()
+        self.bitmap = wx.Bitmap.FromBufferRGBA(int(agg_bitmap.width), int(agg_bitmap.height),
+                                        agg_bitmap.buffer_rgba())
 
         # Create DC for rendering graphics in bitmap
         destDC = wx.MemoryDC()
@@ -1381,8 +1371,6 @@
         # rendering
         destGC = wx.GCDC(destDC)
 
-        destGC.BeginDrawing()
-
         # Get canvas size and figure bounding box in canvas
         width, height = self.GetSize()
         bbox = self.GetAxesBoundingBox()
@@ -1409,7 +1397,5 @@
         # Draw other Viewer common elements
         self.DrawCommonElements(destGC, self.GetButtons())
 
-        destGC.EndDrawing()
-
         self._isDrawn = True
         self.gui_repaint(drawDC=drawDC)
--- a/controls/DebugVariablePanel/DebugVariableItem.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/DebugVariableItem.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from datetime import timedelta
 import binascii
 import numpy as np
@@ -63,13 +63,6 @@
         # Get Variable data type
         self.RefreshVariableType()
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Reset reference to debug variable panel
-        self.Parent = None
-
     def SetVariable(self, variable):
         """
         Set path of variable
@@ -262,7 +255,7 @@
 
                 if self.VariableType in ["STRING", "WSTRING"]:
                     # String data value is CRC
-                    num_value = (binascii.crc32(value) & STRING_CRC_MASK)
+                    num_value = (binascii.crc32(value.encode()) & STRING_CRC_MASK)
                 elif self.VariableType in ["TIME", "TOD", "DT", "DATE"]:
                     # Numeric value of time type variables
                     # is represented in seconds
--- a/controls/DebugVariablePanel/DebugVariablePanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/DebugVariablePanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 from functools import reduce
 import numpy as np
 
@@ -92,13 +92,6 @@
         wx.TextDropTarget.__init__(self)
         self.ParentWindow = window
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to Debug Variable Panel
-        self.ParentWindow = None
-
     def OnDragOver(self, x, y, d):
         """
         Function called when mouse is dragged over Drop Target
@@ -134,6 +127,7 @@
         # Display message if data is invalid
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
 
         # Data contain a reference to a variable to debug
         elif values[1] == "debug":
@@ -147,6 +141,9 @@
             else:
                 self.ParentWindow.InsertValue(values[0], force=True)
 
+            return True
+        return False
+
     def OnLeave(self):
         """
         Function called when mouse is leave Drop Target
@@ -202,7 +199,6 @@
         # data is available
         self.Force = False
 
-        self.SetBackgroundColour(wx.WHITE)
 
         main_sizer = wx.BoxSizer(wx.VERTICAL)
 
@@ -221,14 +217,14 @@
         self.GraphicPanels = []
 
         graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(graphics_button_sizer, border=5, flag=wx.GROW | wx.ALL)
 
         range_label = wx.StaticText(self, label=_('Range:'))
-        graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        graphics_button_sizer.Add(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
-        graphics_button_sizer.AddWindow(self.CanvasRange, 1,
+        graphics_button_sizer.Add(self.CanvasRange, 1,
                                         border=5,
                                         flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
 
@@ -246,10 +242,10 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
-            graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
+            graphics_button_sizer.Add(button, border=5, flag=wx.LEFT)
 
         self.CanvasPosition = wx.ScrollBar(
             self, size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
@@ -263,19 +259,19 @@
                                  self.OnPositionChanging, self.CanvasPosition)
         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN,
                                  self.OnPositionChanging, self.CanvasPosition)
-        main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
+        main_sizer.Add(self.CanvasPosition, border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
         self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL | wx.GROW)
+        main_sizer.Add(self.TickSizer, border=5, flag=wx.ALL | wx.GROW)
 
         self.TickLabel = wx.StaticText(self)
-        self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
+        self.TickSizer.Add(self.TickLabel, border=5, flag=wx.RIGHT)
 
         self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_CENTER | wx.NO_BORDER)
-        self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT | wx.GROW)
+        self.TickSizer.Add(self.MaskLabel, 1, border=5, flag=wx.RIGHT | wx.GROW)
 
         self.TickTimeLabel = wx.StaticText(self)
-        self.TickSizer.AddWindow(self.TickTimeLabel)
+        self.TickSizer.Add(self.TickTimeLabel)
 
         self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL | wx.VSCROLL)
         self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
@@ -284,7 +280,7 @@
         self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
         self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
 
-        main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
+        main_sizer.Add(self.GraphicsWindow, 1, flag=wx.GROW)
 
         self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
         self.GraphicsWindow.SetSizer(self.GraphicsSizer)
@@ -441,7 +437,7 @@
             x, y = panel.GetPosition()
             width, height = panel.GetSize()
             rect = wx.Rect(x, y, width, height)
-            if rect.InsideXY(x_mouse, y_mouse) or \
+            if rect.Contains(x_mouse, y_mouse) or \
                idx == 0 and y_mouse < 0 or \
                idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]:
                 panel.RefreshHighlight(x_mouse - x, y_mouse - y)
@@ -488,7 +484,7 @@
             xw, yw = panel.GetPosition()
             width, height = panel.GetSize()
             bbox = wx.Rect(xw, yw, width, height)
-            if bbox.InsideXY(x_mouse, y_mouse):
+            if bbox.Contains(x_mouse, y_mouse):
                 panel.ShowButtons(True)
                 merge_type = GRAPH_PARALLEL
                 if isinstance(panel, DebugVariableTextViewer) or panel.Is3DCanvas():
@@ -497,9 +493,9 @@
                     wx.CallAfter(self.MoveValue, variable, idx, True)
                 else:
                     rect = panel.GetAxesBoundingBox(True)
-                    if rect.InsideXY(x_mouse, y_mouse):
+                    if rect.Contains(x_mouse, y_mouse):
                         merge_rect = wx.Rect(rect.x, rect.y, rect.width // 2, rect.height)
-                        if merge_rect.InsideXY(x_mouse, y_mouse):
+                        if merge_rect.Contains(x_mouse, y_mouse):
                             merge_type = GRAPH_ORTHOGONAL
                         wx.CallAfter(self.MergeGraphs, variable, idx, merge_type, force=True)
                     else:
@@ -510,7 +506,7 @@
                 return
         width, height = self.GraphicsWindow.GetVirtualSize()
         rect = wx.Rect(0, 0, width, height)
-        if rect.InsideXY(x_mouse, y_mouse):
+        if rect.Contains(x_mouse, y_mouse):
             wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
         self.ForceRefresh()
 
@@ -518,7 +514,7 @@
         self.GraphicsSizer.Clear()
 
         for panel in self.GraphicPanels:
-            self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
+            self.GraphicsSizer.Add(panel, flag=wx.GROW)
 
         self.GraphicsSizer.Layout()
         self.RefreshGraphicsWindowScrollbars()
--- a/controls/DebugVariablePanel/DebugVariableTextViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/DebugVariableTextViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 
 import wx
 
@@ -51,15 +51,6 @@
         self.ParentControl = parent
         self.ParentWindow = window
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to Debug Variable Text Viewer and Debug Variable
-        # Panel
-        self.ParentControl = None
-        self.ParentWindow = None
-
     def OnDragOver(self, x, y, d):
         """
         Function called when mouse is dragged over Drop Target
@@ -96,6 +87,7 @@
         # Display message if data is invalid
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
 
         # Data contain a reference to a variable to debug
         elif values[1] == "debug":
@@ -118,6 +110,10 @@
                 self.ParentWindow.InsertValue(values[0],
                                               target_idx,
                                               force=True)
+            return True
+        return False
+
+        return True
 
     def OnLeave(self):
         """
@@ -195,7 +191,7 @@
 
         # Create buffered DC for drawing in panel
         width, height = self.GetSize()
-        bitmap = wx.EmptyBitmap(width, height)
+        bitmap = wx.Bitmap(width, height)
         dc = wx.BufferedDC(wx.PaintDC(self), bitmap)
         dc.Clear()
 
@@ -203,10 +199,8 @@
         # rendering
         gc = wx.GCDC(dc)
 
-        gc.BeginDrawing()
-
         # Get first item
-        item = self.ItemsDict.values()[0]
+        item = list(self.ItemsDict.values())[0]
 
         # Get item variable path masked according Debug Variable Panel mask
         item_path = item.GetVariable(
@@ -232,15 +226,13 @@
         # Draw other Viewer common elements
         self.DrawCommonElements(gc)
 
-        gc.EndDrawing()
-
     def OnLeftDown(self, event):
         """
         Function called when mouse left button is pressed
         @param event: wx.MouseEvent
         """
         # Get first item
-        item = self.ItemsDict.values()[0]
+        item = list(self.ItemsDict.values())[0]
 
         # Calculate item path bounding box
         _width, height = self.GetSize()
@@ -252,7 +244,7 @@
         # start a move drag'n drop of item variable
         x, y = event.GetPosition()
         item_path_bbox = wx.Rect(20, (height - h) / 2, w, h)
-        if item_path_bbox.InsideXY(x, y):
+        if item_path_bbox.Contains(x, y):
             self.ShowButtons(False)
             data = wx.TextDataObject(str((item.GetVariable(), "debug", "move")))
             dragSource = wx.DropSource(self)
@@ -279,7 +271,7 @@
         @param event: wx.MouseEvent
         """
         # Only numeric variables can be toggled to graph canvas
-        if self.ItemsDict.values()[0].IsNumVariable():
+        if list(self.ItemsDict.values())[0].IsNumVariable():
             self.ParentWindow.ToggleViewerType(self)
 
     def OnPaint(self, event):
--- a/controls/DebugVariablePanel/DebugVariableViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/DebugVariableViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,13 +23,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 from collections import OrderedDict
 from functools import reduce
 
 import wx
-from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
 
 from dialogs.ForceVariableDialog import ForceVariableDialog
 
@@ -39,7 +38,7 @@
  HIGHLIGHT_AFTER,
  HIGHLIGHT_LEFT,
  HIGHLIGHT_RIGHT,
- HIGHLIGHT_RESIZE] = range(6)
+ HIGHLIGHT_RESIZE] = list(range(6))
 
 # Viewer highlight styles
 HIGHLIGHT = {
@@ -66,7 +65,7 @@
         items = [] if items is None else items
         self.ItemsDict = OrderedDict([(item.GetVariable(), item)
                                       for item in items])
-        self.Items = self.ItemsDict.viewvalues()
+        self.Items = self.ItemsDict.values()
 
         # Variable storing current highlight displayed in Viewer
         self.Highlight = HIGHLIGHT_NONE
@@ -74,13 +73,6 @@
         self.Buttons = []
         self.InitHighlightPensBrushes()
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to Debug Variable Panel
-        self.ParentWindow = None
-
     def InitHighlightPensBrushes(self):
         """
         Init global pens and brushes
@@ -111,7 +103,7 @@
         Return items displayed by Viewer
         @return: List of items displayed in Viewer
         """
-        return self.ItemsDict.values()
+        return list(self.ItemsDict.values())
 
     def AddItem(self, item):
         """
@@ -150,7 +142,7 @@
         Function that unsubscribe and remove every item that store values of
         a variable that doesn't exist in PLC anymore
         """
-        for item in self.ItemsDict.values()[:]:
+        for item in self.ItemsDict.values():
             iec_path = item.GetVariable()
 
             # Check that variablepath exist in PLC
@@ -310,8 +302,10 @@
                 srcX = srcBBox.x - (srcPos.x if destBBox.x == 0 else 0)
                 srcY = srcBBox.y - (srcPos.y if destBBox.y == 0 else 0)
 
-                srcBmp = _convert_agg_to_wx_bitmap(
-                    srcPanel.get_renderer(), None)
+                agg_bitmap = srcPanel.get_renderer()
+                srcBmp = wx.Bitmap.FromBufferRGBA(int(agg_bitmap.width), int(agg_bitmap.height),
+                                        agg_bitmap.buffer_rgba())
+
                 srcDC = wx.MemoryDC()
                 srcDC.SelectObject(srcBmp)
 
@@ -347,13 +341,13 @@
         """
         Function called when Force button is pressed
         """
-        self.ForceValue(self.ItemsDict.values()[0])
+        self.ForceValue(list(self.ItemsDict.values())[0])
 
     def OnReleaseButton(self):
         """
         Function called when Release button is pressed
         """
-        self.ReleaseValue(self.ItemsDict.values()[0])
+        self.ReleaseValue(list(self.ItemsDict.values())[0])
 
     def OnMouseDragging(self, x, y):
         """
--- a/controls/DebugVariablePanel/GraphButton.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/GraphButton.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from util.BitmapLibrary import GetBitmap
@@ -58,13 +58,6 @@
         # Save reference to callback function
         self.Callback = callback
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to callback function
-        self.callback = None
-
     def SetBitmap(self, bitmap):
         """
         Set bitmap to use for button
@@ -146,7 +139,7 @@
         # Test if point is inside button
         w, h = self.Bitmap.GetSize()
         rect = wx.Rect(self.Position.x, self.Position.y, w, h)
-        return rect.InsideXY(x, y)
+        return rect.Contains(x, y)
 
     def ProcessCallback(self):
         """
--- a/controls/DebugVariablePanel/RingBuffer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/RingBuffer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -44,7 +44,7 @@
         note: only when this function is called, is an O(size) performance hit incurred,
         and this cost is amortized over the whole padding space
         """
-        print 'compacting'
+        print('compacting')
         self.buffer[:self.count] = self.view
         self.cursor -= self.size
 
--- a/controls/DebugVariablePanel/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DebugVariablePanel/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,5 +22,5 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from controls.DebugVariablePanel.DebugVariablePanel import DebugVariablePanel
--- a/controls/DiscoveryPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DiscoveryPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,9 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import socket
-from six.moves import xrange
 import wx
 import wx.lib.mixins.listctrl as listmix
 from zeroconf import ServiceBrowser, Zeroconf, get_all_addresses
@@ -44,17 +42,17 @@
 class DiscoveryPanel(wx.Panel, listmix.ColumnSorterMixin):
 
     def _init_coll_MainSizer_Items(self, parent):
-        parent.AddWindow(self.staticText1,    0, border=20, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
-        parent.AddWindow(self.ServicesList,   0, border=20, flag=wx.LEFT | wx.RIGHT | wx.GROW)
-        parent.AddSizer(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
+        parent.Add(self.staticText1,    0, border=20, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
+        parent.Add(self.ServicesList,   0, border=20, flag=wx.LEFT | wx.RIGHT | wx.GROW)
+        parent.Add(self.ButtonGridSizer, 0, border=20, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
     def _init_coll_MainSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
         parent.AddGrowableRow(1)
 
     def _init_coll_ButtonGridSizer_Items(self, parent):
-        parent.AddWindow(self.RefreshButton, 0, border=0, flag=0)
-        # parent.AddWindow(self.ByIPCheck, 0, border=0, flag=0)
+        parent.Add(self.RefreshButton, 0, border=0, flag=0)
+        # parent.Add(self.ByIPCheck, 0, border=0, flag=0)
 
     def _init_coll_ButtonGridSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
@@ -129,10 +127,23 @@
         self.IfacesMonitorTimer.Start(2000)
         self.Bind(wx.EVT_TIMER, self.IfacesMonitor, self.IfacesMonitorTimer)
 
+    def _cleanup(self):
+        if self.IfacesMonitorTimer is not None:
+            self.IfacesMonitorTimer.Stop()
+            self.IfacesMonitorTimer = None
+        if self.Browser is not None:
+            self.Browser.cancel()
+            self.Browser = None
+        if self.ZeroConfInstance is not None:
+            self.ZeroConfInstance.close()
+            self.ZeroConfInstance = None
+
     def __del__(self):
-        self.IfacesMonitorTimer.Stop()
-        self.Browser.cancel()
-        self.ZeroConfInstance.close()
+        self._cleanup()
+
+    def Destroy(self):
+        self._cleanup()
+        wx.Panel.Destroy(self)
 
     def IfacesMonitor(self, event):
         NewState = get_all_addresses(socket.AF_INET)
@@ -190,8 +201,7 @@
         if self.LatestSelection is not None:
             # if self.ByIPCheck.IsChecked():
             svcname, scheme, host, port = \
-                map(lambda col: self.getColumnText(self.LatestSelection, col),
-                    range(4))
+                [self.getColumnText(self.LatestSelection, col) for col in range(4)]
             return ("%s://%s:%s#%s" % (scheme, host, port, svcname)) \
                 if scheme[-1] == "S" \
                 else ("%s://%s:%s" % (scheme, host, port))
@@ -210,7 +220,7 @@
         '''
 
         # loop through the list items looking for the service that went offline
-        for idx in xrange(self.ServicesList.GetItemCount()):
+        for idx in range(self.ServicesList.GetItemCount()):
             # this is the unique identifier assigned to the item
             item_id = self.ServicesList.GetItemData(idx)
 
--- a/controls/DurationCellEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/DurationCellEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from dialogs.DurationEditorDialog import DurationEditorDialog
@@ -46,12 +46,12 @@
         self.Duration = wx.TextCtrl(self, size=wx.Size(0, -1),
                                     style=wx.TE_PROCESS_ENTER)
         self.Duration.Bind(wx.EVT_KEY_DOWN, self.OnDurationChar)
-        main_sizer.AddWindow(self.Duration, flag=wx.GROW)
+        main_sizer.Add(self.Duration, flag=wx.GROW)
 
         # create browse button
         self.EditButton = wx.Button(self, label='...', size=wx.Size(30, -1))
         self.Bind(wx.EVT_BUTTON, self.OnEditButtonClick, self.EditButton)
-        main_sizer.AddWindow(self.EditButton, flag=wx.GROW)
+        main_sizer.Add(self.EditButton, flag=wx.GROW)
 
         self.Bind(wx.EVT_SIZE, self.OnSize)
 
@@ -98,19 +98,16 @@
         self.Duration.SetFocus()
 
 
-class DurationCellEditor(wx.grid.PyGridCellEditor):
+class DurationCellEditor(wx.grid.GridCellEditor):
     '''
     Grid cell editor that uses DurationCellControl to display an edit button.
     '''
     def __init__(self, table, colname):
-        wx.grid.PyGridCellEditor.__init__(self)
+        wx.grid.GridCellEditor.__init__(self)
 
         self.Table = table
         self.Colname = colname
 
-    def __del__(self):
-        self.CellControl = None
-
     def Create(self, parent, id, evt_handler):
         self.CellControl = DurationCellControl(parent)
         self.SetControl(self.CellControl)
--- a/controls/EnhancedStatusBar.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/EnhancedStatusBar.py	Wed Oct 04 16:24:00 2023 +0200
@@ -55,8 +55,8 @@
 
 """
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import wx
 
 # Horizontal Alignment Constants
@@ -85,12 +85,12 @@
 
 class EnhancedStatusBar(wx.StatusBar):
 
-    def __init__(self, parent, id=wx.ID_ANY, style=wx.ST_SIZEGRIP,
+    def __init__(self, parent, id=wx.ID_ANY, style=wx.STB_SIZEGRIP,
                  name="EnhancedStatusBar"):
         """Default Class Constructor.
 
         EnhancedStatusBar.__init__(self, parent, id=wx.ID_ANY,
-                                   style=wx.ST_SIZEGRIP,
+                                   style=wx.STB_SIZEGRIP,
                                    name="EnhancedStatusBar")
         """
 
@@ -100,7 +100,7 @@
         self._curPos = 0
         self._parent = parent
 
-        wx.EVT_SIZE(self, self.OnSize)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
         wx.CallAfter(self.OnSize, None)
 
     def OnSize(self, event):
@@ -109,7 +109,7 @@
         Actually, All The Calculations Linked To HorizontalAlignment And
         VerticalAlignment Are Done In This Function."""
 
-        for pos, item in self._items.items():
+        for pos, item in list(self._items.items()):
             widget, horizontalalignment, verticalalignment = item.widget, item.horizontalalignment, item.verticalalignment
 
             rect = self.GetFieldRect(pos)
--- a/controls/FolderTree.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/FolderTree.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,20 +23,20 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+from functools import cmp_to_key
+from operator import eq
 import os
 
 import wx
-from six.moves import xrange
 
 from util.BitmapLibrary import GetBitmap
 
-DRIVE, FOLDER, FILE = range(3)
+DRIVE, FOLDER, FILE = list(range(3))
 
 
 def sort_folder(x, y):
     if x[1] == y[1]:
-        return cmp(x[0], y[0])
+        return eq(x[0], y[0])
     elif x[1] != FILE:
         return -1
     else:
@@ -74,12 +74,12 @@
         self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnTreeItemCollapsed, self.Tree)
         self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeBeginLabelEdit, self.Tree)
         self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeEndLabelEdit, self.Tree)
-        main_sizer.AddWindow(self.Tree, 1, flag=wx.GROW)
+        main_sizer.Add(self.Tree, 1, flag=wx.GROW)
 
         if filter is not None:
             self.Filter = wx.ComboBox(self, style=wx.CB_READONLY)
             self.Bind(wx.EVT_COMBOBOX, self.OnFilterChanged, self.Filter)
-            main_sizer.AddWindow(self.Filter, flag=wx.GROW)
+            main_sizer.Add(self.Filter, flag=wx.GROW)
         else:
             self.Filter = None
 
@@ -99,7 +99,7 @@
         self.Filters = {}
         if self.Filter is not None:
             filter_parts = filter.split("|")
-            for idx in xrange(0, len(filter_parts), 2):
+            for idx in range(0, len(filter_parts), 2):
                 if filter_parts[idx + 1] == "*.*":
                     self.Filters[filter_parts[idx]] = ""
                 else:
@@ -115,7 +115,7 @@
     def _GetFolderChildren(self, folderpath, recursive=True):
         items = []
         if wx.Platform == '__WXMSW__' and folderpath == "/":
-            for c in xrange(ord('a'), ord('z')):
+            for c in range(ord('a'), ord('z')):
                 drive = os.path.join("%s:\\" % chr(c))
                 if os.path.exists(drive):
                     items.append((drive, DRIVE, self._GetFolderChildren(drive, False)))
@@ -137,7 +137,7 @@
                           os.path.splitext(filename)[1] == self.CurrentFilter):
                         items.append((filename, FILE, None))
         if recursive:
-            items.sort(sort_folder)
+            items.sort(key=cmp_to_key(sort_folder))
         return items
 
     def SetFilter(self, filter):
@@ -160,7 +160,7 @@
                 if wx.Platform != '__WXMSW__':
                     item, item_cookie = self.Tree.GetNextChild(root, item_cookie)
             elif self.Tree.GetItemText(item) != filename:
-                item = self.Tree.InsertItemBefore(root, idx, filename, self.TreeImageDict[item_type])
+                item = self.Tree.InsertItem(root, idx, filename, self.TreeImageDict[item_type])
             filepath = os.path.join(folderpath, filename)
             if item_type != FILE:
                 if self.Tree.IsExpanded(item):
--- a/controls/IDBrowser.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/IDBrowser.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,8 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
+from operator import eq
 import wx
 import wx.dataview as dv
 import PSKManagement as PSK
@@ -43,9 +44,9 @@
         row1 = self.GetRow(item1)
         row2 = self.GetRow(item2)
         if col == 0:
-            return cmp(int(self.data[row1][col]), int(self.data[row2][col]))
+            return eq(int(self.data[row1][col]), int(self.data[row2][col]))
         else:
-            return cmp(self.data[row1][col], self.data[row2][col])
+            return eq(self.data[row1][col], self.data[row2][col])
 
     def DeleteRows(self, rows):
         rows = list(rows)
--- a/controls/LibraryPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/LibraryPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from functools import reduce
 import wx
 
@@ -31,7 +31,7 @@
 # -------------------------------------------------------------------------------
 
 
-[CATEGORY, BLOCK] = range(2)
+[CATEGORY, BLOCK] = list(range(2))
 
 
 # -------------------------------------------------------------------------------
@@ -77,12 +77,12 @@
             search_textctrl = self.SearchCtrl.GetChildren()[0]
             search_textctrl.Bind(wx.EVT_CHAR, self.OnKeyDown)
 
-        main_sizer.AddWindow(self.SearchCtrl, flag=wx.GROW)
+        main_sizer.Add(self.SearchCtrl, flag=wx.GROW)
 
         # Add Splitter window for tree and block comment to main sizer
         splitter_window = wx.SplitterWindow(self)
         splitter_window.SetSashGravity(1.0)
-        main_sizer.AddWindow(splitter_window, flag=wx.GROW)
+        main_sizer.Add(splitter_window, flag=wx.GROW)
 
         # Add TreeCtrl for functions and function blocks library in splitter
         # window
@@ -114,13 +114,6 @@
         # Variable storing functions and function blocks library to display
         self.BlockList = None
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to project controller
-        self.Controller = None
-
     def SetController(self, controller):
         """
         Set reference to project controller
@@ -173,7 +166,7 @@
 
             # Get current selected item for selected it when values refreshed
             selected_item = self.Tree.GetSelection()
-            selected_pydata = (self.Tree.GetPyData(selected_item)
+            selected_pydata = (self.Tree.GetItemData(selected_item)
                                if (selected_item.IsOk() and
                                    selected_item != self.Tree.GetRootItem())
                                else None)
@@ -216,7 +209,7 @@
 
                 # Set data associated to tree item (only save that item is a
                 # category)
-                self.Tree.SetPyData(category_item, {"type": CATEGORY})
+                self.Tree.SetItemData(category_item, {"type": CATEGORY})
 
                 # Iterate over functions and function blocks defined in library
                 # category add a tree item to category tree item for each of
@@ -253,7 +246,7 @@
                                        if blocktype["extensible"] else None),
                         "comment":    _(comment) + blocktype.get("usage", "")
                     }
-                    self.Tree.SetPyData(blocktype_item, block_data)
+                    self.Tree.SetItemData(blocktype_item, block_data)
 
                     # Select block tree item in tree if it corresponds to
                     # previously selected one
@@ -298,7 +291,7 @@
         """
         # Get selected item associated data in tree
         selected_item = self.Tree.GetSelection()
-        selected_pydata = (self.Tree.GetPyData(selected_item)
+        selected_pydata = (self.Tree.GetItemData(selected_item)
                            if (selected_item.IsOk() and
                                selected_item != self.Tree.GetRootItem())
                            else None)
@@ -336,7 +329,7 @@
             return None
 
         # Get data associated to item to test
-        item_pydata = self.Tree.GetPyData(item)
+        item_pydata = self.Tree.GetItemData(item)
         if item_pydata is not None and item_pydata["type"] == BLOCK:
             # Only test item corresponding to block
 
@@ -346,12 +339,10 @@
             if inputs is not None and type_inputs is not None:
                 same_inputs = reduce(
                     lambda x, y: x and y,
-                    map(
-                        lambda x: x[0] == x[1] or x[0] == 'ANY' or x[1] == 'ANY',
-                        zip(type_inputs,
+                    [x[0] == x[1] or x[0] == 'ANY' or x[1] == 'ANY' for x in zip(type_inputs,
                             (inputs[:type_extension]
                              if type_extension is not None
-                             else inputs))),
+                             else inputs))],
                     True)
             else:
                 same_inputs = True
@@ -402,7 +393,7 @@
         while item.IsOk():
 
             # Get item data to get item type
-            item_pydata = self.Tree.GetPyData(item)
+            item_pydata = self.Tree.GetItemData(item)
 
             # Item is a block category
             if (item == root) or item_pydata["type"] == CATEGORY:
@@ -467,7 +458,7 @@
         @param event: wx.TreeEvent
         """
         # Update TextCtrl value with block selected usage
-        item_pydata = self.Tree.GetPyData(event.GetItem())
+        item_pydata = self.Tree.GetItemData(event.GetItem())
         self.Comment.SetValue(
             item_pydata["comment"]
             if item_pydata is not None and item_pydata["type"] == BLOCK
@@ -485,7 +476,7 @@
         @param event: wx.TreeEvent
         """
         selected_item = event.GetItem()
-        item_pydata = self.Tree.GetPyData(selected_item)
+        item_pydata = self.Tree.GetItemData(selected_item)
 
         # Item dragged is a block
         if item_pydata is not None and item_pydata["type"] == BLOCK:
--- a/controls/LocationCellEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/LocationCellEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,20 +23,20 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from dialogs.BrowseLocationsDialog import BrowseLocationsDialog
 
 
-class LocationCellControl(wx.PyControl):
+class LocationCellControl(wx.Control):
 
     '''
     Custom cell editor control with a text box and a button that launches
     the BrowseLocationsDialog.
     '''
     def __init__(self, parent):
-        wx.PyControl.__init__(self, parent)
+        wx.Control.__init__(self, parent)
 
         main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
         main_sizer.AddGrowableCol(0)
@@ -46,12 +46,12 @@
         self.Location = wx.TextCtrl(self, size=wx.Size(0, -1),
                                     style=wx.TE_PROCESS_ENTER)
         self.Location.Bind(wx.EVT_KEY_DOWN, self.OnLocationChar)
-        main_sizer.AddWindow(self.Location, flag=wx.GROW)
+        main_sizer.Add(self.Location, flag=wx.GROW)
 
         # create browse button
         self.BrowseButton = wx.Button(self, label='...', size=wx.Size(30, -1))
         self.BrowseButton.Bind(wx.EVT_BUTTON, self.OnBrowseButtonClick)
-        main_sizer.AddWindow(self.BrowseButton, flag=wx.GROW)
+        main_sizer.Add(self.BrowseButton, flag=wx.GROW)
 
         self.Bind(wx.EVT_SIZE, self.OnSize)
 
@@ -62,9 +62,6 @@
         self.Default = False
         self.VariableName = None
 
-    def __del__(self):
-        self.Controller = None
-
     def SetController(self, controller):
         self.Controller = controller
 
@@ -150,20 +147,16 @@
         self.Location.SetFocus()
 
 
-class LocationCellEditor(wx.grid.PyGridCellEditor):
+class LocationCellEditor(wx.grid.GridCellEditor):
     '''
     Grid cell editor that uses LocationCellControl to display a browse button.
     '''
     def __init__(self, table, controller):
-        wx.grid.PyGridCellEditor.__init__(self)
+        wx.grid.GridCellEditor.__init__(self)
 
         self.Table = table
         self.Controller = controller
 
-    def __del__(self):
-        self.CellControl = None
-        self.Controller = None
-
     def Create(self, parent, id, evt_handler):
         self.CellControl = LocationCellControl(parent)
         self.SetControl(self.CellControl)
@@ -178,7 +171,7 @@
             self.CellControl.SetVarType(self.Table.GetValueByName(row, 'Type'))
         self.CellControl.SetFocus()
 
-    def EndEditInternal(self, row, col, grid, old_loc):
+    def EndEdit(self, row, col, grid, old_loc):
         loc = self.CellControl.GetValue()
         changed = loc != old_loc
         if changed:
@@ -201,18 +194,13 @@
         self.CellControl.Disable()
         return changed
 
-    if wx.VERSION >= (3, 0, 0):
-        def EndEdit(self, row, col, grid, oldval):
-            return self.EndEditInternal(row, col, grid, oldval)
-    else:
-        def EndEdit(self, row, col, grid):
-            old_loc = self.Table.GetValueByName(row, 'Location')
-            return self.EndEditInternal(row, col, grid, old_loc)
+    def ApplyEdit(self, row, col, grid):
+        pass
 
     def SetSize(self, rect):
-        self.CellControl.SetDimensions(rect.x + 1, rect.y,
-                                       rect.width, rect.height,
-                                       wx.SIZE_ALLOW_MINUS_ONE)
+        self.CellControl.SetSize(rect.x + 1, rect.y,
+                                 rect.width, rect.height,
+                                 wx.SIZE_ALLOW_MINUS_ONE)
 
     def Clone(self):
         return LocationCellEditor(self.Table, self.Controller)
--- a/controls/LogViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/LogViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,15 +23,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 from datetime import datetime
 from time import time as gettime
 from weakref import proxy
 
 import numpy
 import wx
-from six.moves import xrange
 
 from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
 from editors.DebugViewer import DebugViewer, REFRESH_PERIOD
@@ -42,7 +39,8 @@
 THUMB_SIZE_RATIO = 1. / 8.
 
 
-def ArrowPoints(direction, width, height, xoffset, yoffset):
+def ArrowPoints(*args):
+    direction, width, height, xoffset, yoffset = map(lambda x:x if type(x)==int else int(x), args)
     if direction == wx.TOP:
         return [wx.Point(xoffset + 1, yoffset + height - 2),
                 wx.Point(xoffset + width // 2, yoffset + 1),
@@ -99,8 +97,8 @@
         width, height = self.GetClientSize()
         range_rect = self.GetRangeRect()
         thumb_rect = self.GetThumbRect()
-        if range_rect.InsideXY(posx, posy):
-            if thumb_rect.InsideXY(posx, posy):
+        if range_rect.Contains(posx, posy):
+            if thumb_rect.Contains(posx, posy):
                 self.ThumbScrollingStartPos = wx.Point(posx, posy)
             elif posy < thumb_rect.y:
                 self.Parent.ScrollToLast()
@@ -139,13 +137,12 @@
     def OnPaint(self, event):
         dc = wx.BufferedPaintDC(self)
         dc.Clear()
-        dc.BeginDrawing()
 
         gc = wx.GCDC(dc)
 
         width, height = self.GetClientSize()
 
-        gc.SetPen(wx.Pen(wx.NamedColour("GREY"), 3))
+        gc.SetPen(wx.Pen(wx.Colour("GREY"), 3))
         gc.SetBrush(wx.GREY_BRUSH)
 
         gc.DrawLines(ArrowPoints(wx.TOP, width * 0.75, width * 0.5, 2, (width + height) // 4 - 3))
@@ -162,7 +159,7 @@
         if self.Parent.IsMessagePanelBottom():
             exclusion_rect.height = height - width - exclusion_rect.y
         if exclusion_rect != thumb_rect:
-            colour = wx.NamedColour("LIGHT GREY")
+            colour = wx.Colour("LIGHT GREY")
             gc.SetPen(wx.Pen(colour))
             gc.SetBrush(wx.Brush(colour))
 
@@ -179,7 +176,6 @@
         gc.DrawRectangle(thumb_rect.x, thumb_rect.y,
                          thumb_rect.width, thumb_rect.height)
 
-        dc.EndDrawing()
         event.Skip()
 
 
@@ -195,9 +191,6 @@
         self.Shown = True
         self.Callback = callback
 
-    def __del__(self):
-        self.callback = None
-
     def GetSize(self):
         return self.Size
 
@@ -207,7 +200,7 @@
     def HitTest(self, x, y):
         rect = wx.Rect(self.Position.x, self.Position.y,
                        self.Size.width, self.Size.height)
-        if rect.InsideXY(x, y):
+        if rect.Contains(x, y):
             return True
         return False
 
@@ -217,7 +210,7 @@
 
     def Draw(self, dc):
         dc.SetPen(wx.TRANSPARENT_PEN)
-        dc.SetBrush(wx.Brush(wx.NamedColour("LIGHT GREY")))
+        dc.SetBrush(wx.Brush(wx.Colour("LIGHT GREY")))
 
         dc.DrawRectangle(self.Position.x, self.Position.y,
                          self.Size.width, self.Size.height)
@@ -244,10 +237,10 @@
         self.Message = msg
         self.DrawDate = True
 
-    def __cmp__(self, other):
+    def __lt__(self, other):
         if self.Date == other.Date:
-            return cmp(self.Seconds, other.Seconds)
-        return cmp(self.Date, other.Date)
+            return self.Seconds < other.Seconds
+        return self.Date < other.Date
 
     def GetFullText(self):
         date = self.Date.replace(second=int(self.Seconds))
@@ -303,7 +296,7 @@
         main_sizer.AddGrowableRow(1)
 
         filter_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(filter_sizer, border=5, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
+        main_sizer.Add(filter_sizer, border=5, flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
         self.MessageFilter = wx.ComboBox(self, style=wx.CB_READONLY)
         self.MessageFilter.Append(_("All"))
@@ -312,7 +305,7 @@
         for level in levels:
             self.MessageFilter.Append(_(level))
         self.Bind(wx.EVT_COMBOBOX, self.OnMessageFilterChanged, self.MessageFilter)
-        filter_sizer.AddWindow(self.MessageFilter, 1, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
+        filter_sizer.Add(self.MessageFilter, 1, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
 
         self.SearchMessage = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
         self.SearchMessage.ShowSearchButton(True)
@@ -322,18 +315,18 @@
                   self.OnSearchMessageSearchButtonClick, self.SearchMessage)
         self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN,
                   self.OnSearchMessageCancelButtonClick, self.SearchMessage)
-        filter_sizer.AddWindow(self.SearchMessage, 3, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
+        filter_sizer.Add(self.SearchMessage, 3, border=5, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
 
         self.CleanButton = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap("Clean"),
                                                           size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.CleanButton.SetToolTipString(_("Clean log messages"))
+        self.CleanButton.SetToolTip(_("Clean log messages"))
         self.Bind(wx.EVT_BUTTON, self.OnCleanButton, self.CleanButton)
-        filter_sizer.AddWindow(self.CleanButton)
+        filter_sizer.Add(self.CleanButton)
 
         message_panel_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=0)
         message_panel_sizer.AddGrowableCol(0)
         message_panel_sizer.AddGrowableRow(0)
-        main_sizer.AddSizer(message_panel_sizer, border=5, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
+        main_sizer.Add(message_panel_sizer, border=5, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         self.MessagePanel = wx.Panel(self)
         if wx.Platform == '__WXMSW__':
@@ -349,10 +342,10 @@
         self.MessagePanel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnMessagePanelEraseBackground)
         self.MessagePanel.Bind(wx.EVT_PAINT, self.OnMessagePanelPaint)
         self.MessagePanel.Bind(wx.EVT_SIZE, self.OnMessagePanelResize)
-        message_panel_sizer.AddWindow(self.MessagePanel, flag=wx.GROW)
+        message_panel_sizer.Add(self.MessagePanel, flag=wx.GROW)
 
         self.MessageScrollBar = LogScrollBar(self, wx.Size(16, -1))
-        message_panel_sizer.AddWindow(self.MessageScrollBar, flag=wx.GROW)
+        message_panel_sizer.Add(self.MessageScrollBar, flag=wx.GROW)
 
         self.SetSizer(main_sizer)
 
@@ -372,7 +365,7 @@
         self.ParentWindow = window
 
         self.LevelIcons = [GetBitmap("LOG_" + level) for level in LogLevels]
-        self.LevelFilters = [range(i) for i in xrange(4, 0, -1)]
+        self.LevelFilters = [list(range(i)) for i in range(4, 0, -1)]
         self.CurrentFilter = self.LevelFilters[0]
         self.CurrentSearchValue = ""
 
@@ -386,9 +379,6 @@
         self.MessageToolTipTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnMessageToolTipTimer, self.MessageToolTipTimer)
 
-    def __del__(self):
-        self.ScrollTimer.Stop()
-
     def ResetLogMessages(self):
         self.ResetLogCounters()
         self.OldestMessages = []
@@ -417,14 +407,14 @@
 
     def SetLogCounters(self, log_count):
         new_messages = []
-        for level, count, prev in zip(xrange(LogLevelsCount), log_count, self.previous_log_count):
+        for level, count, prev in zip(range(LogLevelsCount), log_count, self.previous_log_count):
             if count is not None and prev != count:
                 if prev is None:
                     dump_end = max(-1, count - 10)
                     oldest_message = (-1, None)
                 else:
                     dump_end = prev - 1
-                for msgidx in xrange(count-1, dump_end, -1):
+                for msgidx in range(count-1, dump_end, -1):
                     new_message = self.GetLogMessageFromSource(msgidx, level)
                     if new_message is None:
                         if prev is None:
@@ -534,10 +524,9 @@
 
     def RefreshView(self):
         width, height = self.MessagePanel.GetClientSize()
-        bitmap = wx.EmptyBitmap(width, height)
+        bitmap = wx.Bitmap(width, height)
         dc = wx.BufferedDC(wx.ClientDC(self.MessagePanel), bitmap)
         dc.Clear()
-        dc.BeginDrawing()
 
         if self.CurrentMessage is not None:
 
@@ -559,13 +548,11 @@
                     draw_date = message.Date != previous_message.Date
                 message = previous_message
 
-        dc.EndDrawing()
-
         self.MessageScrollBar.RefreshThumbPosition()
 
     def IsPLCLogEmpty(self):
         empty = True
-        for _level, prev in zip(xrange(LogLevelsCount), self.previous_log_count):
+        for _level, prev in zip(range(LogLevelsCount), self.previous_log_count):
             if prev is not None:
                 empty = False
                 break
@@ -711,7 +698,7 @@
         if message is not None:
             menu = wx.Menu(title='')
 
-            menu_entry = menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=_("Copy"))
+            menu_entry = menu.Append(wx.ID_ANY, _("Copy"))
             self.Bind(wx.EVT_MENU, self.GetCopyMessageToClipboardFunction(message), menu_entry)
 
             self.MessagePanel.PopupMenu(menu)
--- a/controls/PouInstanceVariablesPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/PouInstanceVariablesPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#.!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 # This file is part of Beremiz, a Integrated Development Environment for
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 from collections import namedtuple
 
 import wx
@@ -93,7 +93,7 @@
             rect = wx.Rect(images_bbx.x + 4, images_bbx.y + 4,
                            r_image_w, r_image_h)
             for r_image in rightimages:
-                if rect.Inside(point):
+                if rect.Contains(point):
                     return r_image
                 rect.x += r_image_w + 4
 
@@ -132,7 +132,7 @@
 
         self.ParentButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("top"), size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.ParentButton.SetToolTipString(_("Parent instance"))
+        self.ParentButton.SetToolTip(_("Parent instance"))
         self.Bind(wx.EVT_BUTTON, self.OnParentButtonClick,
                   self.ParentButton)
 
@@ -142,7 +142,7 @@
 
         self.DebugButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("debug_instance"), size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.DebugButton.SetToolTipString(_("Debug instance"))
+        self.DebugButton.SetToolTip(_("Debug instance"))
         self.Bind(wx.EVT_BUTTON, self.OnDebugButtonClick,
                   self.DebugButton)
 
@@ -188,16 +188,16 @@
 
 
         buttons_sizer = wx.FlexGridSizer(cols=3, hgap=0, rows=1, vgap=0)
-        buttons_sizer.AddWindow(self.ParentButton)
-        buttons_sizer.AddWindow(self.InstanceChoice, flag=wx.GROW)
-        buttons_sizer.AddWindow(self.DebugButton)
+        buttons_sizer.Add(self.ParentButton)
+        buttons_sizer.Add(self.InstanceChoice, flag=wx.GROW)
+        buttons_sizer.Add(self.DebugButton)
         buttons_sizer.AddGrowableCol(1)
         buttons_sizer.AddGrowableRow(0)
 
         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=0)
-        main_sizer.AddSizer(buttons_sizer, flag=wx.GROW)
-        main_sizer.AddWindow(self.VariablesList, flag=wx.GROW)
-        main_sizer.AddWindow(self.FilterCtrl, flag=wx.GROW)
+        main_sizer.Add(buttons_sizer, flag=wx.GROW)
+        main_sizer.Add(self.VariablesList, flag=wx.GROW)
+        main_sizer.Add(self.FilterCtrl, flag=wx.GROW)
         main_sizer.AddGrowableCol(0)
         main_sizer.AddGrowableRow(1)
 
@@ -217,10 +217,6 @@
         self.FilterCaseSensitive = False
         self.FilterWholeWord = False
 
-
-    def __del__(self):
-        self.Controller = None
-
     def SetTreeImageList(self, tree_image_list):
         self.VariablesList.SetImageList(tree_image_list)
 
@@ -444,7 +440,7 @@
     def OnVariablesListItemActivated(self, event):
         selected_item = event.GetItem()
         if selected_item is not None and selected_item.IsOk():
-            item_infos = self.VariablesList.GetPyData(selected_item)
+            item_infos = self.VariablesList.GetItemData(selected_item)
             if item_infos is not None:
 
                 item_button = self.VariablesList.IsOverItemRightImage(
@@ -464,7 +460,7 @@
                         else:
                             tagname = None
                     else:
-                        parent_infos = self.VariablesList.GetPyData(selected_item.GetParent())
+                        parent_infos = self.VariablesList.GetItemData(selected_item.GetParent())
                         if item_infos.var_class == ITEM_ACTION:
                             tagname = ComputePouActionName(parent_infos.type, item_infos.name)
                         elif item_infos.var_class == ITEM_TRANSITION:
@@ -487,7 +483,7 @@
             instance_path = self.InstanceChoice.GetStringSelection()
             item, flags = self.VariablesList.HitTest(event.GetPosition())
             if item is not None:
-                item_infos = self.VariablesList.GetPyData(item)
+                item_infos = self.VariablesList.GetItemData(item)
                 if item_infos is not None:
 
                     item_button = self.VariablesList.IsOverItemRightImage(
--- a/controls/ProjectPropertiesPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/ProjectPropertiesPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 from wx.lib.scrolledpanel import ScrolledPanel
 
@@ -38,7 +38,7 @@
 [
     TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
     POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-] = range(10)
+] = list(range(10))
 
 
 # -------------------------------------------------------------------------------
@@ -57,7 +57,7 @@
                 border |= wx.BOTTOM
 
             st = wx.StaticText(parent, label=label)
-            sizer.AddWindow(st, border=10,
+            sizer.Add(st, border=10,
                             flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
 
             tc = wx.TextCtrl(parent, style=wx.TE_PROCESS_ENTER)
@@ -65,7 +65,7 @@
             callback = self.GetTextCtrlChangedFunction(tc, name)
             self.Bind(wx.EVT_TEXT_ENTER, callback, tc)
             tc.Bind(wx.EVT_KILL_FOCUS, callback)
-            sizer.AddWindow(tc, border=10,
+            sizer.Add(tc, border=10,
                             flag=wx.GROW | border | wx.RIGHT)
 
     def __init__(self, parent, controller=None, window=None, enable_required=True, scrolling=True):
@@ -125,19 +125,19 @@
 
         pageSize_st = wx.StaticText(self.GraphicsPanel,
                                     label=_('Page Size (optional):'))
-        graphicpanel_sizer.AddWindow(
+        graphicpanel_sizer.Add(
             pageSize_st, border=10,
             flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pageSize_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
         pageSize_sizer.AddGrowableCol(1)
-        graphicpanel_sizer.AddSizer(pageSize_sizer, border=10,
+        graphicpanel_sizer.Add(pageSize_sizer, border=10,
                                     flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         for name, label in [('PageWidth', _('Width:')),
                             ('PageHeight', _('Height:'))]:
             st = wx.StaticText(self.GraphicsPanel, label=label)
-            pageSize_sizer.AddWindow(st, border=12,
+            pageSize_sizer.Add(st, border=12,
                                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
 
             sp = wx.SpinCtrl(self.GraphicsPanel,
@@ -146,15 +146,15 @@
             callback = self.GetPageSizeChangedFunction(sp, name)
             self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
             sp.Bind(wx.EVT_KILL_FOCUS, callback)
-            pageSize_sizer.AddWindow(sp, flag=wx.GROW)
+            pageSize_sizer.Add(sp, flag=wx.GROW)
 
         scaling_st = wx.StaticText(self.GraphicsPanel,
                                    label=_('Grid Resolution:'))
-        graphicpanel_sizer.AddWindow(scaling_st, border=10,
+        graphicpanel_sizer.Add(scaling_st, border=10,
                                      flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         scaling_nb = wx.Notebook(self.GraphicsPanel)
-        graphicpanel_sizer.AddWindow(scaling_nb, border=10,
+        graphicpanel_sizer.Add(scaling_nb, border=10,
                                      flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.Scalings = {}
@@ -173,7 +173,7 @@
                     border = wx.BOTTOM
 
                 st = wx.StaticText(scaling_panel, label=label)
-                scalingpanel_sizer.AddWindow(
+                scalingpanel_sizer.Add(
                     st, border=10,
                     flag=wx.ALIGN_CENTER_VERTICAL | border | wx.LEFT)
 
@@ -183,7 +183,7 @@
                 callback = self.GetScalingChangedFunction(sp, language, name)
                 self.Bind(wx.EVT_TEXT_ENTER, callback, sp)
                 sp.Bind(wx.EVT_KILL_FOCUS, callback)
-                scalingpanel_sizer.AddWindow(sp, border=10,
+                scalingpanel_sizer.Add(sp, border=10,
                                              flag=wx.GROW | border | wx.RIGHT)
 
             self.Scalings[language] = scaling_controls
@@ -206,18 +206,18 @@
 
         language_label = wx.StaticText(self.MiscellaneousPanel,
                                        label=_('Language (optional):'))
-        miscellaneouspanel_sizer.AddWindow(language_label, border=10,
+        miscellaneouspanel_sizer.Add(language_label, border=10,
                                            flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.LEFT)
 
         self.Language = wx.ComboBox(self.MiscellaneousPanel,
                                     style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnLanguageChanged, self.Language)
-        miscellaneouspanel_sizer.AddWindow(self.Language, border=10,
+        miscellaneouspanel_sizer.Add(self.Language, border=10,
                                            flag=wx.GROW | wx.TOP | wx.RIGHT)
 
         description_label = wx.StaticText(
             self.MiscellaneousPanel, label=_('Content Description (optional):'))
-        miscellaneouspanel_sizer.AddWindow(description_label, border=10,
+        miscellaneouspanel_sizer.Add(description_label, border=10,
                                            flag=wx.BOTTOM | wx.LEFT)
 
         self.ContentDescription = wx.TextCtrl(
@@ -227,7 +227,7 @@
                   self.ContentDescription)
         self.ContentDescription.Bind(wx.EVT_KILL_FOCUS,
                                      self.OnContentDescriptionChanged)
-        miscellaneouspanel_sizer.AddWindow(self.ContentDescription, border=10,
+        miscellaneouspanel_sizer.Add(self.ContentDescription, border=10,
                                            flag=wx.GROW | wx.BOTTOM | wx.RIGHT)
 
         self.AddPage(self.MiscellaneousPanel, _("Miscellaneous"))
@@ -246,19 +246,19 @@
 
     def SetValues(self, values):
         self.Values = values
-        for item, value in values.items():
+        for item, value in list(values.items()):
             if item == "language":
                 self.Language.SetStringSelection(value)
             elif item == "contentDescription":
                 self.ContentDescription.SetValue(value)
             elif item == "pageSize":
-                self.PageWidth.SetValue(value[0])
-                self.PageHeight.SetValue(value[1])
+                self.PageWidth.SetValue(int(value[0]))
+                self.PageHeight.SetValue(int(value[1]))
             elif item == "scaling":
-                for language, (x, y) in value.items():
+                for language, (x, y) in list(value.items()):
                     if language in self.Scalings:
-                        self.Scalings[language][0].SetValue(x)
-                        self.Scalings[language][1].SetValue(y)
+                        self.Scalings[language][0].SetValue(int(x))
+                        self.Scalings[language][1].SetValue(int(y))
             else:
                 tc = getattr(self, item, None)
                 if tc is not None:
--- a/controls/SearchResultPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/SearchResultPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from functools import reduce
 
 import wx
@@ -52,16 +52,16 @@
 class SearchResultPanel(wx.Panel):
 
     def _init_coll_MainSizer_Items(self, parent):
-        parent.AddSizer(self.HeaderSizer, 0, border=0, flag=wx.GROW)
-        parent.AddWindow(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
+        parent.Add(self.HeaderSizer, 0, border=0, flag=wx.GROW)
+        parent.Add(self.SearchResultsTree, 1, border=0, flag=wx.GROW)
 
     def _init_coll_MainSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
         parent.AddGrowableRow(1)
 
     def _init_coll_HeaderSizer_Items(self, parent):
-        parent.AddWindow(self.HeaderLabel, 1, border=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
-        parent.AddWindow(self.ResetButton, 0, border=0, flag=0)
+        parent.Add(self.HeaderLabel, 1, border=5, flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
+        parent.Add(self.ResetButton, 0, border=0, flag=0)
 
     def _init_coll_HeaderSizer_Growables(self, parent):
         parent.AddGrowableCol(0)
@@ -90,7 +90,7 @@
         self.ResetButton = wx.lib.buttons.GenBitmapButton(
             self, bitmap=GetBitmap("reset"),
             size=wx.Size(28, 28), style=wx.NO_BORDER)
-        self.ResetButton.SetToolTipString(_("Reset search result"))
+        self.ResetButton.SetToolTip(_("Reset search result"))
         self.Bind(wx.EVT_BUTTON, self.OnResetButton, self.ResetButton)
 
         self._init_sizers()
@@ -289,7 +289,7 @@
             start, end = infos["data"][1:3]
             text_lines = infos["text"].splitlines()
             start_idx = start[1]
-            end_idx = reduce(lambda x, y: x + y, map(lambda x: len(x) + 1, text_lines[:end[0] - start[0]]), end[1] + 1)
+            end_idx = reduce(lambda x, y: x + y, [len(x) + 1 for x in text_lines[:end[0] - start[0]]], end[1] + 1)
             style = wx.TextAttr(wx.BLACK, wx.Colour(206, 204, 247))
         elif infos["type"] is not None and infos["matches"] > 1:
             text = _("(%d matches)") % infos["matches"]
@@ -320,7 +320,7 @@
             item, root_cookie = self.SearchResultsTree.GetNextChild(root, root_cookie)
 
     def ShowSearchResults(self, item):
-        data = self.SearchResultsTree.GetPyData(item)
+        data = self.SearchResultsTree.GetItemData(item)
         if isinstance(data, tuple):
             search_results = [data]
         else:
--- a/controls/TextCtrlAutoComplete.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/TextCtrlAutoComplete.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from six.moves import cPickle
+import pickle
 import wx
 
 MAX_ITEM_COUNT = 10
@@ -97,7 +96,7 @@
         parent_rect = wx.Rect(0, -parent_size[1], parent_size[0], parent_size[1])
         if selected != wx.NOT_FOUND:
             wx.CallAfter(self.Parent.SetValueFromSelected, self.ListBox.GetString(selected))
-        elif parent_rect.InsideXY(event.GetX(), event.GetY()):
+        elif parent_rect.Contains(event.GetX(), event.GetY()):
             result, x, y = self.Parent.HitTest(wx.Point(event.GetX(), event.GetY() + parent_size[1]))
             if result != wx.TE_HT_UNKNOWN:
                 self.Parent.SetInsertionPoint(self.Parent.XYToPosition(x, y))
@@ -198,10 +197,12 @@
     def OnControlChanged(self, event):
         res = self.GetValue()
         config = wx.ConfigBase.Get()
-        listentries = cPickle.loads(str(config.Read(self.element_path, cPickle.dumps([]))))
+        listentries = pickle.loads(config.Read(self.element_path,
+                                               pickle.dumps([], 0).decode()
+                                              ).encode())
         if res and res not in listentries:
             listentries = (listentries + [res])[-MAX_ITEM_COUNT:]
-            config.Write(self.element_path, cPickle.dumps(listentries))
+            config.Write(self.element_path, pickle.dumps(listentries, 0))
             config.Flush()
             self.SetChoices(listentries)
         self.DismissListBox()
--- a/controls/VariablePanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/VariablePanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,17 +23,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 import re
-from builtins import str as text
 
 import wx
 import wx.grid
 import wx.lib.buttons
-from six import string_types
-from six.moves import xrange
-
 
 from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS, DefaultType
 from plcopen.VariableInfoCollector import _VariableInfos
@@ -54,7 +48,7 @@
 [
     TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, PROJECTTREE,
     POUINSTANCEVARIABLESPANEL, LIBRARYTREE, SCALING, PAGETITLES
-] = range(10)
+] = list(range(10))
 
 
 def GetVariableTableColnames(location):
@@ -123,7 +117,7 @@
         self.OPTIONS_DICT = dict([(_(option), option)
                                   for option in GetOptions()])
         self.VARIABLE_CLASSES_DICT = dict([(_(_class), _class)
-                                           for _class in GetFilterChoiceTransfer().itervalues()])
+                                           for _class in GetFilterChoiceTransfer().values()])
 
     def GetValueByName(self, row, colname):
         if row < self.GetNumberRows():
@@ -144,7 +138,7 @@
             if colname == "Type" and isinstance(value, tuple):
                 if value[0] == "array":
                     return "ARRAY [%s] OF %s" % (",".join(map("..".join, value[2])), value[1])
-            if not isinstance(value, string_types):
+            if not isinstance(value, str):
                 value = str(value)
             if colname in ["Class", "Option"]:
                 return _(value)
@@ -200,8 +194,7 @@
                                              retain=self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"],
                                              non_retain=self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"])
                         if len(options) > 1:
-                            editor = wx.grid.GridCellChoiceEditor()
-                            editor.SetParameters(",".join(map(_, options)))
+                            editor = wx.grid.GridCellChoiceEditor(list(map(_, options)))
                         else:
                             grid.SetReadOnly(row, col, True)
                     elif col != 0 and self._GetRowEdit(row):
@@ -212,8 +205,7 @@
                         elif colname == "Initial Value":
                             if var_class not in ["External", "InOut"]:
                                 if self.Parent.Controler.IsEnumeratedType(var_type):
-                                    editor = wx.grid.GridCellChoiceEditor()
-                                    editor.SetParameters(",".join([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type)))
+                                    editor = wx.grid.GridCellChoiceEditor([""] + self.Parent.Controler.GetEnumeratedDataValues(var_type))
                                 else:
                                     editor = wx.grid.GridCellTextEditor()
                                 renderer = wx.grid.GridCellStringRenderer()
@@ -229,11 +221,10 @@
                             if len(self.Parent.ClassList) == 1:
                                 grid.SetReadOnly(row, col, True)
                             else:
-                                editor = wx.grid.GridCellChoiceEditor()
                                 excluded = []
                                 if self.Parent.IsFunctionBlockType(var_type):
                                     excluded.extend(["Local", "Temp"])
-                                editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
+                                editor = wx.grid.GridCellChoiceEditor([_(choice) for choice in self.Parent.ClassList if choice not in excluded])
                     elif colname != "Documentation":
                         grid.SetReadOnly(row, col, True)
 
@@ -325,7 +316,7 @@
                                         selected = None
                                     dialog.Destroy()
                                     if selected is None:
-                                        return
+                                        return False
                                     if selected == 0:
                                         location = "%I" + location
                                     elif selected == 1:
@@ -360,7 +351,7 @@
                 var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                 dlg.Destroy()
                 if var_name is None:
-                    return
+                    return False
                 elif var_name.upper() in [
                         name.upper() for name in
                         self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
@@ -388,7 +379,7 @@
                                 selected = None
                             dialog.Destroy()
                             if selected is None:
-                                return
+                                return False
                             if selected == 0:
                                 location = "%I" + location
                             elif selected == 1:
@@ -399,7 +390,7 @@
                             configs = self.ParentWindow.Controler.GetProjectConfigNames(
                                 self.ParentWindow.Debug)
                             if len(configs) == 0:
-                                return
+                                return False
                             if not var_name.upper() in [
                                     name.upper() for name in
                                     self.ParentWindow.Controler.GetConfigurationVariableNames(configs[0])]:
@@ -417,7 +408,7 @@
                             var_infos.Class = "Local"
                             var_infos.InitialValue = values[0]
                         else:
-                            return
+                            return False
                     else:
                         var_infos.Class = "External"
                     var_infos.Number = len(self.ParentWindow.Values)
@@ -429,6 +420,11 @@
 
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
+
+        return True
+
+        return True
 
     def ShowMessage(self, message):
         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK | wx.ICON_ERROR)
@@ -447,7 +443,7 @@
         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
 
         self.VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for
-                                           _class in GetFilterChoiceTransfer().iterkeys()])
+                                           _class in GetFilterChoiceTransfer().keys()])
 
         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
         self.MainSizer.AddGrowableCol(0)
@@ -456,32 +452,32 @@
         controls_sizer = wx.FlexGridSizer(cols=10, hgap=5, rows=1, vgap=5)
         controls_sizer.AddGrowableCol(5)
         controls_sizer.AddGrowableRow(0)
-        self.MainSizer.AddSizer(controls_sizer, border=5, flag=wx.GROW | wx.ALL)
+        self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW | wx.ALL)
 
         self.ReturnTypeLabel = wx.StaticText(self, label=_('Return Type:'))
-        controls_sizer.AddWindow(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.ReturnTypeLabel, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.ReturnType = wx.ComboBox(self,
                                       size=wx.Size(145, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, self.ReturnType)
-        controls_sizer.AddWindow(self.ReturnType)
+        controls_sizer.Add(self.ReturnType)
 
         self.DescriptionLabel = wx.StaticText(self, label=_('Description:'))
-        controls_sizer.AddWindow(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.DescriptionLabel, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.Description = wx.TextCtrl(self,
                                        size=wx.Size(250, -1), style=wx.TE_PROCESS_ENTER)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnDescriptionChanged, self.Description)
         self.Description.Bind(wx.EVT_KILL_FOCUS, self.OnDescriptionChanged)
-        controls_sizer.AddWindow(self.Description)
+        controls_sizer.Add(self.Description)
 
         class_filter_label = wx.StaticText(self, label=_('Class Filter:'))
-        controls_sizer.AddWindow(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(class_filter_label, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.ClassFilter = wx.ComboBox(self,
                                        size=wx.Size(145, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, self.ClassFilter)
-        controls_sizer.AddWindow(self.ClassFilter)
+        controls_sizer.Add(self.ClassFilter)
 
         for name, bitmap, help in [
                 ("AddButton", "add_element", _("Add variable")),
@@ -490,19 +486,19 @@
                 ("DownButton", "down", _("Move variable down"))]:
             button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            controls_sizer.AddWindow(button)
+            controls_sizer.Add(button)
 
         self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL | wx.HSCROLL)
         self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                 self.OnVariablesGridCellChange)
         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
                                 self.OnVariablesGridCellLeftClick)
         self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                 self.OnVariablesGridEditorShown)
-        self.MainSizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        self.MainSizer.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.SetSizer(self.MainSizer)
 
@@ -575,14 +571,13 @@
 
         self.PanelWidthMin = sum(self.ColSettings["size"])
 
-        self.ElementType = element_type
         self.BodyType = None
 
         for choice in self.FilterChoices:
             self.ClassFilter.Append(_(choice))
 
         reverse_transfer = {}
-        for filter, choice in self.FilterChoiceTransfer.items():
+        for filter, choice in list(self.FilterChoiceTransfer.items()):
             reverse_transfer[choice] = filter
         self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
         self.RefreshTypeList()
@@ -610,7 +605,7 @@
                     model = re.compile(r"%[IQM][XBWLD]?(.*\.|)")
                     prefix = model.match(old_location).group(0)
                     addr = int(re.split(model, old_location)[-1]) + 1
-                    row_content.Location = prefix + text(addr)
+                    row_content.Location = prefix + str(addr)
 
             if not row_content.Class:
                 row_content.Class = self.DefaultTypes.get(self.Filter, self.Filter)
@@ -690,9 +685,6 @@
             else:
                 self.VariablesGrid.SetColSize(col, self.ColSettings["size"][col])
 
-    def __del__(self):
-        self.RefreshHighlightsTimer.Stop()
-
     def SetTagName(self, tagname):
         self.TagName = tagname
         self.BodyType = self.Controler.GetEditedElementBodyType(self.TagName)
@@ -848,20 +840,20 @@
         # build a submenu containing standard IEC types
         base_menu = wx.Menu(title='')
         for base_type in self.Controler.GetBaseTypes():
-            item = base_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=base_type)
+            item = base_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=base_type)
             self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), item)
 
-        type_menu.AppendMenu(wx.ID_ANY, _("Base Types"), base_menu)
+        type_menu.Append(wx.ID_ANY, _("Base Types"), base_menu)
 
     def BuildUserTypesMenu(self, type_menu):
         # build a submenu containing user-defined types
         datatype_menu = wx.Menu(title='')
         datatypes = self.Controler.GetDataTypes(basetypes=False, confnodetypes=False)
         for datatype in datatypes:
-            item = datatype_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=datatype)
+            item = datatype_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
             self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), item)
 
-        type_menu.AppendMenu(wx.ID_ANY, _("User Data Types"), datatype_menu)
+        type_menu.Append(wx.ID_ANY, _("User Data Types"), datatype_menu)
 
     def BuildLibsTypesMenu(self, type_menu):
         for category in self.Controler.GetConfNodeDataTypes():
@@ -869,10 +861,10 @@
                 # build a submenu containing confnode types
                 confnode_datatype_menu = wx.Menu(title='')
                 for datatype in category["list"]:
-                    item = confnode_datatype_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=datatype)
+                    item = confnode_datatype_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=datatype)
                     self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), item)
 
-                type_menu.AppendMenu(wx.ID_ANY, category["name"], confnode_datatype_menu)
+                type_menu.Append(wx.ID_ANY, category["name"], confnode_datatype_menu)
 
     def BuildProjectTypesMenu(self, type_menu, classtype):
         # build a submenu containing function block types
@@ -883,13 +875,13 @@
             functionblock_menu = wx.Menu(title='')
             fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
             for functionblock_type in fbtypes:
-                item = functionblock_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=functionblock_type)
+                item = functionblock_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=functionblock_type)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), item)
 
-            type_menu.AppendMenu(wx.ID_ANY, _("Function Block Types"), functionblock_menu)
+            type_menu.Append(wx.ID_ANY, _("Function Block Types"), functionblock_menu)
 
     def BuildArrayTypesMenu(self, type_menu):
-        item = type_menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Array"))
+        item = type_menu.Append(wx.ID_ANY, helpString='', kind=wx.ITEM_NORMAL, item=_("Array"))
         self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, item)
 
     def OnVariablesGridEditorShown(self, event):
@@ -916,7 +908,7 @@
             corner_y = rect.y + self.VariablesGrid.GetColLabelSize()
 
             # pop up this new menu
-            self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
+            self.VariablesGrid.PopupMenu(type_menu, corner_x, corner_y)
             type_menu.Destroy()
             event.Veto()
             value = self.Values[row].Type
@@ -996,7 +988,7 @@
 
     def AddVariableHighlight(self, infos, highlight_type):
         if isinstance(infos[0], tuple):
-            for i in xrange(*infos[0]):
+            for i in range(*infos[0]):
                 self.Table.AddHighlight((i,) + infos[1:], highlight_type)
             cell_visible = infos[0][0]
         else:
@@ -1008,7 +1000,7 @@
 
     def RemoveVariableHighlight(self, infos, highlight_type):
         if isinstance(infos[0], tuple):
-            for i in xrange(*infos[0]):
+            for i in range(*infos[0]):
                 self.Table.RemoveHighlight((i,) + infos[1:], highlight_type)
         else:
             self.Table.RemoveHighlight(infos, highlight_type)
--- a/controls/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/controls/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 
 # Package initialization
 
-from __future__ import absolute_import
+
 
 from controls.CustomEditableListBox import CustomEditableListBox
 from controls.CustomGrid import CustomGrid
--- a/dialogs/AboutDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/AboutDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -31,9 +31,10 @@
 """
 
 
-from __future__ import absolute_import
+
 import os
 import wx
+import wx.adv
 from wx.lib.agw.hyperlink import HyperLinkCtrl
 
 
@@ -52,7 +53,8 @@
 
         image = None
         if self.info.Icon:
-            bitmap = wx.BitmapFromIcon(self.info.Icon)
+            bitmap = wx.Bitmap()
+            bitmap.CopyFromIcon(self.info.Icon)
             image = wx.StaticBitmap(self, bitmap=bitmap)
 
         name = wx.StaticText(self, label="%s %s" % (info.Name, info.Version))
@@ -120,8 +122,8 @@
         developer = wx.TextCtrl(notebook, style=wx.TE_READONLY | wx.TE_MULTILINE)
         translators = wx.TextCtrl(notebook, style=wx.TE_READONLY | wx.TE_MULTILINE)
 
-        developer.SetValue(u'\n'.join(info.Developers))
-        translators.SetValue(u'\n'.join(info.Translators))
+        developer.SetValue('\n'.join(info.Developers))
+        translators.SetValue('\n'.join(info.Translators))
 
         notebook.AddPage(developer, text=_("Written by"))
         notebook.AddPage(translators, text=_("Translated by"))
@@ -172,4 +174,4 @@
     if os.name == "nt":
         AboutDialog(parent, info)
     else:
-        wx.AboutBox(info)
+        wx.adv.AboutBox(info)
--- a/dialogs/ActionBlockDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/ActionBlockDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 import wx.grid
 import wx.lib.buttons
@@ -85,35 +85,29 @@
                 readonly = False
                 colname = self.GetColLabelValue(col, False)
                 if colname == "Qualifier":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.QualifierList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.QualifierList)
                 if colname == "Duration":
                     editor = wx.grid.GridCellTextEditor()
                     renderer = wx.grid.GridCellStringRenderer()
                     readonly = not self.Parent.DurationList[self.data[row].qualifier]
                 elif colname == "Type":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TypeList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TypeList)
                 elif colname == "Value":
                     value_type = self.data[row].type
                     if value_type == "Action":
-                        editor = wx.grid.GridCellChoiceEditor()
-                        editor.SetParameters(self.Parent.ActionList)
+                        editor = wx.grid.GridCellChoiceEditor(self.Parent.ActionList)
                     elif value_type == "Variable":
-                        editor = wx.grid.GridCellChoiceEditor()
-                        editor.SetParameters(self.Parent.VariableList)
+                        editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
                     elif value_type == "Inline":
                         editor = wx.grid.GridCellTextEditor()
                         renderer = wx.grid.GridCellStringRenderer()
                 elif colname == "Indicator":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.VariableList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
 
                 grid.SetCellEditor(row, col, editor)
                 grid.SetCellRenderer(row, col, renderer)
                 grid.SetReadOnly(row, col, readonly)
 
-                grid.SetCellBackgroundColour(row, col, wx.WHITE)
             self.ResizeRow(grid, row)
 
 # -------------------------------------------------------------------------------
@@ -133,11 +127,11 @@
         top_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         top_sizer.AddGrowableCol(0)
         top_sizer.AddGrowableRow(0)
-        main_sizer.AddSizer(top_sizer, border=20,
+        main_sizer.Add(top_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         actions_label = wx.StaticText(self, label=_('Actions:'))
-        top_sizer.AddWindow(actions_label, flag=wx.ALIGN_BOTTOM)
+        top_sizer.Add(actions_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddButton", "add_element", _("Add action")),
@@ -147,28 +141,28 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            top_sizer.AddWindow(button)
+            top_sizer.Add(button)
 
         self.ActionsGrid = CustomGrid(self, size=wx.Size(-1, 250), style=wx.VSCROLL)
         self.ActionsGrid.DisableDragGridSize()
         self.ActionsGrid.EnableScrolling(False, True)
-        self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.ActionsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                               self.OnActionsGridCellChange)
-        main_sizer.AddSizer(self.ActionsGrid, border=20,
+        main_sizer.Add(self.ActionsGrid, border=20,
                             flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
 
         self.Table = ActionTable(self, [], GetActionTableColnames())
         typelist = GetTypeList()
-        self.TypeList = ",".join(map(_, typelist))
+        self.TypeList = list(map(_, typelist))
         self.TranslateType = dict([(_(value), value) for value in typelist])
         self.ColSizes = [60, 90, 130, 200, 50]
         self.ColAlignements = [wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
@@ -201,15 +195,15 @@
         wx.CallAfter(self.Table.ResetView, self.ActionsGrid)
         event.Skip()
 
-    def SetQualifierList(self, list):
-        self.QualifierList = ",".join(list)
-        self.DurationList = list
-
-    def SetVariableList(self, list):
-        self.VariableList = "," + ",".join([variable.Name for variable in list])
-
-    def SetActionList(self, list):
-        self.ActionList = "," + ",".join(list)
+    def SetQualifierList(self, odict):
+        self.QualifierList = [qname for qname in odict]
+        self.DurationList = odict
+
+    def SetVariableList(self, lst):
+        self.VariableList = [variable.Name for variable in lst]
+
+    def SetActionList(self, lst):
+        self.ActionList = lst
 
     def SetValues(self, actions):
         for action in actions:
--- a/dialogs/ArrayTypeDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/ArrayTypeDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import re
 
 import wx
@@ -50,31 +50,31 @@
         main_sizer.AddGrowableRow(1)
 
         top_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(top_sizer, border=20,
+        main_sizer.Add(top_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         basetype_label = wx.StaticText(self, label=_('Base Type:'))
-        top_sizer.AddWindow(basetype_label, 1, flag=wx.ALIGN_BOTTOM)
+        top_sizer.Add(basetype_label, 1, flag=wx.ALIGN_BOTTOM)
 
         self.BaseType = wx.ComboBox(self, style=wx.CB_READONLY)
-        top_sizer.AddWindow(self.BaseType, 1, flag=wx.GROW)
+        top_sizer.Add(self.BaseType, 1, flag=wx.GROW)
 
         self.Dimensions = CustomEditableListBox(self, label=_("Dimensions:"),
-                                                style=(wx.gizmos.EL_ALLOW_NEW |
-                                                       wx.gizmos.EL_ALLOW_EDIT |
-                                                       wx.gizmos.EL_ALLOW_DELETE))
+                                                style=(wx.adv.EL_ALLOW_NEW |
+                                                       wx.adv.EL_ALLOW_EDIT |
+                                                       wx.adv.EL_ALLOW_DELETE))
         for func in ["_OnLabelEndEdit",
                      "_OnAddButton",
                      "_OnDelButton",
                      "_OnUpButton",
                      "_OnDownButton"]:
             setattr(self.Dimensions, func, self.OnDimensionsChanged)
-        main_sizer.AddSizer(self.Dimensions, border=20,
+        main_sizer.Add(self.Dimensions, border=20,
                             flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
@@ -84,7 +84,7 @@
 
         if isinstance(infos, tuple) and infos[0] == "array":
             self.BaseType.SetStringSelection(infos[1])
-            self.Dimensions.SetStrings(map("..".join, infos[2]))
+            self.Dimensions.SetStrings(list(map("..".join, infos[2])))
         elif infos in datatypes:
             self.BaseType.SetStringSelection(infos)
 
--- a/dialogs/BlockPreviewDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/BlockPreviewDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,8 +24,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import wx
 
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
@@ -75,8 +75,7 @@
 
         # Add default dialog buttons sizer
         self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.ButtonSizer.GetAffirmativeButton())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
         self.Element = None            # Graphic element to display in preview
         self.MinElementSize = None     # Graphic element minimal size
@@ -88,13 +87,6 @@
         # List of variables defined in POU {var_name: (var_class, var_type),...}
         self.VariableList = {}
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Remove reference to project controller
-        self.Controller = None
-
     def _init_sizers(self,
                      main_rows, main_growable_row,
                      left_rows, left_growable_row,
@@ -120,7 +112,7 @@
 
         # Create a sizer for dividing parameters in two columns
         self.ColumnSizer = wx.BoxSizer(wx.HORIZONTAL)
-        self.MainSizer.AddSizer(self.ColumnSizer, border=20,
+        self.MainSizer.Add(self.ColumnSizer, border=20,
                                 flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         # Create a sizer for left column
@@ -129,7 +121,7 @@
         self.LeftGridSizer.AddGrowableCol(0)
         if left_growable_row is not None:
             self.LeftGridSizer.AddGrowableRow(left_growable_row)
-        self.ColumnSizer.AddSizer(self.LeftGridSizer, 1, border=5,
+        self.ColumnSizer.Add(self.LeftGridSizer, 1, border=5,
                                   flag=wx.GROW | wx.RIGHT | wx.EXPAND)
 
         # Create a sizer for right column
@@ -138,7 +130,7 @@
         self.RightGridSizer.AddGrowableCol(0)
         if right_growable_row is not None:
             self.RightGridSizer.AddGrowableRow(right_growable_row)
-        self.ColumnSizer.AddSizer(self.RightGridSizer, 1, border=5,
+        self.ColumnSizer.Add(self.RightGridSizer, 1, border=5,
                                   flag=wx.GROW | wx.LEFT)
 
         self.SetSizer(self.MainSizer)
--- a/dialogs/BrowseLocationsDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/BrowseLocationsDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.#
 
 
-from __future__ import absolute_import
+
 import wx
 
 from plcopen.structures import LOCATIONDATATYPES
@@ -54,7 +54,7 @@
 
 # turn LOCATIONDATATYPES inside-out
 LOCATION_SIZES = {}
-for size, types in LOCATIONDATATYPES.iteritems():
+for size, types in LOCATIONDATATYPES.items():
     for type in types:
         LOCATION_SIZES[type] = size
 
@@ -76,7 +76,7 @@
         main_sizer.AddGrowableRow(1)
 
         locations_label = wx.StaticText(self, label=_('Locations available:'))
-        main_sizer.AddWindow(locations_label, border=20,
+        main_sizer.Add(locations_label, border=20,
                              flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
         self.LocationsTree = wx.TreeCtrl(self,
@@ -88,7 +88,7 @@
         self.LocationsTree.SetInitialSize(wx.Size(-1, 300))
         self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnLocationsTreeItemActivated,
                   self.LocationsTree)
-        main_sizer.AddWindow(self.LocationsTree, border=20,
+        main_sizer.Add(self.LocationsTree, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.GROW)
 
         self.RenameCheckBox = wx.CheckBox(self, label=_("Rename variable to signal name"))
@@ -97,37 +97,37 @@
         self.RenameCheckBox.SetValue(default_checked)
         self.do_rename = default_checked
 
-        main_sizer.AddWindow(self.RenameCheckBox, border=20,
+        main_sizer.Add(self.RenameCheckBox, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.GROW)
 
         button_gridsizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         button_gridsizer.AddGrowableCol(1)
         button_gridsizer.AddGrowableCol(3)
         button_gridsizer.AddGrowableRow(0)
-        main_sizer.AddSizer(button_gridsizer, border=20,
+        main_sizer.Add(button_gridsizer, border=20,
                             flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.GROW)
 
         direction_label = wx.StaticText(self, label=_('Direction:'))
-        button_gridsizer.AddWindow(direction_label,
+        button_gridsizer.Add(direction_label,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.DirFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.DirFilterChoice)
-        button_gridsizer.AddWindow(self.DirFilterChoice,
+        button_gridsizer.Add(self.DirFilterChoice,
                                    flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL)
 
         filter_label = wx.StaticText(self, label=_('Type:'))
-        button_gridsizer.AddWindow(filter_label,
+        button_gridsizer.Add(filter_label,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.TypeFilterChoice = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnFilterChoice, self.TypeFilterChoice)
-        button_gridsizer.AddWindow(self.TypeFilterChoice,
+        button_gridsizer.Add(self.TypeFilterChoice,
                                    flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        button_gridsizer.AddSizer(button_sizer, flag=wx.ALIGN_RIGHT)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        button_gridsizer.Add(button_sizer, flag=wx.ALIGN_RIGHT)
 
         self.SetSizer(main_sizer)
 
@@ -204,7 +204,7 @@
                         item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie)
                 else:
                     self.LocationsTree.SetItemText(item, infos["name"])
-                self.LocationsTree.SetPyData(item, infos)
+                self.LocationsTree.SetItemData(item, infos)
                 self.LocationsTree.SetItemImage(item, self.TreeImageDict[infos["type"]])
                 self.GenerateLocationsTreeBranch(item, children)
                 item, root_cookie = self.LocationsTree.GetNextChild(root, root_cookie)
@@ -215,7 +215,7 @@
             self.LocationsTree.Delete(item)
 
     def OnLocationsTreeItemActivated(self, event):
-        infos = self.LocationsTree.GetPyData(event.GetItem())
+        infos = self.LocationsTree.GetItemData(event.GetItem())
         if infos["type"] not in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]:
             wx.CallAfter(self.EndModal, wx.ID_OK)
         event.Skip()
@@ -226,7 +226,7 @@
 
     def GetValues(self):
         selected = self.LocationsTree.GetSelection()
-        infos = self.LocationsTree.GetPyData(selected)
+        infos = self.LocationsTree.GetItemData(selected)
         if not self.do_rename:
             infos["var_name"] = None
         return infos
@@ -237,7 +237,7 @@
         selected = self.LocationsTree.GetSelection()
         var_infos = None
         if selected.IsOk():
-            var_infos = self.LocationsTree.GetPyData(selected)
+            var_infos = self.LocationsTree.GetItemData(selected)
         if var_infos is None or var_infos["type"] in [LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP]:
             dialog = wx.MessageDialog(self, _("A location must be selected!"), _("Error"), wx.OK | wx.ICON_ERROR)
             dialog.ShowModal()
--- a/dialogs/BrowseValuesLibraryDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/BrowseValuesLibraryDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 
@@ -51,13 +51,13 @@
 
         self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.ButtonSizer.GetAffirmativeButton().GetId())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
         self.flexGridSizer1 = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=10)
 
-        self.flexGridSizer1.AddWindow(self.staticText1,   0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
-        self.flexGridSizer1.AddWindow(self.ValuesLibrary, 0, border=20, flag=wx.GROW | wx.LEFT | wx.RIGHT)
-        self.flexGridSizer1.AddSizer(self.ButtonSizer,    0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.staticText1,   0, border=20, flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.ValuesLibrary, 0, border=20, flag=wx.GROW | wx.LEFT | wx.RIGHT)
+        self.flexGridSizer1.Add(self.ButtonSizer,    0, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.flexGridSizer1.AddGrowableCol(0)
         self.flexGridSizer1.AddGrowableRow(1)
@@ -79,11 +79,11 @@
 
     def GetValueInfos(self):
         selected = self.ValuesLibrary.GetSelection()
-        return self.ValuesLibrary.GetPyData(selected)
+        return self.ValuesLibrary.GetItemData(selected)
 
     def OnOK(self, event):
         selected = self.ValuesLibrary.GetSelection()
-        if not selected.IsOk() or self.ValuesLibrary.GetPyData(selected) is None:
+        if not selected.IsOk() or self.ValuesLibrary.GetItemData(selected) is None:
             message = wx.MessageDialog(self, _("No valid value selected!"), _("Error"), wx.OK | wx.ICON_ERROR)
             message.ShowModal()
             message.Destroy()
--- a/dialogs/CommentEditDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/CommentEditDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -20,7 +20,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 
 import wx
 
--- a/dialogs/ConnectionDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/ConnectionDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import CONNECTOR, CONTINUATION
@@ -59,7 +59,7 @@
 
         # Create label for connection type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting connection type
         self.TypeRadioButtons = {}
@@ -70,39 +70,39 @@
                                           style=(wx.RB_GROUP if first else 0))
             radio_button.SetValue(first)
             self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
-            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             first = False
 
         # Create label for connection name
         name_label = wx.StaticText(self, label=_('Name:'))
-        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(name_label, flag=wx.GROW)
 
         # Create text control for defining connection name
         self.ConnectionName = wx.TextCtrl(self)
         self.ConnectionName.SetMinSize(wx.Size(200, -1))
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.ConnectionName)
-        self.LeftGridSizer.AddWindow(self.ConnectionName, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.ConnectionName, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
         self.Preview.SetMinSize(wx.Size(-1, 100))
-        self.LeftGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.LeftGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
-        self.ColumnSizer.RemoveSizer(self.RightGridSizer)
+        self.ColumnSizer.Remove(self.RightGridSizer)
 
         # Add button for applying connection name modification to all connection
         # of POU
         if apply_button:
             self.ApplyToAllButton = wx.Button(self, label=_("Propagate Name"))
-            self.ApplyToAllButton.SetToolTipString(
+            self.ApplyToAllButton.SetToolTip(
                 _("Apply name modification to all continuations with the same name"))
             self.Bind(wx.EVT_BUTTON, self.OnApplyToAll, self.ApplyToAllButton)
-            self.ButtonSizer.AddWindow(self.ApplyToAllButton, flag=wx.LEFT)
+            self.ButtonSizer.Add(self.ApplyToAllButton, flag=wx.LEFT)
         else:
             self.ConnectionName.ChangeValue(
                 controller.GenerateNewName(
@@ -127,7 +127,7 @@
         @param values: Connection parameters values
         """
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is connection type
             if name == "type":
--- a/dialogs/DurationEditorDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/DurationEditorDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,8 +24,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import re
 
 import wx
@@ -68,7 +68,7 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.FlexGridSizer(cols=len(CONTROLS), hgap=10, rows=2, vgap=10)
-        main_sizer.AddSizer(controls_sizer, border=20,
+        main_sizer.Add(controls_sizer, border=20,
                             flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
         controls = []
@@ -85,14 +85,14 @@
             controls.append((st, txtctrl))
 
         for st, txtctrl in controls:
-            controls_sizer.AddWindow(st, flag=wx.GROW)
+            controls_sizer.Add(st, flag=wx.GROW)
 
         for st, txtctrl in controls:
-            controls_sizer.AddWindow(txtctrl, flag=wx.GROW)
+            controls_sizer.Add(txtctrl, flag=wx.GROW)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
@@ -153,6 +153,7 @@
         return duration
 
     def OnOK(self, event):
+        event.Skip()
         self.OnCloseDialog()
 
     def OnCloseDialog(self):
--- a/dialogs/FBDBlockDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/FBDBlockDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import re
 
 import wx
@@ -68,7 +68,7 @@
         # Create static box around library panel
         type_staticbox = wx.StaticBox(self, label=_('Type:'))
         left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL)
-        self.LeftGridSizer.AddSizer(left_staticboxsizer, border=5, flag=wx.GROW)
+        self.LeftGridSizer.Add(left_staticboxsizer, border=5, flag=wx.GROW)
 
         # Create Library panel and add it to static box
         self.LibraryPanel = LibraryPanel(self)
@@ -77,65 +77,65 @@
         # Set function to call when selection in Library panel changed
         setattr(self.LibraryPanel, "_OnTreeItemSelected",
                 self.OnLibraryTreeItemSelected)
-        left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5,
+        left_staticboxsizer.Add(self.LibraryPanel, 1, border=5,
                                       flag=wx.GROW | wx.TOP)
 
         # Create sizer for other block parameters
         top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5)
         top_right_gridsizer.AddGrowableCol(1)
-        self.RightGridSizer.AddSizer(top_right_gridsizer, flag=wx.GROW)
+        self.RightGridSizer.Add(top_right_gridsizer, flag=wx.GROW)
 
         # Create label for block name
         name_label = wx.StaticText(self, label=_('Name:'))
-        top_right_gridsizer.AddWindow(name_label,
+        top_right_gridsizer.Add(name_label,
                                       flag=wx.ALIGN_CENTER_VERTICAL)
 
         # Create text control for defining block name
         self.BlockName = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.BlockName)
-        top_right_gridsizer.AddWindow(self.BlockName, flag=wx.GROW)
+        top_right_gridsizer.Add(self.BlockName, flag=wx.GROW)
 
         # Create label for extended block input number
         inputs_label = wx.StaticText(self, label=_('Inputs:'))
-        top_right_gridsizer.AddWindow(inputs_label,
+        top_right_gridsizer.Add(inputs_label,
                                       flag=wx.ALIGN_CENTER_VERTICAL)
 
         # Create spin control for defining extended block input number
         self.Inputs = wx.SpinCtrl(self, min=2, max=20,
                                   style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, self.Inputs)
-        top_right_gridsizer.AddWindow(self.Inputs, flag=wx.GROW)
+        top_right_gridsizer.Add(self.Inputs, flag=wx.GROW)
 
         # Create label for block execution order
         execution_order_label = wx.StaticText(self,
                                               label=_('Execution Order:'))
-        top_right_gridsizer.AddWindow(execution_order_label,
+        top_right_gridsizer.Add(execution_order_label,
                                       flag=wx.ALIGN_CENTER_VERTICAL)
 
         # Create spin control for defining block execution order
         self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged,
                   self.ExecutionOrder)
-        top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+        top_right_gridsizer.Add(self.ExecutionOrder, flag=wx.GROW)
 
         # Create label for block execution control
         execution_control_label = wx.StaticText(self,
                                                 label=_('Execution Control:'))
-        top_right_gridsizer.AddWindow(execution_control_label,
+        top_right_gridsizer.Add(execution_control_label,
                                       flag=wx.ALIGN_CENTER_VERTICAL)
 
         # Create check box to enable block execution control
         self.ExecutionControl = wx.CheckBox(self)
         self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged,
                   self.ExecutionControl)
-        top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW)
+        top_right_gridsizer.Add(self.ExecutionControl, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(self.ButtonSizer, border=20,
+        self.MainSizer.Add(self.ButtonSizer, border=20,
                                 flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Dictionary containing correspondence between parameter exchanged and
@@ -177,7 +177,7 @@
         default_name_model = GetBlockTypeDefaultNameModel(blocktype)
 
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is block name
             if name == "name":
@@ -212,7 +212,7 @@
         values["width"], values["height"] = self.Element.GetSize()
         values.update({
             name: control.GetValue()
-            for name, control in self.ParamsControl.iteritems()})
+            for name, control in self.ParamsControl.items()})
         return values
 
     def OnOK(self, event):
--- a/dialogs/FBDVariableDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/FBDVariableDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import INPUT, INOUT, OUTPUT
@@ -66,61 +66,61 @@
         }
 
         self.VARIABLE_CLASSES_DICT_REVERSE = dict(
-            [(value, key) for key, value in self.VARIABLE_CLASSES_DICT.iteritems()])
+            [(value, key) for key, value in self.VARIABLE_CLASSES_DICT.items()])
 
         # Init common sizers
         self._init_sizers(4, 2, 4, None, 3, 2)
 
         # Create label for variable class
         class_label = wx.StaticText(self, label=_('Class:'))
-        self.LeftGridSizer.AddWindow(class_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(class_label, flag=wx.GROW)
 
         # Create a combo box for defining variable class
         self.Class = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnClassChanged, self.Class)
-        self.LeftGridSizer.AddWindow(self.Class, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Class, flag=wx.GROW)
 
         # Create label for variable execution order
         execution_order_label = wx.StaticText(self,
                                               label=_('Execution Order:'))
-        self.LeftGridSizer.AddWindow(execution_order_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(execution_order_label, flag=wx.GROW)
 
         # Create spin control for defining variable execution order
         self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged,
                   self.ExecutionOrder)
-        self.LeftGridSizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.ExecutionOrder, flag=wx.GROW)
 
         # Create label for variable expression
         name_label = wx.StaticText(self, label=_('Expression:'))
-        self.RightGridSizer.AddWindow(name_label, border=5,
+        self.RightGridSizer.Add(name_label, border=5,
                                       flag=wx.GROW | wx.BOTTOM)
 
         # Create text control for defining variable expression
         self.Expression = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnExpressionChanged, self.Expression)
-        self.RightGridSizer.AddWindow(self.Expression, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Expression, flag=wx.GROW)
 
         # Create a list box to selected variable expression in the list of
         # variables defined in POU
         self.VariableName = wx.ListBox(self, size=wx.Size(-1, 120),
                                        style=wx.LB_SINGLE | wx.LB_SORT)
         self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
-        self.RightGridSizer.AddWindow(self.VariableName, border=4, flag=wx.GROW | wx.TOP)
+        self.RightGridSizer.Add(self.VariableName, border=4, flag=wx.GROW | wx.TOP)
 
         # Add preview panel and associated label to sizers
-        self.MainSizer.AddWindow(self.PreviewLabel, border=20,
+        self.MainSizer.Add(self.PreviewLabel, border=20,
                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
-        self.MainSizer.AddWindow(self.Preview, border=20,
+        self.MainSizer.Add(self.Preview, border=20,
                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Set options that can be selected in class combo box
-        for var_class, choice in self.VARIABLE_CLASSES_DICT.iteritems():
+        for var_class, choice in self.VARIABLE_CLASSES_DICT.items():
             if not exclude_input or var_class != INPUT:
                 self.Class.Append(choice)
         self.Class.SetSelection(0)
@@ -148,7 +148,7 @@
         # Refresh names in name list box by selecting variables in POU variables
         # list that can be applied to variable class
         self.VariableName.Clear()
-        for name, (var_type, _value_type) in self.VariableList.iteritems():
+        for name, (var_type, _value_type) in self.VariableList.items():
             if var_type != "Input" or var_class == INPUT:
                 self.VariableName.Append(name)
 
@@ -178,7 +178,7 @@
             self.RefreshNameList()
 
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is variable expression
             if name == "expression":
--- a/dialogs/FindInPouDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/FindInPouDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 from plcopen.plcopen import *
 
@@ -48,76 +48,76 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(controls_sizer, border=20,
+        main_sizer.Add(controls_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         patterns_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
         patterns_sizer.AddGrowableCol(1)
-        controls_sizer.AddSizer(patterns_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
+        controls_sizer.Add(patterns_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
 
         find_label = wx.StaticText(panel, label=_("Find:"))
-        patterns_sizer.AddWindow(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
+        patterns_sizer.Add(find_label, flag=wx.ALIGN_CENTER_VERTICAL)
 
         self.FindPattern = wx.TextCtrl(panel)
         self.Bind(wx.EVT_TEXT, self.OnFindPatternChanged, self.FindPattern)
         self.Bind(wx.EVT_CHAR_HOOK, self.OnEscapeKey)
-        patterns_sizer.AddWindow(self.FindPattern, flag=wx.GROW)
+        patterns_sizer.Add(self.FindPattern, flag=wx.GROW)
 
         params_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        controls_sizer.AddSizer(params_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
+        controls_sizer.Add(params_sizer, border=5, flag=wx.GROW | wx.BOTTOM)
 
         direction_staticbox = wx.StaticBox(panel, label=_("Direction"))
         direction_staticboxsizer = wx.StaticBoxSizer(
             direction_staticbox, wx.VERTICAL)
-        params_sizer.AddSizer(direction_staticboxsizer, 1, border=5,
+        params_sizer.Add(direction_staticboxsizer, 1, border=5,
                               flag=wx.GROW | wx.RIGHT)
 
         self.Forward = wx.RadioButton(panel, label=_("Forward"),
                                       style=wx.RB_GROUP)
-        direction_staticboxsizer.AddWindow(self.Forward, border=5,
+        direction_staticboxsizer.Add(self.Forward, border=5,
                                            flag=wx.ALL | wx.GROW)
 
         self.Backward = wx.RadioButton(panel, label=_("Backward"))
-        direction_staticboxsizer.AddWindow(self.Backward, border=5,
+        direction_staticboxsizer.Add(self.Backward, border=5,
                                            flag=wx.ALL | wx.GROW)
 
         options_staticbox = wx.StaticBox(panel, label=_("Options"))
         options_staticboxsizer = wx.StaticBoxSizer(
             options_staticbox, wx.VERTICAL)
-        params_sizer.AddSizer(options_staticboxsizer, 1, flag=wx.GROW)
+        params_sizer.Add(options_staticboxsizer, 1, flag=wx.GROW)
 
         self.CaseSensitive = wx.CheckBox(panel, label=_("Case sensitive"))
         self.CaseSensitive.SetValue(True)
-        options_staticboxsizer.AddWindow(self.CaseSensitive, border=5,
+        options_staticboxsizer.Add(self.CaseSensitive, border=5,
                                          flag=wx.ALL | wx.GROW)
 
         self.WrapSearch = wx.CheckBox(panel, label=_("Wrap search"))
         self.WrapSearch.SetValue(True)
-        options_staticboxsizer.AddWindow(self.WrapSearch, border=5,
+        options_staticboxsizer.Add(self.WrapSearch, border=5,
                                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         self.RegularExpressions = wx.CheckBox(panel, label=_("Regular expressions"))
-        options_staticboxsizer.AddWindow(self.RegularExpressions, border=5,
+        options_staticboxsizer.Add(self.RegularExpressions, border=5,
                                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.GROW)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
 
         self.FindButton = wx.Button(panel, label=_("Find"))
         self.FindButton.SetDefault()
         self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
-        buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
+        buttons_sizer.Add(self.FindButton, border=5, flag=wx.RIGHT)
 
         self.CloseButton = wx.Button(panel, label=_("Close"))
         self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
-        buttons_sizer.AddWindow(self.CloseButton)
+        buttons_sizer.Add(self.CloseButton)
 
         # set the longest message here, to use it length to calculate
         # optimal size of dialog window
         self.RegExpSyntaxErrMsg = _("Syntax error in regular expression of pattern to search!")
         self.StatusLabel = wx.StaticText(panel, label=self.RegExpSyntaxErrMsg)
-        controls_sizer.AddWindow(self.StatusLabel, flag=wx.ALIGN_CENTER_VERTICAL)
+        controls_sizer.Add(self.StatusLabel)
 
         panel.SetSizer(main_sizer)
         main_sizer.Fit(self)
--- a/dialogs/ForceVariableDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/ForceVariableDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,10 +22,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import re
 import datetime
-from builtins import str as text
 
 import wx
 
@@ -189,7 +187,7 @@
         info_sizer = wx.BoxSizer(wx.VERTICAL)
 
         message_label = wx.StaticText(self, label=_("Forcing Variable Value"))
-        info_sizer.AddWindow(message_label, border=10,
+        info_sizer.Add(message_label, border=10,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         if GetTypeValue[self.IEC_Type] in [getinteger, getfloat]:
@@ -201,8 +199,8 @@
         self.GetEnteredValue = self.GetValueDefault
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        info_sizer.AddSizer(button_sizer, border=10, flag=wx.ALIGN_RIGHT | wx.ALL)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        info_sizer.Add(button_sizer, border=10, flag=wx.ALIGN_RIGHT | wx.ALL)
 
         self.SetSizer(info_sizer)
         self.Fit()
@@ -216,7 +214,7 @@
         """Add simple text control to change variable of any type"""
         self.ValueCtrl = wx.TextCtrl(self)
         self.ValueCtrl.SetValue(defaultValue)
-        info_sizer.AddWindow(self.ValueCtrl, border=10, proportion=1,
+        info_sizer.Add(self.ValueCtrl, border=10, proportion=1,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
     def GetValueDefault(self):
@@ -224,7 +222,7 @@
         Returns text representation for a variable value
         @return: variable value as a string
         """
-        return text(self.ValueCtrl.GetValue())
+        return str(self.ValueCtrl.GetValue())
 
     # -----------------------------------------------
     # integer and floating point number type methods
@@ -235,11 +233,11 @@
         sizer = wx.BoxSizer(wx.HORIZONTAL)
         self.InitCtrlDefault(sizer, defaultValue)
         self.SpinButtonCtrl = wx.SpinButton(self, style=wx.HORIZONTAL | wx.SP_WRAP)
-        sizer.AddWindow(self.SpinButtonCtrl, border=10,
+        sizer.Add(self.SpinButtonCtrl, border=10,
                         flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND)
         self.Bind(wx.EVT_SPIN_UP, self.SpinButtonChanged)
         self.Bind(wx.EVT_SPIN_DOWN, self.SpinButtonChanged)
-        info_sizer.AddWindow(sizer, proportion=1, flag=wx.EXPAND)
+        info_sizer.Add(sizer, proportion=1, flag=wx.EXPAND)
 
     def SpinButtonChanged(self, evt):
         """Increment/decrement variable value"""
@@ -247,7 +245,7 @@
         if value is not None:
             up = evt.GetEventType() == wx.EVT_SPIN_UP._getEvtType()
             value = value + 1 if up else value - 1
-            self.ValueCtrl.SetValue(text(value))
+            self.ValueCtrl.SetValue(str(value))
         evt.Skip()
 
     # -----------------------------------------------
@@ -261,7 +259,7 @@
         if value is not None:
             self.ValueCtrl.SetValue(value)
 
-        info_sizer.AddWindow(self.ValueCtrl, border=10,
+        info_sizer.Add(self.ValueCtrl, border=10,
                              flag=wx.ALIGN_LEFT | wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT | wx.GROW)
 
     def OnOK(self, event):
--- a/dialogs/IDManager.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/IDManager.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,4 +1,4 @@
-from __future__ import absolute_import
+
 
 import wx
 from controls.IDBrowser import IDBrowser
--- a/dialogs/IDMergeDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/IDMergeDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import wx
 
 
@@ -15,11 +15,11 @@
         main_sizer = wx.BoxSizer(wx.VERTICAL)
 
         message = wx.StaticText(self, label=question)
-        main_sizer.AddWindow(message, border=20,
+        main_sizer.Add(message, border=20,
                              flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.LEFT | wx.RIGHT)
 
         self.check = wx.CheckBox(self, label=optiontext)
-        main_sizer.AddWindow(self.check, border=20,
+        main_sizer.Add(self.check, border=20,
                              flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -30,9 +30,9 @@
                 return lambda event: self.EndModal(_wxID)
 
             self.Bind(wx.EVT_BUTTON, OnButtonFactory(wxID), Button)
-            buttons_sizer.AddWindow(Button)
+            buttons_sizer.Add(Button)
 
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/LDElementDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/LDElementDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import CONTACT_NORMAL, CONTACT_REVERSE, \
@@ -63,7 +63,7 @@
 
         # Create label for LD element modifier
         modifier_label = wx.StaticText(self, label=_('Modifier:'))
-        self.LeftGridSizer.AddWindow(modifier_label, border=5,
+        self.LeftGridSizer.Add(modifier_label, border=5,
                                      flag=wx.GROW | wx.BOTTOM)
 
         # Create radio buttons for selecting LD element modifier
@@ -84,13 +84,13 @@
                                           style=(wx.RB_GROUP if first else 0))
             radio_button.SetValue(first)
             self.Bind(wx.EVT_RADIOBUTTON, self.OnModifierChanged, radio_button)
-            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.ModifierRadioButtons[modifier] = radio_button
             first = False
 
         # Create label for LD element variable
         element_variable_label = wx.StaticText(self, label=_('Variable:'))
-        self.LeftGridSizer.AddWindow(element_variable_label, border=5,
+        self.LeftGridSizer.Add(element_variable_label, border=5,
                                      flag=wx.GROW | wx.TOP)
 
         # Create a combo box for defining LD element variable
@@ -99,15 +99,15 @@
                   self.ElementVariable)
         self.Bind(wx.EVT_TEXT, self.OnVariableChanged,
                   self.ElementVariable)
-        self.LeftGridSizer.AddWindow(self.ElementVariable, border=5,
+        self.LeftGridSizer.Add(self.ElementVariable, border=5,
                                      flag=wx.GROW | wx.TOP)
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(self.ButtonSizer, border=20,
+        self.MainSizer.Add(self.ButtonSizer, border=20,
                                 flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Save LD element class
@@ -117,7 +117,7 @@
         self.RefreshVariableList()
 
         # Set values in ElementVariable
-        for name, (var_type, value_type) in self.VariableList.iteritems():
+        for name, (var_type, value_type) in self.VariableList.items():
             # Only select BOOL variable and avoid input for coil
             if (type == "contact" or var_type != "Input") and \
                value_type == "BOOL":
@@ -134,7 +134,7 @@
         """
         # Go through radio buttons and return modifier associated to the one
         # that is selected
-        for modifier, control in self.ModifierRadioButtons.iteritems():
+        for modifier, control in self.ModifierRadioButtons.items():
             if control.GetValue():
                 return modifier
         return None
@@ -145,7 +145,7 @@
         @param values: LD element parameters values
         """
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is LD element variable
             if name == "variable":
@@ -198,8 +198,9 @@
             self.GetElementModifier(),
             value)
 
-        button = self.ButtonSizer.GetAffirmativeButton()
-        button.Enable(value != "")
+        # FIXME : how to disable OK button when content is not valid
+        # button = self.ButtonSizer.GetAffirmativeButton()
+        # button.Enable(value != "")
 
         # Call BlockPreviewDialog function
         BlockPreviewDialog.DrawPreview(self)
--- a/dialogs/LDPowerRailDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/LDPowerRailDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import LEFTRAIL, RIGHTRAIL
@@ -56,7 +56,7 @@
 
         # Create label for connection type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting power rail type
         self.TypeRadioButtons = {}
@@ -67,27 +67,27 @@
                                           style=(wx.RB_GROUP if first else 0))
             radio_button.SetValue(first)
             self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
-            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             first = False
 
         # Create label for power rail pin number
         pin_number_label = wx.StaticText(self, label=_('Pin number:'))
-        self.LeftGridSizer.AddWindow(pin_number_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(pin_number_label, flag=wx.GROW)
 
         # Create spin control for defining power rail pin number
         self.PinNumber = wx.SpinCtrl(self, min=1, max=50,
                                      style=wx.SP_ARROW_KEYS)
         self.PinNumber.SetValue(1)
         self.Bind(wx.EVT_SPINCTRL, self.OnPinNumberChanged, self.PinNumber)
-        self.LeftGridSizer.AddWindow(self.PinNumber, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.PinNumber, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
         self.Fit()
@@ -118,7 +118,7 @@
         @param values: Power rail parameters values
         """
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is power rail type
             if name == "type":
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogs/MessageBoxOnce.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz
+# Copyright (C) 2022: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+
+
+
+import wx
+
+
+# class RichMessageDialog is still not available in wxPython 3.0.2
+class  MessageBoxOnce(wx.Dialog):
+    """
+    wx.MessageBox that user can ask not to show again
+    """
+    def __init__(self, title, message, config_key):
+        self.Config = wx.ConfigBase.Get()
+        self.config_key = config_key
+        dont_show = self.Config.Read(self.config_key) == "True"
+
+        if dont_show:
+            return
+
+        wx.Dialog.__init__(self, None, title=title)
+
+        main_sizer = wx.BoxSizer(wx.VERTICAL)
+
+        message = wx.StaticText(self, label=message)
+        main_sizer.Add(message, border=20,
+            flag=wx.ALIGN_CENTER_HORIZONTAL | wx.TOP | wx.LEFT | wx.RIGHT)
+
+        self.check = wx.CheckBox(self, label=_("don't show this message again"))
+        main_sizer.Add(self.check, border=20,
+            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL)
+
+        buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        Button = wx.Button(self, label="OK")
+
+        self.Bind(wx.EVT_BUTTON, self.OnOKButton, Button)
+        buttons_sizer.Add(Button)
+
+        main_sizer.Add(buttons_sizer, border=20,
+                            flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
+
+        self.SetSizer(main_sizer)
+        self.Fit()
+
+        self.ShowModal()
+
+    def OnOKButton(self, event):
+        if self.check.GetValue():
+            self.Config.Write(self.config_key, "True")
+        self.EndModal(wx.ID_OK)
--- a/dialogs/PouActionDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/PouActionDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
@@ -50,27 +50,26 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         actionname_label = wx.StaticText(self, label=_('Action Name:'))
-        infos_sizer.AddWindow(actionname_label, border=4,
+        infos_sizer.Add(actionname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.ActionName = wx.TextCtrl(self, size=wx.Size(180, -1))
-        infos_sizer.AddWindow(self.ActionName, flag=wx.GROW)
+        infos_sizer.Add(self.ActionName, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, flag=wx.GROW)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
@@ -122,7 +121,7 @@
         self.PouElementNames = [element_name.upper() for element_name in element_names]
 
     def SetValues(self, values):
-        for item, value in values.items():
+        for item, value in list(values.items()):
             if item == "actionName":
                 self.ActionName.SetValue(value)
             elif item == "language":
--- a/dialogs/PouDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/PouDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
@@ -58,34 +58,34 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=15)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pouname_label = wx.StaticText(self, label=_('POU Name:'))
-        infos_sizer.AddWindow(pouname_label, border=4,
+        infos_sizer.Add(pouname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.PouName = wx.TextCtrl(self)
-        infos_sizer.AddWindow(self.PouName, flag=wx.GROW)
+        infos_sizer.Add(self.PouName, flag=wx.GROW)
 
         poutype_label = wx.StaticText(self, label=_('POU Type:'))
-        infos_sizer.AddWindow(poutype_label, border=4,
+        infos_sizer.Add(poutype_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.PouType = wx.ComboBox(self, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnTypeChanged, self.PouType)
-        infos_sizer.AddWindow(self.PouType, flag=wx.GROW)
+        infos_sizer.Add(self.PouType, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, flag=wx.GROW)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
@@ -201,7 +201,7 @@
         self.PouElementNames = [element_name.upper() for element_name in element_names]
 
     def SetValues(self, values):
-        for item, value in values.items():
+        for item, value in list(values.items()):
             if item == "pouName":
                 self.PouName.SetValue(value)
             elif item == "pouType":
--- a/dialogs/PouNameDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/PouNameDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
 
@@ -40,8 +40,7 @@
 
         self.PouNames = []
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
     def OnOK(self, event):
         message = None
--- a/dialogs/PouTransitionDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/PouTransitionDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
@@ -53,26 +53,26 @@
 
         infos_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=3, vgap=10)
         infos_sizer.AddGrowableCol(1)
-        main_sizer.AddSizer(infos_sizer, border=20,
+        main_sizer.Add(infos_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         transitionname_label = wx.StaticText(self, label=_('Transition Name:'))
-        infos_sizer.AddWindow(transitionname_label, border=4,
+        infos_sizer.Add(transitionname_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.TransitionName = wx.TextCtrl(self, size=wx.Size(180, -1))
-        infos_sizer.AddWindow(self.TransitionName, flag=wx.GROW)
+        infos_sizer.Add(self.TransitionName, flag=wx.GROW)
 
         language_label = wx.StaticText(self, label=_('Language:'))
-        infos_sizer.AddWindow(language_label, border=4,
+        infos_sizer.Add(language_label, border=4,
                               flag=wx.ALIGN_CENTER_VERTICAL | wx.TOP)
 
         self.Language = wx.ComboBox(self, style=wx.CB_READONLY)
-        infos_sizer.AddWindow(self.Language, flag=wx.GROW)
+        infos_sizer.Add(self.Language, flag=wx.GROW)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK, button_sizer.GetAffirmativeButton())
-        main_sizer.AddSizer(button_sizer, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(button_sizer, border=20, flag=wx.ALIGN_RIGHT | wx.BOTTOM)
 
         self.SetSizer(main_sizer)
 
@@ -123,7 +123,7 @@
         self.PouElementNames = [pou_name.upper() for pou_name in pou_names]
 
     def SetValues(self, values):
-        for item, value in values.items():
+        for item, value in list(values.items()):
             if item == "transitionName":
                 self.TransitionName.SetValue(value)
             elif item == "language":
--- a/dialogs/ProjectDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/ProjectDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from controls.ProjectPropertiesPanel import ProjectPropertiesPanel
@@ -42,12 +42,11 @@
 
         self.ProjectProperties = ProjectPropertiesPanel(
             self, enable_required=enable_required, scrolling=False)
-        main_sizer.AddWindow(self.ProjectProperties, flag=wx.GROW)
+        main_sizer.Add(self.ProjectProperties, flag=wx.GROW)
 
         self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.ButtonSizer.GetAffirmativeButton())
-        main_sizer.AddSizer(self.ButtonSizer, border=20,
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
+        main_sizer.Add(self.ButtonSizer, border=20,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/dialogs/SFCDivergenceDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/SFCDivergenceDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import SELECTION_DIVERGENCE, \
@@ -58,7 +58,7 @@
 
         # Create label for divergence type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create radio buttons for selecting divergence type
         divergence_buttons = [
@@ -80,7 +80,7 @@
                                           style=(wx.RB_GROUP if first else 0))
             radio_button.SetValue(first)
             self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
-            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             self.TypeRadioButtons[type] = radio_button
             if first:
                 focusbtn = type
@@ -89,19 +89,19 @@
         # Create label for number of divergence sequences
         sequences_label = wx.StaticText(self,
                                         label=_('Number of sequences:'))
-        self.LeftGridSizer.AddWindow(sequences_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(sequences_label, flag=wx.GROW)
 
         # Create spin control for defining number of divergence sequences
         self.Sequences = wx.SpinCtrl(self, min=2, max=20, initial=2)
         self.Bind(wx.EVT_SPINCTRL, self.OnSequencesChanged, self.Sequences)
-        self.LeftGridSizer.AddWindow(self.Sequences, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Sequences, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
@@ -126,7 +126,7 @@
         """
         # Go through radio buttons and return type associated to the one that
         # is selected
-        for type, control in self.TypeRadioButtons.iteritems():
+        for type, control in self.TypeRadioButtons.items():
             if control.GetValue():
                 return type
         return None
--- a/dialogs/SFCStepDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/SFCStepDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.SFC_Objects import SFC_Step
@@ -57,16 +57,16 @@
 
         # Create label for SFC step name
         name_label = wx.StaticText(self, label=_('Name:'))
-        self.LeftGridSizer.AddWindow(name_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(name_label, flag=wx.GROW)
 
         # Create text control for defining SFC step name
         self.StepName = wx.TextCtrl(self)
         self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.StepName)
-        self.LeftGridSizer.AddWindow(self.StepName, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.StepName, flag=wx.GROW)
 
         # Create label for SFC step connectors
         connectors_label = wx.StaticText(self, label=_('Connectors:'))
-        self.LeftGridSizer.AddWindow(connectors_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(connectors_label, flag=wx.GROW)
 
         # Create check boxes for defining connectors available on SFC step
         self.ConnectorsCheckBox = {}
@@ -77,15 +77,15 @@
             if name == "output" or (name == "input" and not initial):
                 check_box.SetValue(True)
             self.Bind(wx.EVT_CHECKBOX, self.OnConnectorsChanged, check_box)
-            self.LeftGridSizer.AddWindow(check_box, flag=wx.GROW)
+            self.LeftGridSizer.Add(check_box, flag=wx.GROW)
             self.ConnectorsCheckBox[name] = check_box
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
@@ -107,7 +107,7 @@
         @param values: Block parameters values
         """
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is step name
             if name == "name":
@@ -130,7 +130,7 @@
         values = {"name": self.StepName.GetValue()}
         values.update({
             name: control.IsChecked()
-            for name, control in self.ConnectorsCheckBox.iteritems()})
+            for name, control in self.ConnectorsCheckBox.items()})
         values["width"], values["height"] = self.Element.GetSize()
         return values
 
@@ -185,7 +185,7 @@
                                 self.Initial)
 
         # Update connectors of SFC step element according to check boxes value
-        for name, control in self.ConnectorsCheckBox.iteritems():
+        for name, control in self.ConnectorsCheckBox.items():
             if control.IsChecked():
                 getattr(self.Element, "Add" + name.capitalize())()
             else:
--- a/dialogs/SFCStepNameDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/SFCStepNameDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 from plcopen.structures import TestIdentifier, IEC_KEYWORDS
 
@@ -42,8 +42,7 @@
         self.Variables = []
         self.StepNames = []
 
-        self.Bind(wx.EVT_BUTTON, self.OnOK,
-                  self.GetSizer().GetItem(2).GetSizer().GetItem(1).GetSizer().GetAffirmativeButton())
+        self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.GetAffirmativeId())
 
     def OnOK(self, event):
         message = None
--- a/dialogs/SFCTransitionDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/SFCTransitionDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.SFC_Objects import SFC_Transition
@@ -57,7 +57,7 @@
 
         # Create label for transition type
         type_label = wx.StaticText(self, label=_('Type:'))
-        self.LeftGridSizer.AddWindow(type_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(type_label, flag=wx.GROW)
 
         # Create combo box for selecting reference value
         reference = wx.ComboBox(self, style=wx.CB_READONLY)
@@ -80,28 +80,28 @@
                                           style=(wx.RB_GROUP if first else 0))
             radio_button.SetValue(first)
             self.Bind(wx.EVT_RADIOBUTTON, self.OnTypeChanged, radio_button)
-            self.LeftGridSizer.AddWindow(radio_button, flag=wx.GROW)
+            self.LeftGridSizer.Add(radio_button, flag=wx.GROW)
             if control is not None:
                 control.Enable(first)
-                self.LeftGridSizer.AddWindow(control, flag=wx.GROW)
+                self.LeftGridSizer.Add(control, flag=wx.GROW)
             self.TypeRadioButtons[type] = (radio_button, control)
             first = False
 
         # Create label for transition priority
         priority_label = wx.StaticText(self, label=_('Priority:'))
-        self.LeftGridSizer.AddWindow(priority_label, flag=wx.GROW)
+        self.LeftGridSizer.Add(priority_label, flag=wx.GROW)
 
         # Create spin control for defining priority value
         self.Priority = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
         self.Bind(wx.EVT_TEXT, self.OnPriorityChanged, self.Priority)
-        self.LeftGridSizer.AddWindow(self.Priority, flag=wx.GROW)
+        self.LeftGridSizer.Add(self.Priority, flag=wx.GROW)
 
         # Add preview panel and associated label to sizers
-        self.RightGridSizer.AddWindow(self.PreviewLabel, flag=wx.GROW)
-        self.RightGridSizer.AddWindow(self.Preview, flag=wx.GROW)
+        self.RightGridSizer.Add(self.PreviewLabel, flag=wx.GROW)
+        self.RightGridSizer.Add(self.Preview, flag=wx.GROW)
 
         # Add buttons sizer to sizers
-        self.MainSizer.AddSizer(
+        self.MainSizer.Add(
             self.ButtonSizer, border=20,
             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
@@ -117,7 +117,7 @@
         """
         # Go through radio buttons and return type and value associated to the
         # one that is selected
-        for type, (radio, control) in self.TypeRadioButtons.iteritems():
+        for type, (radio, control) in self.TypeRadioButtons.items():
             if radio.GetValue():
                 if isinstance(control, wx.ComboBox):
                     return type, control.GetStringSelection()
@@ -136,7 +136,7 @@
         type_value = values.get("value", None)
 
         # For each parameters defined, set corresponding control value
-        for name, value in values.items():
+        for name, value in list(values.items()):
 
             # Parameter is SFC transition priority
             if name == "priority":
@@ -144,7 +144,7 @@
 
             # Parameter is SFC transition type
             elif name == "type":
-                for type, (radio, control) in self.TypeRadioButtons.iteritems():
+                for type, (radio, control) in self.TypeRadioButtons.items():
                     radio.SetValue(type == value)
                     if control is not None:
                         # Enable associated control to type and set value
@@ -197,7 +197,7 @@
         @param event: wx.RadioButtonEvent
         """
         # Refresh sensibility of control associated to transition types
-        for _type, (radio, control) in self.TypeRadioButtons.iteritems():
+        for _type, (radio, control) in self.TypeRadioButtons.items():
             if control is not None:
                 control.Enable(radio.GetValue())
 
--- a/dialogs/SearchInProjectDialog.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/SearchInProjectDialog.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 from plcopen.plcopen import *
 from util.TranslationCatalogs import NoTranslate
@@ -54,59 +54,59 @@
 
         pattern_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=5)
         pattern_sizer.AddGrowableCol(0)
-        main_sizer.AddSizer(pattern_sizer, border=20,
+        main_sizer.Add(pattern_sizer, border=20,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         pattern_label = wx.StaticText(self, label=_('Pattern to search:'))
-        pattern_sizer.AddWindow(pattern_label, flag=wx.ALIGN_BOTTOM)
+        pattern_sizer.Add(pattern_label, flag=wx.ALIGN_BOTTOM)
 
         self.CaseSensitive = wx.CheckBox(self, label=_('Case sensitive'))
-        pattern_sizer.AddWindow(self.CaseSensitive, flag=wx.GROW)
+        pattern_sizer.Add(self.CaseSensitive, flag=wx.GROW)
 
         self.Pattern = wx.TextCtrl(self, size=wx.Size(250, -1))
         self.Bind(wx.EVT_TEXT, self.FindPatternChanged, self.Pattern)
-        pattern_sizer.AddWindow(self.Pattern, flag=wx.GROW)
+        pattern_sizer.Add(self.Pattern, flag=wx.GROW)
         self.Bind(wx.EVT_CHAR_HOOK, self.OnEscapeKey)
         self.RegularExpression = wx.CheckBox(self, label=_('Regular expression'))
-        pattern_sizer.AddWindow(self.RegularExpression, flag=wx.GROW)
+        pattern_sizer.Add(self.RegularExpression, flag=wx.GROW)
 
         scope_staticbox = wx.StaticBox(self, label=_('Scope'))
         scope_sizer = wx.StaticBoxSizer(scope_staticbox, wx.HORIZONTAL)
-        main_sizer.AddSizer(scope_sizer, border=20,
+        main_sizer.Add(scope_sizer, border=20,
                             flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         scope_selection_sizer = wx.BoxSizer(wx.VERTICAL)
-        scope_sizer.AddSizer(scope_selection_sizer, 1, border=5,
+        scope_sizer.Add(scope_selection_sizer, 1, border=5,
                              flag=wx.GROW | wx.TOP | wx.LEFT | wx.BOTTOM)
 
         self.WholeProject = wx.RadioButton(self, label=_('Whole Project'), style=wx.RB_GROUP)
         self.WholeProject.SetValue(True)
         self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.WholeProject)
-        scope_selection_sizer.AddWindow(self.WholeProject, border=5,
+        scope_selection_sizer.Add(self.WholeProject, border=5,
                                         flag=wx.GROW | wx.BOTTOM)
 
         self.OnlyElements = wx.RadioButton(self, label=_('Only Elements'))
         self.Bind(wx.EVT_RADIOBUTTON, self.OnScopeChanged, self.OnlyElements)
         self.OnlyElements.SetValue(False)
-        scope_selection_sizer.AddWindow(self.OnlyElements, flag=wx.GROW)
+        scope_selection_sizer.Add(self.OnlyElements, flag=wx.GROW)
 
         self.ElementsList = wx.CheckListBox(self)
         self.ElementsList.Enable(False)
-        scope_sizer.AddWindow(self.ElementsList, 1, border=5,
+        scope_sizer.Add(self.ElementsList, 1, border=5,
                               flag=wx.GROW | wx.TOP | wx.RIGHT | wx.BOTTOM)
 
         buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        main_sizer.AddSizer(buttons_sizer, border=20,
+        main_sizer.Add(buttons_sizer, border=20,
                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT)
 
         self.FindButton = wx.Button(self, label=_("Find"))
         self.FindButton.SetDefault()
         self.Bind(wx.EVT_BUTTON, self.OnFindButton, self.FindButton)
-        buttons_sizer.AddWindow(self.FindButton, border=5, flag=wx.RIGHT)
+        buttons_sizer.Add(self.FindButton, border=5, flag=wx.RIGHT)
 
         self.CloseButton = wx.Button(self, label=_("Close"))
         self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.CloseButton)
-        buttons_sizer.AddWindow(self.CloseButton)
+        buttons_sizer.Add(self.CloseButton)
 
         self.SetSizer(main_sizer)
 
--- a/dialogs/UriEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/UriEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,4 +1,4 @@
-from __future__ import absolute_import
+
 
 import wx
 from connectors import ConnectorSchemes, EditorClassFromScheme
--- a/dialogs/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/dialogs/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -25,7 +25,7 @@
 
 # Package initialization
 
-from __future__ import absolute_import
+
 
 from dialogs.CommentEditDialog import CommentEditDialog
 from dialogs.ConnectionDialog import ConnectionDialog
@@ -51,3 +51,4 @@
 from dialogs.BrowseValuesLibraryDialog import BrowseValuesLibraryDialog
 from dialogs.UriEditor import UriEditor
 from dialogs.IDManager import IDManager
+from dialogs.MessageBoxOnce import MessageBoxOnce
--- a/docutil/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/docutil/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from docutil.dochtml import *
 from docutil.docpdf import *
 from docutil.docsvg import *
--- a/docutil/dochtml.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/docutil/dochtml.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import subprocess
 import wx
 import wx.html
@@ -41,7 +41,7 @@
         window.Show()
 
 
-EVT_HTML_URL_CLICK = wx.NewId()
+EVT_HTML_URL_CLICK = wx.NewIdRef()
 
 
 class HtmlWindowUrlClick(wx.PyEvent):
--- a/docutil/docpdf.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/docutil/docpdf.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import os
 import wx
 
--- a/docutil/docsvg.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/docutil/docsvg.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,16 +23,19 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
+import os
+import sys
 import wx
 import subprocess
+from dialogs import MessageBoxOnce
 
 
 def _get_inkscape_path():
     """ Return the Inkscape binary path """
 
-    if wx.Platform == '__WXMSW__':
-        from six.moves import winreg
+    if sys.platform.startswith('win32'):
+        import winreg
         inkcmd = None
         tries = [(winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes\\svgfile\\shell\\Inkscape\\command'),
                  (winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes\\inkscape.svg\\shell\\open\\command'),
@@ -73,8 +76,8 @@
     inkpath = get_inkscape_path()
     if inkpath is None:
         return None
-    return map(int, 
-        subprocess.check_output([inkpath,"--version"]).split()[1].split('.'))
+    return list(map(int, 
+        subprocess.check_output([inkpath,"--version"]).split()[1].split(b'.')))
 
 _inkscape_version = None
 def get_inkscape_version():
@@ -86,11 +89,23 @@
     _inkscape_version = _get_inkscape_version()
     return _inkscape_version
 
-def open_svg(svgfile):
-    """ Generic function to open SVG file """
-    
-    inkpath = get_inkscape_path()
-    if inkpath is None:
-        wx.MessageBox("Inkscape is not found or installed !")
-    else:
-        subprocess.Popen([inkpath,svgfile])
+if "SNAP" in os.environ:
+    def open_svg(svgfile):
+        MessageBoxOnce("Launching Inkscape with xdg-open",
+                "Confined app can't launch Inkscape directly.\n"+
+                    "Instead, SVG file is passed to xdg-open.\n"+
+                    "Please select Inskape when proposed.\n\n"+
+                    "Notes: \n"+
+                    " - Inkscape must be installed on you system.\n"+
+                    " - If no choice is proposed, use file manager to change SVG file properties.\n",
+                "DocSVGSnapWarning")
+
+        subprocess.Popen(["xdg-open",svgfile])
+else:
+    def open_svg(svgfile):
+        """ Generic function to open SVG file """
+        inkpath = get_inkscape_path()
+        if inkpath is None:
+            wx.MessageBox("Inkscape is not found or installed !")
+        else:
+            subprocess.Popen([inkpath,svgfile])
--- a/editors/CodeFileEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/CodeFileEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,16 +23,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 import re
-from builtins import str as text
 
 import wx
 import wx.grid
 import wx.stc as stc
 import wx.lib.buttons
-from six.moves import xrange
 
 
 from plcopen.plcopen import TestTextElement
@@ -46,7 +42,7 @@
 
 
 [STC_CODE_ERROR, STC_CODE_SEARCH_RESULT,
- STC_CODE_SECTION] = range(15, 18)
+ STC_CODE_SECTION] = list(range(15, 18))
 
 HIGHLIGHT_TYPES = {
     ERROR_HIGHLIGHT: STC_CODE_ERROR,
@@ -285,23 +281,28 @@
     def RefreshSectionStyling(self):
         self.Colourise(0, -1)
 
-        for line in xrange(self.GetLineCount()):
+        for line in range(self.GetLineCount()):
             self.SetLineState(line, 0)
 
         doc_end_pos = self.GetLength()
         for section in self.Controler.SECTIONS_NAMES:
             section_comments = self.SectionsComments[section]
-            start_pos = self.FindText(0, doc_end_pos, section_comments["comment"])
-            end_pos = start_pos + len(section_comments["comment"])
-            self.StartStyling(start_pos, 0xff)
+            txttofind = section_comments["comment"]
+            results = self.FindText(0, doc_end_pos, txttofind)
+            if wx.VERSION < (4, 1, 0):
+                start_pos = results
+                end_pos = start_pos+len(txttofind)
+            else:
+                start_pos, end_pos = results
+            self.StartStyling(start_pos)
             self.SetStyling(end_pos - start_pos, STC_CODE_SECTION)
             self.SetLineState(self.LineFromPosition(start_pos), 1)
 
-        self.StartStyling(end_pos, 0x00)
+        self.StartStyling(end_pos)
         self.SetStyling(doc_end_pos - end_pos, stc.STC_STYLE_DEFAULT)
 
     def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
+        return self.ParentWindow.GetBestSize()
 
     def RefreshModel(self):
         text = self.GetText()
@@ -597,7 +598,7 @@
                 highlight_end_pos = end[1] + 1
             else:
                 highlight_end_pos = self.GetLineEndPosition(end[0] - 1) + end[1] + 2
-            self.StartStyling(highlight_start_pos, 0xff)
+            self.StartStyling(highlight_start_pos)
             self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
             self.StartStyling(highlight_end_pos, 0x00)
             self.SetStyling(len(self.GetText()) - highlight_end_pos, stc.STC_STYLE_DEFAULT)
@@ -614,8 +615,7 @@
 
 class ClassGridCellEditor(wx.grid.GridCellChoiceEditor):
     def __init__(self, table, row, col):
-        wx.grid.GridCellChoiceEditor.__init__(self)
-        self.SetParameters("input,memory,output")
+        wx.grid.GridCellChoiceEditor.__init__(self,["input","memory","output"])
 
 
 class VariablesTable(CustomTable):
@@ -629,8 +629,8 @@
         super(VariablesTable, self).__init__(*args, **kwargs)
         self.columnTypes = dict(self.__defaultColumnType)
         if my_columns is not None:
-            for key in my_columns.keys():
-                if key in self.columnTypes.keys():
+            for key in list(my_columns.keys()):
+                if key in list(self.columnTypes.keys()):
                     self.columnTypes[key] = my_columns[key]
 
     def GetValue(self, row, col):
@@ -638,7 +638,7 @@
             if col == 0:
                 return row + 1
             else:
-                return text(self.data[row].get(self.GetColLabelValue(col, False), ""))
+                return str(self.data[row].get(self.GetColLabelValue(col, False), ""))
 
     def _updateColAttrs(self, grid):
         """
@@ -678,7 +678,7 @@
         main_sizer.AddGrowableRow(0)
 
         controls_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(controls_sizer, border=5, flag=wx.ALL)
+        main_sizer.Add(controls_sizer, border=5, flag=wx.ALL)
 
         for name, bitmap, help in [
                 ("AddVariableButton", "add_element", _("Add variable")),
@@ -687,15 +687,15 @@
                 ("DownVariableButton", "down", _("Move variable down"))]:
             button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            controls_sizer.AddWindow(button, border=5, flag=wx.BOTTOM)
+            controls_sizer.Add(button, border=5, flag=wx.BOTTOM)
 
         self.VariablesGrid = CustomGrid(self, style=wx.VSCROLL)
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
+        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING, self.OnVariablesGridCellChange)
         self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
         self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
-        main_sizer.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        main_sizer.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.SetSizer(main_sizer)
 
@@ -785,7 +785,7 @@
         self.VariablesGrid.RefreshButtons()
 
     def DoGetBestSize(self):
-        return self.ParentWindow.GetPanelBestSize()
+        return self.ParentWindow.GetBestSize()
 
     def ShowErrorMessage(self, message):
         dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR)
@@ -826,17 +826,17 @@
             type_menu = wx.Menu(title='')
             base_menu = wx.Menu(title='')
             for base_type in self.Controler.GetBaseTypes():
-                new_entry = base_menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=base_type)
+                new_entry = base_menu.Append(wx.ID_ANY, base_type)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), new_entry)
             type_menu.AppendMenu(wx.ID_ANY, "Base Types", base_menu)
             datatype_menu = wx.Menu(title='')
             for datatype in self.Controler.GetDataTypes():
-                new_entry = datatype_menu.Append(help='', id=wx.ID_ANY, kind=wx.ITEM_NORMAL, text=datatype)
+                new_entry = datatype_menu.Append(wx.ID_ANY, item=datatype)
                 self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), new_entry)
             type_menu.AppendMenu(wx.ID_ANY, "User Data Types", datatype_menu)
             rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
 
-            self.VariablesGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
+            self.VariablesGrid.PopupMenu(type_menu, rect.x + rect.width, rect.y + self.VariablesGrid.GetColLabelSize())
             type_menu.Destroy()
             event.Veto()
         else:
--- a/editors/ConfTreeNodeEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/ConfTreeNodeEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,8 +24,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 
 import wx
 
@@ -120,7 +120,7 @@
 
         bitmap = GetBitmap(bitmapname)
         if bitmap is None:
-            bitmap = wx.EmptyBitmap(0, 0)
+            bitmap = wx.Bitmap()
 
         wx.StaticBitmap.__init__(self, parent, ID,
                                  bitmap,
@@ -148,18 +148,18 @@
 
             if self.SHOW_BASE_PARAMS:
                 baseparamseditor_sizer = wx.BoxSizer(wx.HORIZONTAL)
-                self.MainSizer.AddSizer(baseparamseditor_sizer, border=5,
+                self.MainSizer.Add(baseparamseditor_sizer, border=5,
                                         flag=wx.GROW | wx.ALL)
 
                 self.FullIECChannel = wx.StaticText(self.Editor, -1)
                 self.FullIECChannel.SetFont(
                     wx.Font(faces["size"], wx.DEFAULT, wx.NORMAL,
                             wx.BOLD, faceName=faces["helv"]))
-                baseparamseditor_sizer.AddWindow(self.FullIECChannel,
+                baseparamseditor_sizer.Add(self.FullIECChannel,
                                                  flag=wx.ALIGN_CENTER_VERTICAL)
 
                 updownsizer = wx.BoxSizer(wx.VERTICAL)
-                baseparamseditor_sizer.AddSizer(updownsizer, border=5,
+                baseparamseditor_sizer.Add(updownsizer, border=5,
                                                 flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
 
                 self.IECCUpButton = wx.lib.buttons.GenBitmapTextButton(
@@ -169,35 +169,35 @@
                     style=wx.NO_BORDER)
                 self.IECCUpButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(1),
                                        self.IECCUpButton)
-                updownsizer.AddWindow(self.IECCUpButton, flag=wx.ALIGN_LEFT)
+                updownsizer.Add(self.IECCUpButton, flag=wx.ALIGN_LEFT)
 
                 self.IECCDownButton = wx.lib.buttons.GenBitmapButton(
                     self.Editor, bitmap=GetBitmap('IECCUp'),
                     size=wx.Size(16, 16), style=wx.NO_BORDER)
                 self.IECCDownButton.Bind(wx.EVT_BUTTON, self.GetItemChannelChangedFunction(-1),
                                          self.IECCDownButton)
-                updownsizer.AddWindow(self.IECCDownButton, flag=wx.ALIGN_LEFT)
+                updownsizer.Add(self.IECCDownButton, flag=wx.ALIGN_LEFT)
 
                 self.ConfNodeName = wx.TextCtrl(self.Editor,
                                                 size=wx.Size(150, 25))
                 self.ConfNodeName.SetFont(
-                    wx.Font(faces["size"] * 0.75, wx.DEFAULT, wx.NORMAL,
+                    wx.Font(round(faces["size"] * 0.75), wx.DEFAULT, wx.NORMAL,
                             wx.BOLD, faceName=faces["helv"]))
                 self.ConfNodeName.Bind(
                     wx.EVT_TEXT,
                     self.GetTextCtrlCallBackFunction(self.ConfNodeName, "BaseParams.Name", True),
                     self.ConfNodeName)
-                baseparamseditor_sizer.AddWindow(
+                baseparamseditor_sizer.Add(
                     self.ConfNodeName, border=5,
                     flag=wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL)
 
                 buttons_sizer = self.GenerateMethodButtonSizer()
-                baseparamseditor_sizer.AddSizer(buttons_sizer, flag=wx.ALIGN_CENTER)
+                baseparamseditor_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER)
 
             if tabs_num > 1:
                 self.ConfNodeNoteBook = wx.Notebook(self.Editor)
                 parent = self.ConfNodeNoteBook
-                self.MainSizer.AddWindow(self.ConfNodeNoteBook, 1, flag=wx.GROW)
+                self.MainSizer.Add(self.ConfNodeNoteBook, 1, flag=wx.GROW)
             else:
                 parent = self.Editor
                 self.ConfNodeNoteBook = None
@@ -212,7 +212,7 @@
             if self.ConfNodeNoteBook is not None:
                 self.ConfNodeNoteBook.AddPage(editor, title)
             elif self.SHOW_BASE_PARAMS:
-                self.MainSizer.AddWindow(editor, 1, flag=wx.GROW)
+                self.MainSizer.Add(editor, 1, flag=wx.GROW)
             else:
                 self.Editor = editor
 
@@ -232,7 +232,7 @@
             self.ParamsEditor.SetSizer(self.ParamsEditorSizer)
 
             self.ConfNodeParamsSizer = wx.BoxSizer(wx.VERTICAL)
-            self.ParamsEditorSizer.AddSizer(self.ConfNodeParamsSizer, border=5,
+            self.ParamsEditorSizer.Add(self.ConfNodeParamsSizer, border=5,
                                             flag=wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
             self.RefreshConfNodeParamsSizer()
@@ -240,7 +240,7 @@
             if self.ConfNodeNoteBook is not None:
                 self.ConfNodeNoteBook.AddPage(self.ParamsEditor, _("Config"))
             elif self.SHOW_BASE_PARAMS:
-                self.MainSizer.AddWindow(self.ParamsEditor, 1, flag=wx.GROW)
+                self.MainSizer.Add(self.ParamsEditor, 1, flag=wx.GROW)
             else:
                 self.Editor = self.ParamsEditor
         else:
@@ -255,9 +255,6 @@
         else:
             self.SetIcon(GetBitmap("Extension"))
 
-    def __del__(self):
-        self.Controler.OnCloseEditor(self)
-
     def GetTagName(self):
         return self.Controler.CTNFullName()
 
@@ -317,7 +314,7 @@
                                              label=confnode_method["name"],
                                              style=wx.NO_BORDER)
                 button.SetFont(normal_bt_font)
-                button.SetToolTipString(confnode_method["tooltip"])
+                button.SetToolTip(confnode_method["tooltip"])
                 if confnode_method.get("push", False):
                     button.Bind(wx.EVT_LEFT_DOWN, self.GetButtonCallBackFunction(confnode_method["method"], True))
                 else:
@@ -335,7 +332,7 @@
                 # hack to force size to mini
                 if not confnode_method.get("enabled", True):
                     button.Disable()
-                msizer.AddWindow(button, flag=wx.ALIGN_CENTER)
+                msizer.Add(button, flag=wx.ALIGN_CENTER)
         return msizer
 
     def UriOptions(self, event):
@@ -380,32 +377,33 @@
                 flags = (wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
                 if first:
                     flags |= wx.TOP
-                sizer.AddSizer(staticboxsizer, border=5, flag=flags)
+                sizer.Add(staticboxsizer, border=5, flag=flags)
                 self.GenerateSizerElements(staticboxsizer,
                                            element_infos["children"],
                                            element_path)
             else:
-                boxsizer = wx.FlexGridSizer(cols=4, rows=1)
+                boxsizer = wx.FlexGridSizer(cols=4, rows=1, gap=wx.Size(0,0))
                 boxsizer.AddGrowableCol(1)
                 flags = (wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
                 if first:
                     flags |= wx.TOP
-                sizer.AddSizer(boxsizer, border=5, flag=flags)
+                sizer.Add(boxsizer, border=5, flag=flags)
                 staticbitmap = GenStaticBitmap(
                     ID=-1, bitmapname=element_infos["name"],
                     name="%s_bitmap" % element_infos["name"], parent=self.ParamsEditor,
                     pos=wx.Point(0, 0), size=wx.Size(24, 24), style=0)
-                boxsizer.AddWindow(staticbitmap, border=5, flag=wx.RIGHT)
-
+                boxsizer.Add(staticbitmap, border=5, flag=wx.RIGHT)
+
+                label = element_infos["name"].replace('_', ' ')
                 statictext = wx.StaticText(self.ParamsEditor,
-                                           label="%s:" % _(element_infos["name"]))
-                boxsizer.AddWindow(statictext, border=5,
+                                           label="%s:" % _(label))
+                boxsizer.Add(statictext, border=5,
                                    flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
 
                 if isinstance(element_infos["type"], list):
                     if isinstance(element_infos["value"], tuple):
                         browse_boxsizer = wx.BoxSizer(wx.HORIZONTAL)
-                        boxsizer.AddSizer(browse_boxsizer)
+                        boxsizer.Add(browse_boxsizer)
 
                         textctrl = wx.TextCtrl(self.ParamsEditor,
                                                size=wx.Size(275, -1), style=wx.TE_READONLY)
@@ -414,10 +412,10 @@
                             value_infos = element_infos["value"][1]
                         else:
                             value_infos = None
-                        browse_boxsizer.AddWindow(textctrl)
+                        browse_boxsizer.Add(textctrl)
 
                         button = wx.Button(self.ParamsEditor, label="...")
-                        browse_boxsizer.AddWindow(button)
+                        browse_boxsizer.Add(button)
                         button.Bind(wx.EVT_BUTTON,
                                     self.GetBrowseCallBackFunction(element_infos["name"], textctrl, element_infos["type"],
                                                                    value_infos, element_path),
@@ -425,23 +423,26 @@
                     else:
                         combobox = wx.ComboBox(self.ParamsEditor,
                                                size=wx.Size(300, -1), style=wx.CB_READONLY)
-                        boxsizer.AddWindow(combobox)
+                        boxsizer.Add(combobox)
 
                         if element_infos["use"] == "optional":
                             combobox.Append("")
                         if len(element_infos["type"]) > 0 and isinstance(element_infos["type"][0], tuple):
                             for choice, _xsdclass in element_infos["type"]:
-                                combobox.Append(choice)
+                                combobox.Append(choice.replace('_',' '))
                             name = element_infos["name"]
                             value = element_infos["value"]
 
-                            staticbox = wx.StaticBox(self.ParamsEditor,
-                                                     label="%s - %s" % (_(name), _(value)),
-                                                     size=wx.Size(10, 0))
-                            staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
-                            sizer.AddSizer(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
-                            self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
-                            callback = self.GetChoiceContentCallBackFunction(combobox, staticboxsizer, element_path)
+                            staticboxsizer = None
+                            if element_infos["children"]:
+                                staticbox = wx.StaticBox(self.ParamsEditor,
+                                                         label="%s - %s" % (_(name), _(value)),
+                                                         size=wx.Size(10, 0))
+                                staticboxsizer = wx.StaticBoxSizer(staticbox, wx.VERTICAL)
+                                sizer.Add(staticboxsizer, border=5, flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
+                                self.GenerateSizerElements(staticboxsizer, element_infos["children"], element_path)
+
+                            callback = self.GetChoiceContentCallBackFunction(combobox, element_path)
                         else:
                             for choice in element_infos["type"]:
                                 combobox.Append(choice)
@@ -449,7 +450,8 @@
                         if element_infos["value"] is None:
                             combobox.SetStringSelection("")
                         else:
-                            combobox.SetStringSelection(element_infos["value"])
+                            combobox.SetStringSelection(
+                                element_infos["value"].replace('_',' '))
                         combobox.Bind(wx.EVT_COMBOBOX, callback, combobox)
 
                 elif isinstance(element_infos["type"], dict):
@@ -463,7 +465,7 @@
                                            size=wx.Size(300, -1),
                                            style=wx.SP_ARROW_KEYS | wx.ALIGN_RIGHT)
                     spinctrl.SetRange(scmin, scmax)
-                    boxsizer.AddWindow(spinctrl)
+                    boxsizer.Add(spinctrl)
                     if element_infos["value"] is not None:
                         spinctrl.SetValue(element_infos["value"])
                     spinctrl.Bind(wx.EVT_SPINCTRL,
@@ -473,7 +475,7 @@
                 else:
                     if element_infos["type"] == "boolean":
                         checkbox = wx.CheckBox(self.ParamsEditor)
-                        boxsizer.AddWindow(checkbox, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
+                        boxsizer.Add(checkbox, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT)
                         if element_infos["value"] is not None:
                             checkbox.SetValue(element_infos["value"])
                         checkbox.Bind(wx.EVT_CHECKBOX,
@@ -490,7 +492,7 @@
                                                size=wx.Size(300, -1),
                                                style=wx.SP_ARROW_KEYS | wx.ALIGN_RIGHT)
                         spinctrl.SetRange(scmin, scmax)
-                        boxsizer.AddWindow(spinctrl)
+                        boxsizer.Add(spinctrl)
                         if element_infos["value"] is not None:
                             spinctrl.SetValue(element_infos["value"])
                         spinctrl.Bind(wx.EVT_SPINCTRL,
@@ -513,12 +515,12 @@
                             self.EditButton = wx.Button(self.ParamsEditor, label='...', size=wx.Size(30, -1))
                             self.Bind(wx.EVT_BUTTON, self.UriOptions, self.EditButton)
 
-                            uriSizer.AddWindow(textctrl, flag=wx.GROW)
-                            uriSizer.AddWindow(self.EditButton, flag=wx.GROW)
-
-                            boxsizer.AddWindow(uriSizer)
+                            uriSizer.Add(textctrl, flag=wx.GROW)
+                            uriSizer.Add(self.EditButton, flag=wx.GROW)
+
+                            boxsizer.Add(uriSizer)
                         else:
-                            boxsizer.AddWindow(textctrl)
+                            boxsizer.Add(textctrl)
 
                         if element_infos["value"] is not None:
                             textctrl.ChangeValue(str(element_infos["value"]))
@@ -527,7 +529,7 @@
                         textctrl.Bind(wx.EVT_TEXT, callback)
                         textctrl.Bind(wx.EVT_KILL_FOCUS, callback)
 
-                if not isinstance(element_infos["type"], list) and element_infos["use"] == "optional":
+                if not isinstance(element_infos["type"], list) and element_infos.get("use", None) == "optional":
                     bt = wx.BitmapButton(self.ParamsEditor, 
                         bitmap=wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR, (16,16)),
                         style=wx.BORDER_NONE)
@@ -535,7 +537,7 @@
                               self.GetResetFunction(element_path),
                               bt)
 
-                    boxsizer.AddWindow(bt, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
+                    boxsizer.Add(bt, border=5, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT)
             first = False
         sizer.Layout()
         self.RefreshScrollbars()
@@ -579,9 +581,10 @@
             event.Skip()
         return OnChoiceChanged
 
-    def GetChoiceContentCallBackFunction(self, choicectrl, staticboxsizer, path):
+    def GetChoiceContentCallBackFunction(self, choicectrl, path):
         def OnChoiceContentChanged(event):
-            self.SetConfNodeParamsAttribute(path, choicectrl.GetStringSelection())
+            self.SetConfNodeParamsAttribute(
+                path, choicectrl.GetStringSelection().replace(' ','_'))
             wx.CallAfter(self.RefreshConfNodeParamsSizer)
             event.Skip()
         return OnChoiceContentChanged
--- a/editors/DataTypeEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/DataTypeEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,9 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import re
-from six.moves import xrange
 
 import wx
 import wx.grid
@@ -47,7 +45,7 @@
 
 
 def AppendMenu(parent, help, kind, text):
-    return parent.Append(help=help, id=wx.ID_ANY, kind=kind, text=text)
+    return parent.Append(wx.MenuItem(helpString=help, id=wx.ID_ANY, kind=kind, text=text))
 
 
 def GetElementsTableColnames():
@@ -155,49 +153,49 @@
         self.MainSizer.AddGrowableRow(1)
 
         top_sizer = wx.BoxSizer(wx.HORIZONTAL)
-        self.MainSizer.AddSizer(top_sizer, border=5,
+        self.MainSizer.Add(top_sizer, border=5,
                                 flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         derivation_type_label = wx.StaticText(self.Editor, label=_('Derivation Type:'))
-        top_sizer.AddWindow(derivation_type_label, border=5,
+        top_sizer.Add(derivation_type_label, border=5,
                             flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT)
 
         self.DerivationType = wx.ComboBox(self.Editor,
                                           size=wx.Size(200, -1), style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnDerivationTypeChanged, self.DerivationType)
-        top_sizer.AddWindow(self.DerivationType, border=5, flag=wx.GROW | wx.RIGHT)
+        top_sizer.Add(self.DerivationType, border=5, flag=wx.GROW | wx.RIGHT)
 
         typeinfos_staticbox = wx.StaticBox(self.Editor, label=_('Type infos:'))
         typeinfos_sizer = wx.StaticBoxSizer(typeinfos_staticbox, wx.HORIZONTAL)
-        self.MainSizer.AddSizer(typeinfos_sizer, border=5,
+        self.MainSizer.Add(typeinfos_sizer, border=5,
                                 flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         # Panel for Directly derived data types
 
         self.DirectlyPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.DirectlyPanel, 1)
+        typeinfos_sizer.Add(self.DirectlyPanel, 1)
 
         directly_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         directly_basetype_label = wx.StaticText(self.DirectlyPanel,
                                                 label=_('Base Type:'))
-        directly_panel_sizer.AddWindow(directly_basetype_label, 1, border=5,
+        directly_panel_sizer.Add(directly_basetype_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.DirectlyBaseType = wx.ComboBox(self.DirectlyPanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.DirectlyBaseType)
-        directly_panel_sizer.AddWindow(self.DirectlyBaseType, 1, border=5,
+        directly_panel_sizer.Add(self.DirectlyBaseType, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         directly_initialvalue_label = wx.StaticText(self.DirectlyPanel,
                                                     label=_('Initial Value:'))
-        directly_panel_sizer.AddWindow(directly_initialvalue_label, 1, border=5,
+        directly_panel_sizer.Add(directly_initialvalue_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.DirectlyInitialValue = wx.TextCtrl(self.DirectlyPanel,
                                                 style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.DirectlyInitialValue)
-        directly_panel_sizer.AddWindow(self.DirectlyInitialValue, 1, border=5,
+        directly_panel_sizer.Add(self.DirectlyInitialValue, 1, border=5,
                                        flag=wx.ALL)
 
         self.DirectlyPanel.SetSizer(directly_panel_sizer)
@@ -205,52 +203,52 @@
         # Panel for Subrange data types
 
         self.SubrangePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.SubrangePanel, 1)
+        typeinfos_sizer.Add(self.SubrangePanel, 1)
 
         subrange_panel_sizer = wx.GridSizer(cols=4, hgap=5, rows=3, vgap=0)
 
         subrange_basetype_label = wx.StaticText(self.SubrangePanel,
                                                 label=_('Base Type:'))
-        subrange_panel_sizer.AddWindow(subrange_basetype_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_basetype_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeBaseType = wx.ComboBox(self.SubrangePanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnSubrangeBaseTypeChanged,
                   self.SubrangeBaseType)
-        subrange_panel_sizer.AddWindow(self.SubrangeBaseType, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeBaseType, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         subrange_initialvalue_label = wx.StaticText(self.SubrangePanel,
                                                     label=_('Initial Value:'))
-        subrange_panel_sizer.AddWindow(subrange_initialvalue_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_initialvalue_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeInitialValue = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeInitialValue.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnInfosChanged)
-        subrange_panel_sizer.AddWindow(self.SubrangeInitialValue, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeInitialValue, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         subrange_minimum_label = wx.StaticText(self.SubrangePanel, label=_('Minimum:'))
-        subrange_panel_sizer.AddWindow(subrange_minimum_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_minimum_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeMinimum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeMinimum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMinimumChanged)
-        subrange_panel_sizer.AddWindow(self.SubrangeMinimum, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeMinimum, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
-        for dummy in xrange(2):
-            subrange_panel_sizer.AddWindow(wx.Size(0, 0), 1)
+        for dummy in range(2):
+            subrange_panel_sizer.Add(wx.Size(0, 0), 1)
 
         subrange_maximum_label = wx.StaticText(self.SubrangePanel,
                                                label=_('Maximum:'))
-        subrange_panel_sizer.AddWindow(subrange_maximum_label, 1, border=5,
+        subrange_panel_sizer.Add(subrange_maximum_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.SubrangeMaximum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
         self.SubrangeMaximum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMaximumChanged)
 
-        subrange_panel_sizer.AddWindow(self.SubrangeMaximum, 1, border=5,
+        subrange_panel_sizer.Add(self.SubrangeMaximum, 1, border=5,
                                        flag=wx.GROW | wx.ALL)
 
         self.SubrangePanel.SetSizer(subrange_panel_sizer)
@@ -258,35 +256,35 @@
         # Panel for Enumerated data types
 
         self.EnumeratedPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.EnumeratedPanel, 1)
+        typeinfos_sizer.Add(self.EnumeratedPanel, 1)
 
         enumerated_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         self.EnumeratedValues = CustomEditableListBox(
             self.EnumeratedPanel,
             label=_("Values:"),
-            style=(wx.gizmos.EL_ALLOW_NEW |
-                   wx.gizmos.EL_ALLOW_EDIT |
-                   wx.gizmos.EL_ALLOW_DELETE))
+            style=(wx.adv.EL_ALLOW_NEW |
+                   wx.adv.EL_ALLOW_EDIT |
+                   wx.adv.EL_ALLOW_DELETE))
         setattr(self.EnumeratedValues, "_OnLabelEndEdit", self.OnEnumeratedValueEndEdit)
         for func in ["_OnAddButton", "_OnDelButton", "_OnUpButton", "_OnDownButton"]:
             setattr(self.EnumeratedValues, func, self.OnEnumeratedValuesChanged)
-        enumerated_panel_sizer.AddWindow(self.EnumeratedValues, 1, border=5,
+        enumerated_panel_sizer.Add(self.EnumeratedValues, 1, border=5,
                                          flag=wx.GROW | wx.ALL)
 
         enumerated_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
-        enumerated_panel_sizer.AddSizer(enumerated_panel_rightsizer, 1)
+        enumerated_panel_sizer.Add(enumerated_panel_rightsizer, 1)
 
         enumerated_initialvalue_label = wx.StaticText(self.EnumeratedPanel,
                                                       label=_('Initial Value:'))
-        enumerated_panel_rightsizer.AddWindow(enumerated_initialvalue_label, 1,
+        enumerated_panel_rightsizer.Add(enumerated_initialvalue_label, 1,
                                               border=5,
                                               flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.EnumeratedInitialValue = wx.ComboBox(self.EnumeratedPanel,
                                                   style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.EnumeratedInitialValue)
-        enumerated_panel_rightsizer.AddWindow(self.EnumeratedInitialValue, 1,
+        enumerated_panel_rightsizer.Add(self.EnumeratedInitialValue, 1,
                                               border=5, flag=wx.ALL)
 
         self.EnumeratedPanel.SetSizer(enumerated_panel_sizer)
@@ -294,7 +292,7 @@
         # Panel for Array data types
 
         self.ArrayPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.ArrayPanel, 1)
+        typeinfos_sizer.Add(self.ArrayPanel, 1)
 
         array_panel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=0)
         array_panel_sizer.AddGrowableCol(0)
@@ -302,41 +300,41 @@
         array_panel_sizer.AddGrowableRow(1)
 
         array_panel_leftSizer = wx.BoxSizer(wx.HORIZONTAL)
-        array_panel_sizer.AddSizer(array_panel_leftSizer, flag=wx.GROW)
+        array_panel_sizer.Add(array_panel_leftSizer, flag=wx.GROW)
 
         array_basetype_label = wx.StaticText(self.ArrayPanel, label=_('Base Type:'))
-        array_panel_leftSizer.AddWindow(array_basetype_label, 1, border=5,
+        array_panel_leftSizer.Add(array_basetype_label, 1, border=5,
                                         flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.ArrayBaseType = wx.ComboBox(self.ArrayPanel, style=wx.CB_READONLY)
         self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.ArrayBaseType)
-        array_panel_leftSizer.AddWindow(self.ArrayBaseType, 1, border=5,
+        array_panel_leftSizer.Add(self.ArrayBaseType, 1, border=5,
                                         flag=wx.GROW | wx.ALL)
 
         array_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
-        array_panel_sizer.AddSizer(array_panel_rightsizer, flag=wx.GROW)
+        array_panel_sizer.Add(array_panel_rightsizer, flag=wx.GROW)
 
         array_initialvalue_label = wx.StaticText(self.ArrayPanel,
                                                  label=_('Initial Value:'))
-        array_panel_rightsizer.AddWindow(array_initialvalue_label, 1, border=5,
+        array_panel_rightsizer.Add(array_initialvalue_label, 1, border=5,
                                          flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)
 
         self.ArrayInitialValue = wx.TextCtrl(self.ArrayPanel,
                                              style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
         self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.ArrayInitialValue)
-        array_panel_rightsizer.AddWindow(self.ArrayInitialValue, 1, border=5,
+        array_panel_rightsizer.Add(self.ArrayInitialValue, 1, border=5,
                                          flag=wx.ALL)
 
         self.ArrayDimensions = CustomEditableListBox(
             self.ArrayPanel,
             label=_("Dimensions:"),
-            style=(wx.gizmos.EL_ALLOW_NEW |
-                   wx.gizmos.EL_ALLOW_EDIT |
-                   wx.gizmos.EL_ALLOW_DELETE))
+            style=(wx.adv.EL_ALLOW_NEW |
+                   wx.adv.EL_ALLOW_EDIT |
+                   wx.adv.EL_ALLOW_DELETE))
         for func in ["_OnLabelEndEdit", "_OnAddButton", "_OnDelButton",
                      "_OnUpButton", "_OnDownButton"]:
             setattr(self.ArrayDimensions, func, self.OnDimensionsChanged)
-        array_panel_sizer.AddWindow(self.ArrayDimensions, 0, border=5,
+        array_panel_sizer.Add(self.ArrayDimensions, 0, border=5,
                                     flag=wx.GROW | wx.ALL)
 
         self.ArrayPanel.SetSizer(array_panel_sizer)
@@ -344,7 +342,7 @@
         # Panel for Structure data types
 
         self.StructurePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
-        typeinfos_sizer.AddWindow(self.StructurePanel, 1)
+        typeinfos_sizer.Add(self.StructurePanel, 1)
 
         structure_panel_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
         structure_panel_sizer.AddGrowableCol(0)
@@ -353,12 +351,12 @@
         structure_button_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         structure_button_sizer.AddGrowableCol(0)
         structure_button_sizer.AddGrowableRow(0)
-        structure_panel_sizer.AddSizer(structure_button_sizer, 0, border=5,
+        structure_panel_sizer.Add(structure_button_sizer, 0, border=5,
                                        flag=wx.ALL | wx.GROW)
 
         structure_elements_label = wx.StaticText(self.StructurePanel,
                                                  label=_('Elements :'))
-        structure_button_sizer.AddWindow(structure_elements_label, flag=wx.ALIGN_BOTTOM)
+        structure_button_sizer.Add(structure_elements_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("StructureAddButton", "add_element", _("Add element")),
@@ -369,17 +367,17 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            structure_button_sizer.AddWindow(button)
+            structure_button_sizer.Add(button)
 
         self.StructureElementsGrid = CustomGrid(self.StructurePanel,
                                                 size=wx.Size(0, 150), style=wx.VSCROLL)
-        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                         self.OnStructureElementsGridCellChange)
         self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                         self.OnStructureElementsGridEditorShown)
-        structure_panel_sizer.AddWindow(self.StructureElementsGrid, flag=wx.GROW)
+        structure_panel_sizer.Add(self.StructureElementsGrid, flag=wx.GROW)
 
         self.StructurePanel.SetSizer(structure_panel_sizer)
 
@@ -458,9 +456,6 @@
         self.RefreshHighlightsTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
 
-    def __del__(self):
-        self.RefreshHighlightsTimer.Stop()
-
     def GetBufferState(self):
         return self.Controler.GetBufferState()
 
@@ -512,7 +507,7 @@
                 self.EnumeratedInitialValue.SetStringSelection(type_infos["initial"])
             elif type_infos["type"] == "Array":
                 self.ArrayBaseType.SetStringSelection(type_infos["base_type"])
-                self.ArrayDimensions.SetStrings(map("..".join, type_infos["dimensions"]))
+                self.ArrayDimensions.SetStrings(list(map("..".join, type_infos["dimensions"])))
                 self.ArrayInitialValue.SetValue(type_infos["initial"])
             elif type_infos["type"] == "Structure":
                 self.StructureElementsTable.SetData(type_infos["elements"])
@@ -647,7 +642,7 @@
             self.Bind(wx.EVT_MENU, self.ElementArrayTypeFunction, new_entry)
 
             rect = self.StructureElementsGrid.BlockToDeviceRect((row, col), (row, col))
-            self.StructureElementsGrid.PopupMenuXY(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
+            self.StructureElementsGrid.PopupMenu(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
             type_menu.Destroy()
             event.Veto()
         else:
@@ -778,7 +773,7 @@
             self.Highlights = []
         else:
             self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type]
-        for control in self.HighlightControls.itervalues():
+        for control in self.HighlightControls.values():
             if isinstance(control, (wx.ComboBox, wx.SpinCtrl)):
                 control.SetBackgroundColour(wx.NullColour)
                 control.SetForegroundColour(wx.NullColour)
@@ -786,9 +781,9 @@
                 value = control.GetValueStr() if isinstance(control, CustomIntCtrl) else \
                         control.GetValue()
                 control.SetStyle(0, len(value), wx.TextAttr(wx.NullColour))
-            elif isinstance(control, wx.gizmos.EditableListBox):
+            elif isinstance(control, wx.adv.EditableListBox):
                 listctrl = control.GetListCtrl()
-                for i in xrange(listctrl.GetItemCount()):
+                for i in range(listctrl.GetItemCount()):
                     listctrl.SetItemBackgroundColour(i, wx.NullColour)
                     listctrl.SetItemTextColour(i, wx.NullColour)
         self.StructureElementsTable.ClearHighlights(highlight_type)
@@ -811,7 +806,7 @@
                         control.SetForegroundColour(highlight_type[1])
                     elif isinstance(control, wx.TextCtrl):
                         control.SetStyle(start[1], end[1] + 1, wx.TextAttr(highlight_type[1], highlight_type[0]))
-                    elif isinstance(control, wx.gizmos.EditableListBox):
+                    elif isinstance(control, wx.adv.EditableListBox):
                         listctrl = control.GetListCtrl()
                         listctrl.SetItemBackgroundColour(infos[1], highlight_type[0])
                         listctrl.SetItemTextColour(infos[1], highlight_type[1])
--- a/editors/DebugViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/DebugViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from threading import Lock, Timer
 from time import time as gettime
 
@@ -79,24 +79,6 @@
         # Set DataProducer and subscribe tick if needed
         self.SetDataProducer(producer)
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        # Unsubscribe all data consumers
-        self.UnsubscribeAllDataConsumers()
-
-        # Delete reference to DataProducer
-        self.DataProducer = None
-
-        # Stop last refresh timer
-        if self.LastRefreshTimer is not None:
-            self.LastRefreshTimer.cancel()
-
-        # Release Common debug lock if DebugViewer has acquired it
-        if self.HasAcquiredLock:
-            DEBUG_REFRESH_LOCK.release()
-
     def SetDataProducer(self, producer):
         """
         Set Data Producer
@@ -130,7 +112,7 @@
         @param inhibit: Inhibit flag
         """
         # Inhibit every data consumers in list
-        for consumer, _iec_path in self.DataConsumers.iteritems():
+        for consumer, _iec_path in self.DataConsumers.items():
             consumer.Inhibit(inhibit)
 
         # Save inhibit flag
@@ -192,7 +174,7 @@
                 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
 
             # Unsubscribe all data consumers in list
-            for consumer, iec_path in self.DataConsumers.iteritems():
+            for consumer, iec_path in self.DataConsumers.items():
                 self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
 
         self.DataConsumers = {}
--- a/editors/EditorPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/EditorPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from controls import VariablePanel
--- a/editors/FileManagementPanel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/FileManagementPanel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 import shutil
 
@@ -43,14 +43,14 @@
         main_sizer = wx.BoxSizer(wx.HORIZONTAL)
 
         left_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(left_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(left_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
 
         managed_dir_label = wx.StaticText(self.Editor, label=_(self.TagName) + ":")
-        left_sizer.AddWindow(managed_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
+        left_sizer.Add(managed_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
 
         FILTER = _("All files (*.*)|*.*|CSV files (*.csv)|*.csv")
         self.ManagedDir = FolderTree(self.Editor, self.Folder, FILTER)
-        left_sizer.AddWindow(self.ManagedDir, 1, flag=wx.GROW)
+        left_sizer.Add(self.ManagedDir, 1, flag=wx.GROW)
 
         managed_treectrl = self.ManagedDir.GetTreeCtrl()
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, managed_treectrl)
@@ -58,7 +58,7 @@
             self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnTreeBeginDrag, managed_treectrl)
 
         button_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(button_sizer, border=5,
+        main_sizer.Add(button_sizer, border=5,
                             flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL)
 
         for idx, (name, bitmap, help) in enumerate([
@@ -70,26 +70,26 @@
                 self.Editor,
                 bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             if idx > 0:
                 flag = wx.TOP
             else:
                 flag = 0
             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
-            button_sizer.AddWindow(button, border=20, flag=flag)
+            button_sizer.Add(button, border=20, flag=flag)
 
         right_sizer = wx.BoxSizer(wx.VERTICAL)
-        main_sizer.AddSizer(right_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
+        main_sizer.Add(right_sizer, 1, border=5, flag=wx.GROW | wx.ALL)
 
         if wx.Platform == '__WXMSW__':
             system_dir_label = wx.StaticText(self.Editor, label=_("My Computer:"))
         else:
             system_dir_label = wx.StaticText(self.Editor, label=_("Home Directory:"))
-        right_sizer.AddWindow(system_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
+        right_sizer.Add(system_dir_label, border=5, flag=wx.GROW | wx.BOTTOM)
 
         self.SystemDir = FolderTree(self.Editor, self.HomeDirectory, FILTER, False)
-        right_sizer.AddWindow(self.SystemDir, 1, flag=wx.GROW)
+        right_sizer.Add(self.SystemDir, 1, flag=wx.GROW)
 
         system_treectrl = self.SystemDir.GetTreeCtrl()
         self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeItemChanged, system_treectrl)
@@ -114,9 +114,6 @@
 
         self.SetIcon(GetBitmap("FOLDER"))
 
-    def __del__(self):
-        self.Controler.OnCloseEditor(self)
-
     def GetTitle(self):
         return _(self.TagName)
 
--- a/editors/IECCodeViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/IECCodeViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,18 +23,13 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from editors.TextViewer import TextViewer
 from plcopen.plcopen import TestTextElement
 
 
 class IECCodeViewer(TextViewer):
 
-    def __del__(self):
-        TextViewer.__del__(self)
-        if getattr(self, "_OnClose"):
-            self._OnClose(self)
-
     def Paste(self):
         if self.Controler is not None:
             TextViewer.Paste(self)
--- a/editors/LDViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/LDViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,12 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
-from future.builtins import round
-
 import wx
-from six.moves import xrange
 
 from editors.Viewer import *
 
@@ -69,7 +64,7 @@
         elif element_tree[element]:
             element_tree[element]["parents"].append("start")
     remove_stops = {"start": [], "stop": []}
-    for element, values in element_tree.items():
+    for element, values in list(element_tree.items()):
         if "stop" in values["children"]:
             removed = []
             for child in values["children"]:
@@ -88,7 +83,7 @@
         element_tree[element]["parents"].remove("start")
     for element in remove_stops["stop"]:
         element_tree[element]["children"].remove("stop")
-    for element, values in element_tree.items():
+    for element, values in list(element_tree.items()):
         if values and "stop" in values["children"]:
             CalcWeight(element, element_tree)
             if values["weight"]:
@@ -301,7 +296,7 @@
             return Viewer.SearchElements(self, bbox)
 
         elements = []
-        for element in self.Blocks.values() + self.Comments.values():
+        for element in list(self.Blocks.values()) + list(self.Comments.values()):
             if element.IsInSelection(bbox):
                 elements.append(element)
         return elements
@@ -1190,7 +1185,7 @@
 
     def RefreshRungs(self, movey, fromidx):
         if movey != 0:
-            for i in xrange(fromidx, len(self.Rungs)):
+            for i in range(fromidx, len(self.Rungs)):
                 self.RungComments[i].Move(0, movey)
                 self.RungComments[i].RefreshModel()
                 self.Rungs[i].Move(0, movey)
--- a/editors/ProjectNodeEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/ProjectNodeEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from controls import ProjectPropertiesPanel, VariablePanel
@@ -60,7 +60,7 @@
         ConfTreeNodeEditor.__init__(self, parent, controler, window, tagname)
 
         buttons_sizer = self.GenerateMethodButtonSizer()
-        self.MainSizer.InsertSizer(0, buttons_sizer, 0, border=5, flag=wx.ALL)
+        self.MainSizer.Insert(0, buttons_sizer, 0, border=5, flag=wx.ALL)
         self.MainSizer.Layout()
 
         self.VariableEditor = self.VariableEditorPanel
--- a/editors/ResourceEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/ResourceEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,11 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import wx
 import wx.lib.buttons
 import wx.grid
-from six.moves import xrange
 
 from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD, ERROR_HIGHLIGHT
 from controls import CustomGrid, CustomTable, DurationCellEditor
@@ -76,10 +74,6 @@
     return [_("Interrupt"), _("Cyclic")]
 
 
-def SingleCellEditor(*x):
-    return wx.grid.GridCellChoiceEditor()
-
-
 def CheckSingle(single, varlist):
     return single in varlist
 
@@ -162,25 +156,21 @@
                     if interval != "" and IEC_TIME_MODEL.match(interval.upper()) is None:
                         error = True
                 elif colname == "Single":
-                    editor = SingleCellEditor(self, colname)
-                    editor.SetParameters(self.Parent.VariableList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.VariableList)
                     if self.GetValueByName(row, "Triggering") != "Interrupt":
                         grid.SetReadOnly(row, col, True)
                     single = self.GetValueByName(row, colname)
                     if single != "" and not CheckSingle(single, self.Parent.VariableList):
                         error = True
                 elif colname == "Triggering":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(",".join(map(_, GetTaskTriggeringOptions())))
+                    editor = wx.grid.GridCellChoiceEditor(list(map(_, GetTaskTriggeringOptions())))
                 elif colname == "Type":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TypeList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TypeList)
                 elif colname == "Priority":
                     editor = wx.grid.GridCellNumberEditor()
                     editor.SetParameters("0,65535")
                 elif colname == "Task":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters(self.Parent.TaskList)
+                    editor = wx.grid.GridCellChoiceEditor(self.Parent.TaskList)
 
                 grid.SetCellEditor(row, col, editor)
                 grid.SetCellRenderer(row, col, renderer)
@@ -206,8 +196,8 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            for _row, row_highlights in self.Highlights.iteritems():
-                row_items = row_highlights.items()
+            for _row, row_highlights in self.Highlights.items():
+                row_items = list(row_highlights.items())
                 for col, col_highlights in row_items:
                     if highlight_type in col_highlights:
                         col_highlights.remove(highlight_type)
@@ -230,16 +220,16 @@
         tasks_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         tasks_sizer.AddGrowableCol(0)
         tasks_sizer.AddGrowableRow(1)
-        main_sizer.AddSizer(tasks_sizer, border=5,
+        main_sizer.Add(tasks_sizer, border=5,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         tasks_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         tasks_buttons_sizer.AddGrowableCol(0)
         tasks_buttons_sizer.AddGrowableRow(0)
-        tasks_sizer.AddSizer(tasks_buttons_sizer, flag=wx.GROW)
-
-        tasks_label = wx.StaticText(self.Editor, label=_(u'Tasks:'))
-        tasks_buttons_sizer.AddWindow(tasks_label, flag=wx.ALIGN_BOTTOM)
+        tasks_sizer.Add(tasks_buttons_sizer, flag=wx.GROW)
+
+        tasks_label = wx.StaticText(self.Editor, label=_('Tasks:'))
+        tasks_buttons_sizer.Add(tasks_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddTaskButton", "add_element", _("Add task")),
@@ -250,27 +240,27 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            tasks_buttons_sizer.AddWindow(button)
+            tasks_buttons_sizer.Add(button)
 
         self.TasksGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
-        self.TasksGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnTasksGridCellChange)
-        tasks_sizer.AddWindow(self.TasksGrid, flag=wx.GROW)
+        self.TasksGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnTasksGridCellChange)
+        tasks_sizer.Add(self.TasksGrid, flag=wx.GROW)
 
         instances_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
         instances_sizer.AddGrowableCol(0)
         instances_sizer.AddGrowableRow(1)
-        main_sizer.AddSizer(instances_sizer, border=5,
+        main_sizer.Add(instances_sizer, border=5,
                             flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         instances_buttons_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
         instances_buttons_sizer.AddGrowableCol(0)
         instances_buttons_sizer.AddGrowableRow(0)
-        instances_sizer.AddSizer(instances_buttons_sizer, flag=wx.GROW)
-
-        instances_label = wx.StaticText(self.Editor, label=_(u'Instances:'))
-        instances_buttons_sizer.AddWindow(instances_label, flag=wx.ALIGN_BOTTOM)
+        instances_sizer.Add(instances_buttons_sizer, flag=wx.GROW)
+
+        instances_label = wx.StaticText(self.Editor, label=_('Instances:'))
+        instances_buttons_sizer.Add(instances_label, flag=wx.ALIGN_BOTTOM)
 
         for name, bitmap, help in [
                 ("AddInstanceButton", "add_element", _("Add instance")),
@@ -280,13 +270,13 @@
             button = wx.lib.buttons.GenBitmapButton(
                 self.Editor, bitmap=GetBitmap(bitmap),
                 size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            instances_buttons_sizer.AddWindow(button)
+            instances_buttons_sizer.Add(button)
 
         self.InstancesGrid = CustomGrid(self.Editor, style=wx.VSCROLL)
-        self.InstancesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnInstancesGridCellChange)
-        instances_sizer.AddWindow(self.InstancesGrid, flag=wx.GROW)
+        self.InstancesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED, self.OnInstancesGridCellChange)
+        instances_sizer.Add(self.InstancesGrid, flag=wx.GROW)
 
         self.Editor.SetSizer(main_sizer)
 
@@ -401,24 +391,21 @@
 
         self.TasksGrid.SetFocus()
 
-    def __del__(self):
-        self.RefreshHighlightsTimer.Stop()
-
     def RefreshTypeList(self):
-        self.TypeList = ""
+        self.TypeList = []
         blocktypes = self.Controler.GetBlockResource()
         for blocktype in blocktypes:
-            self.TypeList += ",%s" % blocktype
+            self.TypeList.append(blocktype)
 
     def RefreshTaskList(self):
-        self.TaskList = ""
-        for row in xrange(self.TasksTable.GetNumberRows()):
-            self.TaskList += ",%s" % self.TasksTable.GetValueByName(row, "Name")
+        self.TaskList = []
+        for row in range(self.TasksTable.GetNumberRows()):
+            self.TaskList.append(self.TasksTable.GetValueByName(row, "Name"))
 
     def RefreshVariableList(self):
-        self.VariableList = ""
+        self.VariableList = []
         for variable in self.Controler.GetEditedResourceVariables(self.TagName):
-            self.VariableList += ",%s" % variable
+            self.VariableList.append(variable)
 
     def RefreshModel(self):
         self.Controler.SetEditedResourceInfos(self.TagName, self.TasksTable.GetData(), self.InstancesTable.GetData())
@@ -481,15 +468,15 @@
                 wx.CallAfter(self.ShowErrorMessage, message)
                 return
 
-            tasklist = [name for name in self.TaskList.split(",") if name != ""]
-            for i in xrange(self.TasksTable.GetNumberRows()):
+            tasklist = [name for name in self.TaskList if name != ""]
+            for i in range(self.TasksTable.GetNumberRows()):
                 task = self.TasksTable.GetValueByName(i, "Name")
                 if task in tasklist:
                     tasklist.remove(task)
             if len(tasklist) > 0:
                 old_name = tasklist[0].upper()
                 new_name = self.TasksTable.GetValue(row, col)
-                for i in xrange(self.InstancesTable.GetNumberRows()):
+                for i in range(self.InstancesTable.GetNumberRows()):
                     name = self.InstancesTable.GetValueByName(i, "Task").upper()
                     if old_name == name:
                         self.InstancesTable.SetValueByName(i, "Task", new_name)
--- a/editors/SFCViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/SFCViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 
 import wx
 
--- a/editors/TextViewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/TextViewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,14 +23,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 import re
 from functools import reduce
 
 import wx
 import wx.stc
-from six.moves import xrange
 
 from graphics.GraphicCommons import ERROR_HIGHLIGHT, SEARCH_RESULT_HIGHLIGHT, REFRESH_HIGHLIGHT_PERIOD
 from plcopen.structures import ST_BLOCK_START_KEYWORDS, IEC_BLOCK_START_KEYWORDS, LOCATIONDATATYPES
@@ -43,17 +40,17 @@
 
 
 NEWLINE = "\n"
-NUMBERS = [str(i) for i in xrange(10)]
+NUMBERS = [str(i) for i in range(10)]
 LETTERS = ['_']
-for i in xrange(26):
+for i in range(26):
     LETTERS.append(chr(ord('a') + i))
     LETTERS.append(chr(ord('A') + i))
 
 [STC_PLC_WORD, STC_PLC_COMMENT, STC_PLC_NUMBER, STC_PLC_STRING,
  STC_PLC_VARIABLE, STC_PLC_PARAMETER, STC_PLC_FUNCTION, STC_PLC_JUMP,
  STC_PLC_ERROR, STC_PLC_SEARCH_RESULT,
- STC_PLC_EMPTY] = range(11)
-[SPACE, WORD, NUMBER, STRING, WSTRING, COMMENT, PRAGMA, DPRAGMA] = range(8)
+ STC_PLC_EMPTY] = list(range(11))
+[SPACE, WORD, NUMBER, STRING, WSTRING, COMMENT, PRAGMA, DPRAGMA] = list(range(8))
 
 re_texts = {}
 re_texts["letter"] = "[A-Za-z]"
@@ -70,7 +67,7 @@
 
 
 def LineStartswith(line, symbols):
-    return reduce(lambda x, y: x or y, map(line.startswith, symbols), False)
+    return reduce(lambda x, y: x or y, list(map(line.startswith, symbols)), False)
 
 
 class TextViewer(EditorPanel):
@@ -166,9 +163,6 @@
         self.RefreshHighlightsTimer = wx.Timer(self, -1)
         self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
 
-    def __del__(self):
-        self.RefreshHighlightsTimer.Stop()
-
     def GetTitle(self):
         if self.Debug or self.TagName == "":
             if len(self.InstancePath) > 15:
@@ -197,12 +191,20 @@
     def Colourise(self, start, end):
         self.Editor.Colourise(start, end)
 
-    def StartStyling(self, pos, mask):
-        self.Editor.StartStyling(pos, mask)
+    def StartStyling(self, *a, **k):
+        self.Editor.StartStyling(*a, **k)
+
+    INDIC0 = 0
+    INDIC1 = 1
+    INDIC2 = 2
 
     def SetStyling(self, length, style):
         self.Editor.SetStyling(length, style)
 
+    def SetIndicatorCurrentFillRange(self, start, length, indic):
+        self.Editor.SetIndicatorCurrent(indic)
+        self.Editor.IndicatorFillRange(start, length)
+
     def GetCurrentPos(self):
         return self.Editor.GetCurrentPos()
 
@@ -488,7 +490,7 @@
             for category in self.Controler.GetBlockTypes(self.TagName, self.Debug):
                 for blocktype in category["list"]:
                     blockname = blocktype["name"].upper()
-                    if blocktype["type"] == "function" and blockname not in self.Keywords and blockname not in self.Variables.keys():
+                    if blocktype["type"] == "function" and blockname not in self.Keywords and blockname not in list(self.Variables.keys()):
                         interface = dict([(name, {}) for name, _type, _modifier in blocktype["inputs"] + blocktype["outputs"] if name != ''])
                         for param in ["EN", "ENO"]:
                             if param not in interface:
@@ -562,7 +564,7 @@
             start_pos = last_styled_pos = self.Editor.GetLineEndPosition(line_number - 1) + 1
         self.RefreshLineFolding(line_number)
         end_pos = event.GetPosition()
-        self.StartStyling(start_pos, 0xff)
+        self.StartStyling(start_pos)
 
         current_context = self.Variables
         current_call = None
@@ -597,9 +599,8 @@
                     else:
                         self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                         if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
-                            self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
-                            self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
-                            self.StartStyling(current_pos, 0xff)
+                            self.SetIndicatorCurrentFillRange(last_styled_pos, current_pos - last_styled_pos, self.INDIC0)
+                            self.StartStyling(current_pos)
                 else:
                     self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                 last_styled_pos = current_pos
@@ -700,9 +701,8 @@
                     else:
                         self.SetStyling(current_pos - last_styled_pos, STC_PLC_EMPTY)
                         if word not in ["]", ")"] and (self.GetCurrentPos() < last_styled_pos or self.GetCurrentPos() > current_pos):
-                            self.StartStyling(last_styled_pos, wx.stc.STC_INDICS_MASK)
-                            self.SetStyling(current_pos - last_styled_pos, wx.stc.STC_INDIC0_MASK)
-                            self.StartStyling(current_pos, 0xff)
+                            self.SetIndicatorCurrentFillRange(last_styled_pos, current_pos - last_styled_pos, self.INDIC0)
+                            self.StartStyling(current_pos)
                     if char == '.':
                         if word != "]":
                             if current_context is not None:
@@ -893,9 +893,9 @@
                         elif words[0].upper() in ["JMP", "JMPC", "JMPNC"]:
                             kw = self.Jumps
                         else:
-                            kw = self.Variables.keys()
-                else:
-                    kw = self.Keywords + self.Variables.keys() + self.Functions.keys()
+                            kw = list(self.Variables.keys())
+                else:
+                    kw = self.Keywords + list(self.Variables.keys()) + list(self.Functions.keys())
                 if len(kw) > 0:
                     if len(words[-1]) > 0:
                         kw = [keyword for keyword in kw if keyword.startswith(words[-1])]
@@ -975,8 +975,8 @@
             else:
                 highlight_end_pos = self.Editor.GetLineEndPosition(end[0] - 1) + end[1] - indent + 2
             if highlight_start_pos < end_pos and highlight_end_pos > start_pos:
-                self.StartStyling(highlight_start_pos, 0xff)
+                self.StartStyling(highlight_start_pos)
                 self.SetStyling(highlight_end_pos - highlight_start_pos, highlight_type)
-                self.StartStyling(highlight_start_pos, 0x00)
+                self.StartStyling(highlight_end_pos, 0x00)
                 until_end = max(0, len(self.Editor.GetText()) - highlight_end_pos)
                 self.SetStyling(until_end, wx.stc.STC_STYLE_DEFAULT)
--- a/editors/Viewer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/editors/Viewer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,15 +23,13 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+from functools import cmp_to_key
+from operator import eq
 import math
 from time import time as gettime
 from threading import Lock
-from future.builtins import round
 
 import wx
-from six.moves import xrange
 
 from plcopen.structures import *
 from plcopen.types_enums import ComputePouName
@@ -60,11 +58,11 @@
     global CURSORS
     if CURSORS is None:
         CURSORS = [wx.NullCursor,
-                   wx.StockCursor(wx.CURSOR_HAND),
-                   wx.StockCursor(wx.CURSOR_SIZENWSE),
-                   wx.StockCursor(wx.CURSOR_SIZENESW),
-                   wx.StockCursor(wx.CURSOR_SIZEWE),
-                   wx.StockCursor(wx.CURSOR_SIZENS)]
+                   wx.Cursor(wx.CURSOR_HAND),
+                   wx.Cursor(wx.CURSOR_SIZENWSE),
+                   wx.Cursor(wx.CURSOR_SIZENESW),
+                   wx.Cursor(wx.CURSOR_SIZEWE),
+                   wx.Cursor(wx.CURSOR_SIZENS)]
 
 
 if wx.Platform == '__WXMSW__':
@@ -88,8 +86,10 @@
     MAX_ZOOMIN = 4
 else:
     MAX_ZOOMIN = 7
-ZOOM_FACTORS = [math.sqrt(2) ** x for x in xrange(-6, MAX_ZOOMIN)]
-
+ZOOM_FACTORS = [math.sqrt(2) ** x for x in range(-6, MAX_ZOOMIN)]
+
+
+WX_NO_LOGICAL = "gtk3" in wx.PlatformInfo
 
 def GetVariableCreationFunction(variable_type):
     def variableCreationFunction(viewer, id, specific_values):
@@ -225,9 +225,9 @@
     x1, y1 = block_infos1[0].GetPosition()
     x2, y2 = block_infos2[0].GetPosition()
     if y1 == y2:
-        return cmp(x1, x2)
+        return eq(x1, x2)
     else:
-        return cmp(y1, y2)
+        return eq(y1, y2)
 
 # -------------------------------------------------------------------------------
 #                       Graphic elements Viewer base class
@@ -317,7 +317,7 @@
                             selected = None
                         dialog.Destroy()
                         if selected is None:
-                            return
+                            return False
                         if selected == 0:
                             location = "%I" + location
                         elif selected == 1:
@@ -333,7 +333,7 @@
                     var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                     dlg.Destroy()
                     if var_name is None:
-                        return
+                        return False
                     elif var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
                         message = _("\"%s\" pou already exists!") % var_name
                     elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
@@ -363,7 +363,7 @@
                     var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                     dlg.Destroy()
                     if var_name is None:
-                        return
+                        return False
                     elif var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
                         message = _("\"%s\" pou already exists!") % var_name
                     elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
@@ -385,7 +385,7 @@
                 var_name = dlg.GetValue() if dlg.ShowModal() == wx.ID_OK else None
                 dlg.Destroy()
                 if var_name is None:
-                    return
+                    return False
                 elif var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
                     message = _("\"%s\" pou already exists!") % var_name
                 elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
@@ -410,7 +410,7 @@
                     if len(tree[0]) > 0:
                         menu = wx.Menu(title='')
                         self.GenerateTreeMenu(x, y, scaling, menu, "", var_class, [(values[0], values[2], tree)])
-                        self.ParentWindow.PopupMenuXY(menu)
+                        self.ParentWindow.PopupMenu(menu)
                     else:
                         self.ParentWindow.AddVariableBlock(x, y, scaling, var_class, values[0], values[2])
                 else:
@@ -419,6 +419,8 @@
                 message = _("Variable don't belong to this POU!")
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
+        return True
 
     def GenerateTreeMenu(self, x, y, scaling, menu, base_path, var_class, tree):
         for child_name, child_type, (child_tree, child_dimensions) in tree:
@@ -429,8 +431,10 @@
             if len(child_dimensions) > 0:
                 child_path += "[%s]" % ",".join([str(dimension[0]) for dimension in child_dimensions])
                 child_name += "[]"
-            item = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=child_name)
-            self.ParentWindow.Bind(wx.EVT_MENU, self.GetAddVariableBlockFunction(x, y, scaling, var_class, child_path, child_type), item)
+
+            item = self.AppendItem(menu,
+                child_name,
+                self.GetAddVariableBlockFunction(x, y, scaling, var_class, child_path, child_type))
             if len(child_tree) > 0:
                 child_menu = wx.Menu(title='')
                 self.GenerateTreeMenu(x, y, scaling, child_menu, child_path, var_class, child_tree)
@@ -475,7 +479,7 @@
         dc = self.Parent.GetLogicalDC()
         ipw, _iph = dc.GetTextExtent(self.GetInstanceName())
         vw, vh = 0, 0
-        for value in self.VALUE_TRANSLATION.itervalues():
+        for value in self.VALUE_TRANSLATION.values():
             w, h = dc.GetTextExtent(" (%s)" % value)
             vw = max(vw, w)
             vh = max(vh, h)
@@ -520,10 +524,10 @@
 
     # Add Block Pin Menu items to the given menu
     def AddBlockPinMenuItems(self, menu, connector):
-        no_modifier = self.AppendItem(menu,  _(u'No modifier'), self.OnNoModifierMenu, kind=wx.ITEM_RADIO)
-        negated = self.AppendItem(menu,  _(u'Negated'), self.OnNegatedMenu, kind=wx.ITEM_RADIO)
-        rising_edge = self.AppendItem(menu,  _(u'Rising Edge'), self.OnRisingEdgeMenu, kind=wx.ITEM_RADIO)
-        falling_edge = self.AppendItem(menu,  _(u'Falling Edge'), self.OnFallingEdgeMenu, kind=wx.ITEM_RADIO)
+        no_modifier = self.AppendItem(menu,  _('No modifier'), self.OnNoModifierMenu, kind=wx.ITEM_RADIO)
+        negated = self.AppendItem(menu,  _('Negated'), self.OnNegatedMenu, kind=wx.ITEM_RADIO)
+        rising_edge = self.AppendItem(menu,  _('Rising Edge'), self.OnRisingEdgeMenu, kind=wx.ITEM_RADIO)
+        falling_edge = self.AppendItem(menu,  _('Falling Edge'), self.OnFallingEdgeMenu, kind=wx.ITEM_RADIO)
 
         not_a_function = self.Controler.GetEditedElementType(
             self.TagName, self.Debug) != "function"
@@ -541,20 +545,20 @@
 
     # Add Alignment Menu items to the given menu
     def AddAlignmentMenuItems(self, menu):
-        self.AppendItem(menu, _(u'Left'), self.OnAlignLeftMenu)
-        self.AppendItem(menu, _(u'Center'), self.OnAlignCenterMenu)
-        self.AppendItem(menu, _(u'Right'), self.OnAlignRightMenu)
+        self.AppendItem(menu, _('Left'), self.OnAlignLeftMenu)
+        self.AppendItem(menu, _('Center'), self.OnAlignCenterMenu)
+        self.AppendItem(menu, _('Right'), self.OnAlignRightMenu)
         menu.AppendSeparator()
-        self.AppendItem(menu, _(u'Top'), self.OnAlignTopMenu)
-        self.AppendItem(menu, _(u'Middle'), self.OnAlignMiddleMenu)
-        self.AppendItem(menu, _(u'Bottom'), self.OnAlignBottomMenu)
+        self.AppendItem(menu, _('Top'), self.OnAlignTopMenu)
+        self.AppendItem(menu, _('Middle'), self.OnAlignMiddleMenu)
+        self.AppendItem(menu, _('Bottom'), self.OnAlignBottomMenu)
 
     # Add Wire Menu items to the given menu
     def AddWireMenuItems(self, menu, delete=False, replace=False):
-        self.AppendItem(menu, _(u'Add Wire Segment'), self.OnAddSegmentMenu)
-        delete_segment = self.AppendItem(menu, _(u'Delete Wire Segment'),
+        self.AppendItem(menu, _('Add Wire Segment'), self.OnAddSegmentMenu)
+        delete_segment = self.AppendItem(menu, _('Delete Wire Segment'),
                                          self.OnDeleteSegmentMenu)
-        replace_wire = self.AppendItem(menu, _(u'Replace Wire by connections'),
+        replace_wire = self.AppendItem(menu, _('Replace Wire by connections'),
                                        self.OnReplaceWireMenu)
 
         delete_segment.Enable(delete)
@@ -562,81 +566,81 @@
 
     # Add Divergence Menu items to the given menu
     def AddDivergenceMenuItems(self, menu, delete=False):
-        self.AppendItem(menu, _(u'Add Divergence Branch'),
+        self.AppendItem(menu, _('Add Divergence Branch'),
                         self.OnAddBranchMenu)
-        delete_branch = self.AppendItem(menu, _(u'Delete Divergence Branch'),
+        delete_branch = self.AppendItem(menu, _('Delete Divergence Branch'),
                                         self.OnDeleteBranchMenu)
 
         delete_branch.Enable(delete)
 
     # Add Add Menu items to the given menu
     def AddAddMenuItems(self, menu):
-        self.AppendItem(menu, _(u'Block'),
+        self.AppendItem(menu, _('Block'),
                         self.GetAddMenuCallBack(self.AddNewBlock))
-        self.AppendItem(menu, _(u'Variable'),
+        self.AppendItem(menu, _('Variable'),
                         self.GetAddMenuCallBack(self.AddNewVariable))
-        self.AppendItem(menu, _(u'Connection'),
+        self.AppendItem(menu, _('Connection'),
                         self.GetAddMenuCallBack(self.AddNewConnection))
         menu.AppendSeparator()
 
         if self.CurrentLanguage != "FBD":
-            self.AppendItem(menu, _(u'Power Rail'),
+            self.AppendItem(menu, _('Power Rail'),
                             self.GetAddMenuCallBack(self.AddNewPowerRail))
-            self.AppendItem(menu, _(u'Contact'),
+            self.AppendItem(menu, _('Contact'),
                             self.GetAddMenuCallBack(self.AddNewContact))
 
             if self.CurrentLanguage != "SFC":
-                self.AppendItem(menu, _(u'Coil'),
+                self.AppendItem(menu, _('Coil'),
                                 self.GetAddMenuCallBack(self.AddNewCoil))
 
             menu.AppendSeparator()
 
         if self.CurrentLanguage == "SFC":
-            self.AppendItem(menu, _(u'Initial Step'),
+            self.AppendItem(menu, _('Initial Step'),
                             self.GetAddMenuCallBack(self.AddNewStep, True))
-            self.AppendItem(menu, (u'Step'),
+            self.AppendItem(menu, ('Step'),
                             self.GetAddMenuCallBack(self.AddNewStep))
-            self.AppendItem(menu, (u'Transition'),
+            self.AppendItem(menu, ('Transition'),
                             self.GetAddMenuCallBack(self.AddNewTransition))
-            self.AppendItem(menu, (u'Action Block'),
+            self.AppendItem(menu, ('Action Block'),
                             self.GetAddMenuCallBack(self.AddNewActionBlock))
-            self.AppendItem(menu, (u'Divergence'),
+            self.AppendItem(menu, ('Divergence'),
                             self.GetAddMenuCallBack(self.AddNewDivergence))
-            self.AppendItem(menu, (u'Jump'),
+            self.AppendItem(menu, ('Jump'),
                             self.GetAddMenuCallBack(self.AddNewJump))
             menu.AppendSeparator()
 
-        self.AppendItem(menu, _(u'Comment'),
+        self.AppendItem(menu, _('Comment'),
                         self.GetAddMenuCallBack(self.AddNewComment))
 
     # Add Default Menu items to the given menu
     def AddDefaultMenuItems(self, menu, edit=False, block=False):
         if block:
-            edit_block = self.AppendItem(menu, _(u'Edit Block'),
+            edit_block = self.AppendItem(menu, _('Edit Block'),
                                          self.OnEditBlockMenu)
-            self.AppendItem(menu, _(u'Adjust Block Size'),
+            self.AppendItem(menu, _('Adjust Block Size'),
                             self.OnAdjustBlockSizeMenu)
-            self.AppendItem(menu, _(u'Delete'), self.OnDeleteMenu)
+            self.AppendItem(menu, _('Delete'), self.OnDeleteMenu)
 
             edit_block.Enable(edit)
 
         else:
             if self.CurrentLanguage == 'FBD':
-                self.AppendItem(menu, _(u'Clear Execution Order'),
+                self.AppendItem(menu, _('Clear Execution Order'),
                                 self.OnClearExecutionOrderMenu)
-                self.AppendItem(menu, _(u'Reset Execution Order'),
+                self.AppendItem(menu, _('Reset Execution Order'),
                                 self.OnResetExecutionOrderMenu)
                 menu.AppendSeparator()
 
             add_menu = wx.Menu(title='')
             self.AddAddMenuItems(add_menu)
-            menu.AppendMenu(-1, _(u'Add'), add_menu)
+            menu.AppendMenu(-1, _('Add'), add_menu)
 
         menu.AppendSeparator()
 
-        cut = self.AppendItem(menu, _(u'Cut'), self.GetClipboardCallBack(self.Cut))
-        copy = self.AppendItem(menu, _(u'Copy'), self.GetClipboardCallBack(self.Copy))
-        paste = self.AppendItem(menu, _(u'Paste'), self.GetAddMenuCallBack(self.Paste))
+        cut = self.AppendItem(menu, _('Cut'), self.GetClipboardCallBack(self.Cut))
+        copy = self.AppendItem(menu, _('Copy'), self.GetClipboardCallBack(self.Copy))
+        paste = self.AppendItem(menu, _('Paste'), self.GetAddMenuCallBack(self.Paste))
 
         cut.Enable(block)
         copy.Enable(block)
@@ -712,8 +716,8 @@
                 break
             faces["size"] -= 1
         self.Editor.SetFont(font)
-        self.MiniTextDC = wx.MemoryDC(wx.EmptyBitmap(1, 1))
-        self.MiniTextDC.SetFont(wx.Font(faces["size"] * 0.75, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName=faces["helv"]))
+        self.MiniTextDC = wx.MemoryDC(wx.Bitmap(1, 1))
+        self.MiniTextDC.SetFont(wx.Font(int(faces["size"] * 0.75), wx.SWISS, wx.NORMAL, wx.NORMAL, faceName=faces["helv"]))
 
         self.CurrentScale = None
         self.SetScale(ZOOM_FACTORS.index(1.0), False)
@@ -741,13 +745,6 @@
         self.Editor.Bind(wx.EVT_SIZE, self.OnMoveWindow)
         self.Editor.Bind(wx.EVT_MOUSE_EVENTS, self.OnViewerMouseEvent)
 
-    # Destructor
-    def __del__(self):
-        DebugViewer.__del__(self)
-        self.Flush()
-        self.ResetView()
-        self.RefreshHighlightsTimer.Stop()
-
     def SetCurrentCursor(self, cursor):
         if self.Mode != MODE_MOTION:
             if self.CurrentCursor != cursor:
@@ -808,8 +805,8 @@
                 pos = mouse_event.GetLogicalPosition(dc)
                 xmax = self.GetScrollRange(wx.HORIZONTAL) - self.GetScrollThumb(wx.HORIZONTAL)
                 ymax = self.GetScrollRange(wx.VERTICAL) - self.GetScrollThumb(wx.VERTICAL)
-                scrollx = max(0, round(pos.x * self.ViewScale[0] - mouse_pos.x) / SCROLLBAR_UNIT)
-                scrolly = max(0, round(pos.y * self.ViewScale[1] - mouse_pos.y) / SCROLLBAR_UNIT)
+                scrollx = max(0, round(pos.x * self.ViewScale[0] - mouse_pos.x) // SCROLLBAR_UNIT)
+                scrolly = max(0, round(pos.y * self.ViewScale[1] - mouse_pos.y) // SCROLLBAR_UNIT)
                 if scrollx > xmax or scrolly > ymax:
                     self.RefreshScrollBars(max(0, scrollx - xmax), max(0, scrolly - ymax))
                     self.Scroll(scrollx, scrolly)
@@ -825,15 +822,16 @@
     def GetViewScale(self):
         return self.ViewScale
 
-    def GetLogicalDC(self, buffered=False):
-        if buffered:
-            bitmap = wx.EmptyBitmap(*self.Editor.GetClientSize())
-            dc = wx.MemoryDC(bitmap)
-        else:
-            dc = wx.ClientDC(self.Editor)
+    def PrepareDC(self, dc):
         dc.SetFont(self.GetFont())
         self.Editor.DoPrepareDC(dc)
         dc.SetUserScale(self.ViewScale[0], self.ViewScale[1])
+        if WX_NO_LOGICAL:
+            dc.SetLogicalFunction = lambda *a,**k: None
+
+    def GetLogicalDC(self):
+        dc = wx.ClientDC(self.Editor)
+        self.PrepareDC(dc)
         return dc
 
     def RefreshRect(self, rect, eraseBackground=True):
@@ -913,20 +911,22 @@
         self.Comments.pop(comment.GetId())
 
     def GetElements(self, sort_blocks=False, sort_wires=False, sort_comments=False):
-        blocks = self.Blocks.values()
-        wires = self.Wires.keys()
-        comments = self.Comments.values()
+        blocks = list(self.Blocks.values())
+        wires = list(self.Wires.keys())
+        comments = list(self.Comments.values())
         if sort_blocks:
-            blocks.sort(lambda x, y: cmp(x.GetId(), y.GetId()))
+            blocks.sort(key=cmp_to_key(lambda x, y: eq(x.GetId(), y.GetId())))
         if sort_wires:
-            wires.sort(lambda x, y: cmp(self.Wires[x], self.Wires[y]))
+            wires.sort(key=cmp_to_key(lambda x, y: eq(self.Wires[x],
+                                                      self.Wires[y])))
         if sort_comments:
-            comments.sort(lambda x, y: cmp(x.GetId(), y.GetId()))
+            comments.sort(key=cmp_to_key(lambda x, y: eq(x.GetId(),
+                                                         y.GetId())))
         return blocks + wires + comments
 
     def GetContinuationByName(self, name):
         blocks = []
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if isinstance(block, FBD_Connector) and\
                block.GetType() == CONTINUATION and\
                block.GetName() == name:
@@ -934,7 +934,7 @@
         return blocks
 
     def GetConnectorByName(self, name):
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if isinstance(block, FBD_Connector) and\
                block.GetType() == CONNECTOR and\
                block.GetName() == name:
@@ -950,11 +950,11 @@
         width, height = self.Editor.GetClientSize()
         screen = wx.Rect(int(x / self.ViewScale[0]), int(y / self.ViewScale[1]),
                          int(width / self.ViewScale[0]), int(height / self.ViewScale[1]))
-        for comment in self.Comments.itervalues():
+        for comment in self.Comments.values():
             comment.TestVisible(screen)
-        for wire in self.Wires.iterkeys():
+        for wire in self.Wires.keys():
             wire.TestVisible(screen)
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             block.TestVisible(screen)
 
     def GetElementIECPath(self, element):
@@ -1035,12 +1035,12 @@
 
     def Flush(self):
         self.UnsubscribeAllDataConsumers(tick=False)
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             block.Flush()
 
     # Remove all elements
     def CleanView(self):
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             block.Clean()
         self.ResetView()
 
@@ -1058,7 +1058,7 @@
             self.SelectedElement.SetSelected(False)
             self.SelectedElement = None
         if self.Mode == MODE_MOTION:
-            wx.CallAfter(self.Editor.SetCursor, wx.StockCursor(wx.CURSOR_HAND))
+            wx.CallAfter(self.Editor.SetCursor, wx.Cursor(wx.CURSOR_HAND))
             self.SavedMode = True
 
     # Return current drawing mode
@@ -1116,13 +1116,13 @@
             if self.DrawGrid:
                 width = max(2, int(scaling[0] * self.ViewScale[0]))
                 height = max(2, int(scaling[1] * self.ViewScale[1]))
-                bitmap = wx.EmptyBitmap(width, height)
+                bitmap = wx.Bitmap(width, height)
                 dc = wx.MemoryDC(bitmap)
                 dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
                 dc.Clear()
                 dc.SetPen(MiterPen(wx.Colour(180, 180, 180)))
                 dc.DrawPoint(0, 0)
-                self.GridBrush = wx.BrushFromBitmap(bitmap)
+                self.GridBrush = wx.Brush(bitmap)
             else:
                 self.GridBrush = wx.TRANSPARENT_BRUSH
         else:
@@ -1130,7 +1130,7 @@
             self.GridBrush = wx.TRANSPARENT_BRUSH
         page_size = properties["pageSize"]
         if page_size != (0, 0):
-            self.PageSize = map(int, page_size)
+            self.PageSize = list(map(int, page_size))
             self.PagePen = MiterPen(wx.Colour(180, 180, 180))
         else:
             self.PageSize = None
@@ -1212,7 +1212,7 @@
                     wire.SetModifier(self.GetWireModifier(wire))
 
         if self.Debug:
-            for block in self.Blocks.itervalues():
+            for block in self.Blocks.values():
                 block.SpreadCurrent()
                 if isinstance(block, FBD_Block):
                     for output_connector in block.GetConnectors()["outputs"]:
@@ -1373,7 +1373,7 @@
         element.SetPosition(instance.x, instance.y)
         element.SetSize(instance.width, instance.height)
         for i, output_connector in enumerate(instance.outputs):
-            connector_pos = wx.Point(*output_connector.position)
+            connector_pos = wx.Point(*map(int, output_connector.position))
             if isinstance(element, FBD_Block):
                 connector = element.GetConnector(connector_pos,
                                                  output_name=output_connector.name)
@@ -1389,7 +1389,7 @@
                 if connectors["outputs"].index(connector) == i:
                     connector.SetPosition(connector_pos)
         for i, input_connector in enumerate(instance.inputs):
-            connector_pos = wx.Point(*input_connector.position)
+            connector_pos = wx.Point(*map(int,input_connector.position))
             if isinstance(element, FBD_Block):
                 connector = element.GetConnector(connector_pos,
                                                  input_name=input_connector.name)
@@ -1430,7 +1430,7 @@
 
             points = link.points
             end_connector = connected.GetConnector(
-                wx.Point(points[-1].x, points[-1].y)
+                wx.Point(int(points[-1].x), int(points[-1].y))
                 if len(points) > 0 else wx.Point(0, 0),
                 link.formalParameter)
             if end_connector is not None:
@@ -1472,7 +1472,7 @@
     def FindBlock(self, event):
         dc = self.GetLogicalDC()
         pos = event.GetLogicalPosition(dc)
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if block.HitTest(pos) or block.TestHandle(event) != (0, 0):
                 return block
         return None
@@ -1503,7 +1503,7 @@
     def FindBlockConnectorWithError(self, pos, direction=None, exclude=None):
         error = False
         startblock = None
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             connector = block.TestConnector(pos, direction, exclude)
             if connector:
                 if self.IsWire(self.SelectedElement):
@@ -1572,10 +1572,15 @@
         iec_path = self.GetElementIECPath(self.SelectedElement)
         if iec_path is not None:
             menu = wx.Menu(title='')
-            item = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Force value"))
-            self.Bind(wx.EVT_MENU, self.GetForceVariableMenuFunction(iec_path.upper(), self.SelectedElement), item)
-            ritem = menu.Append(wx.ID_ANY, help='', kind=wx.ITEM_NORMAL, text=_("Release value"))
-            self.Bind(wx.EVT_MENU, self.GetReleaseVariableMenuFunction(iec_path.upper()), ritem)
+            item = self.AppendItem(menu,
+                _("Force value"),
+                self.GetForceVariableMenuFunction(
+                    iec_path.upper(),
+                    self.SelectedElement))
+
+            ritem = self.AppendItem(menu,
+                _("Release value"),
+                self.GetReleaseVariableMenuFunction(iec_path.upper()))
             if self.SelectedElement.IsForced():
                 ritem.Enable(True)
             else:
@@ -1658,7 +1663,7 @@
         menu = wx.Menu(title='')
         align_menu = wx.Menu(title='')
         self.AddAlignmentMenuItems(align_menu)
-        menu.AppendMenu(-1, _(u'Alignment'), align_menu)
+        menu.AppendMenu(-1, _('Alignment'), align_menu)
         menu.AppendSeparator()
         self.AddDefaultMenuItems(menu, block=True)
         self.Editor.PopupMenu(menu)
@@ -1903,9 +1908,9 @@
 
     def OnViewerMouseEvent(self, event):
         self.ResetBuffer()
-        if event.Leaving() and self.ToolTipElement is not None:
+        if (event.Leaving() or event.RightUp()) and self.ToolTipElement is not None:
             self.ToolTipElement.DestroyToolTip()
-        elif (not event.Entering() and
+        elif (not event.Entering() and not event.RightUp() and
               gettime() - self.LastToolTipCheckTime > REFRESH_PERIOD):
             self.LastToolTipCheckTime = gettime()
             element = None
@@ -1987,10 +1992,10 @@
                         NORTH: [NORTH, SOUTH],
                         SOUTH: [SOUTH, NORTH]}[connector.GetDirection()]
                     wire = Wire(self,
-                                *map(list, zip(
+                                *list(map(list, list(zip(
                                     [wx.Point(pos.x, pos.y),
                                      wx.Point(scaled_pos.x, scaled_pos.y)],
-                                    directions)))
+                                    directions)))))
                     wire.oldPos = scaled_pos
                     wire.Handle = (HANDLE_POINT, 0)
                     wire.ProcessDragging(0, 0, event, None)
@@ -2267,13 +2272,12 @@
             if not event.Dragging() and (gettime() - self.LastHighlightCheckTime) > REFRESH_PERIOD:
                 self.LastHighlightCheckTime = gettime()
                 highlighted = self.FindElement(event, connectors=False)
-                if self.HighlightedElement is not None and self.HighlightedElement != highlighted:
-                    self.HighlightedElement.SetHighlighted(False)
-                    self.HighlightedElement = None
-                if highlighted is not None:
-                    if not self.Debug and isinstance(highlighted, (Wire, Graphic_Group)):
-                        highlighted.HighlightPoint(pos)
-                    if self.HighlightedElement != highlighted:
+                if self.HighlightedElement != highlighted:
+                    if self.HighlightedElement is not None:
+                        self.HighlightedElement.SetHighlighted(False)
+                    if highlighted is not None:
+                        if not self.Debug and isinstance(highlighted, (Wire, Graphic_Group)):
+                            highlighted.HighlightPoint(pos)
                         highlighted.SetHighlighted(True)
                 self.HighlightedElement = highlighted
             if self.rubberBand.IsShown():
@@ -2361,10 +2365,10 @@
             poss_div_types = []
 
             SFC_WireMenu_Buttons = {
-                'SFC_Step': (_(u'Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, False)),
-                'SFC_Jump': (_(u'Jump'), self.GetAddToWireMenuCallBack(self.AddNewJump)),
-                'SFC_Transition': (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, False)),
-                'SFC_ActionBlock': (_(u'Action Block'), self.GetAddToWireMenuCallBack(self.AddNewActionBlock))}
+                'SFC_Step': (_('Step'), self.GetAddToWireMenuCallBack(self.AddNewStep, False)),
+                'SFC_Jump': (_('Jump'), self.GetAddToWireMenuCallBack(self.AddNewJump)),
+                'SFC_Transition': (_('Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, False)),
+                'SFC_ActionBlock': (_('Action Block'), self.GetAddToWireMenuCallBack(self.AddNewActionBlock))}
 
             for endblock in self.SFC_StandardRules.get(startblockname):
                 if start_direction in endblock:
@@ -2373,27 +2377,27 @@
                     else:
                         items.append(SFC_WireMenu_Buttons[endblock[0]])
             if len(poss_div_types) > 0:
-                items.append((_(u'Divergence'), self.GetAddToWireMenuCallBack(self.AddNewDivergence,
+                items.append((_('Divergence'), self.GetAddToWireMenuCallBack(self.AddNewDivergence,
                                                                               poss_div_types)))
         elif start_direction == EAST:
             items.extend([
-                (_(u'Block'), self.GetAddToWireMenuCallBack(self.AddNewBlock)),
-                (_(u'Connection'), self.GetAddToWireMenuCallBack(self.AddNewConnection))])
+                (_('Block'), self.GetAddToWireMenuCallBack(self.AddNewBlock)),
+                (_('Connection'), self.GetAddToWireMenuCallBack(self.AddNewConnection))])
 
             if self.CurrentLanguage != "FBD":
-                items.append((_(u'Contact'), self.GetAddToWireMenuCallBack(self.AddNewContact)))
+                items.append((_('Contact'), self.GetAddToWireMenuCallBack(self.AddNewContact)))
 
             if self.CurrentLanguage == "LD":
                 items.extend([
-                    (_(u'Coil'), self.GetAddToWireMenuCallBack(self.AddNewCoil)),
-                    (_(u'Power Rail'), self.GetAddToWireMenuCallBack(self.AddNewPowerRail))])
+                    (_('Coil'), self.GetAddToWireMenuCallBack(self.AddNewCoil)),
+                    (_('Power Rail'), self.GetAddToWireMenuCallBack(self.AddNewPowerRail))])
 
             if self.CurrentLanguage == "SFC":
                 items.append(
-                    (_(u'Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, True)))
+                    (_('Transition'), self.GetAddToWireMenuCallBack(self.AddNewTransition, True)))
             else:
                 items.append(
-                    (_(u'Variable'), self.GetAddToWireMenuCallBack(self.AddNewVariable, True)))
+                    (_('Variable'), self.GetAddToWireMenuCallBack(self.AddNewVariable, True)))
         return items
 
     # -------------------------------------------------------------------------------
@@ -2721,7 +2725,7 @@
 
     def AddNewJump(self, bbox, wire=None):
         choices = []
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if isinstance(block, SFC_Step):
                 choices.append(block.GetName())
         dialog = wx.SingleChoiceDialog(self.ParentWindow,
@@ -2936,7 +2940,7 @@
             if self.GetDrawingMode() == DRIVENDRAWING_MODE:
                 old_name = step.GetName().upper()
                 if new_name.upper() != old_name:
-                    for block in self.Blocks.itervalues():
+                    for block in self.Blocks.values():
                         if isinstance(block, SFC_Jump):
                             if old_name == block.GetTarget().upper():
                                 block.SetTarget(new_name)
@@ -2989,7 +2993,7 @@
 
     def EditJumpContent(self, jump):
         choices = []
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if isinstance(block, SFC_Step):
                 choices.append(block.GetName())
         dialog = wx.SingleChoiceDialog(self.ParentWindow,
@@ -3303,7 +3307,7 @@
         if self.GetDrawingMode() == DRIVENDRAWING_MODE:
             name = step.GetName().upper()
             remove_jumps = []
-            for block in self.Blocks.itervalues():
+            for block in self.Blocks.values():
                 if isinstance(block, SFC_Jump):
                     if name == block.GetTarget().upper():
                         remove_jumps.append(block)
@@ -3379,7 +3383,7 @@
             element = self.ParentWindow.GetCopyBuffer()
             if bbx is None:
                 mouse_pos = self.Editor.ScreenToClient(wx.GetMousePosition())
-                middle = wx.Rect(0, 0, *self.Editor.GetClientSize()).InsideXY(mouse_pos.x, mouse_pos.y)
+                middle = wx.Rect(0, 0, *self.Editor.GetClientSize()).Contains(mouse_pos.x, mouse_pos.y)
                 if middle:
                     x, y = self.CalcUnscrolledPosition(mouse_pos.x, mouse_pos.y)
                 else:
@@ -3389,7 +3393,7 @@
                 middle = True
                 new_pos = [bbx.x, bbx.y]
             result = self.Controler.PasteEditedElementInstances(self.TagName, element, new_pos, middle, self.Debug)
-            if not isinstance(result, string_types):
+            if not isinstance(result, str):
                 self.RefreshBuffer()
                 self.RefreshView(selection=result)
                 self.RefreshVariablePanel()
@@ -3502,7 +3506,7 @@
                         block = self.Blocks.get(infos[2])
                         if block is not None:
                             blocks.append((block, (infos[1:], start, end, SEARCH_RESULT_HIGHLIGHT)))
-            blocks.sort(sort_blocks)
+            blocks.sort(key=cmp_to_key(sort_blocks))
             self.SearchResults.extend([infos for block, infos in blocks])
             self.CurrentFindHighlight = None
 
@@ -3633,7 +3637,6 @@
         else:
             dc.SetBackground(wx.Brush(self.Editor.GetBackgroundColour()))
             dc.Clear()
-            dc.BeginDrawing()
         if self.Scaling is not None and self.DrawGrid and not printing:
             dc.SetPen(wx.TRANSPARENT_PEN)
             dc.SetBrush(self.GridBrush)
@@ -3648,27 +3651,27 @@
             xstart, ystart = self.GetViewStart()
             window_size = self.Editor.GetClientSize()
             if self.PageSize[0] != 0:
-                for x in xrange(self.PageSize[0] - (xstart * SCROLLBAR_UNIT) % self.PageSize[0], int(window_size[0] / self.ViewScale[0]), self.PageSize[0]):
+                for x in range(self.PageSize[0] - (xstart * SCROLLBAR_UNIT) % self.PageSize[0], int(window_size[0] / self.ViewScale[0]), self.PageSize[0]):
                     dc.DrawLine(xstart * SCROLLBAR_UNIT + x + 1, int(ystart * SCROLLBAR_UNIT / self.ViewScale[0]),
                                 xstart * SCROLLBAR_UNIT + x + 1, int((ystart * SCROLLBAR_UNIT + window_size[1]) / self.ViewScale[0]))
             if self.PageSize[1] != 0:
-                for y in xrange(self.PageSize[1] - (ystart * SCROLLBAR_UNIT) % self.PageSize[1], int(window_size[1] / self.ViewScale[1]), self.PageSize[1]):
+                for y in range(self.PageSize[1] - (ystart * SCROLLBAR_UNIT) % self.PageSize[1], int(window_size[1] / self.ViewScale[1]), self.PageSize[1]):
                     dc.DrawLine(int(xstart * SCROLLBAR_UNIT / self.ViewScale[0]), ystart * SCROLLBAR_UNIT + y + 1,
                                 int((xstart * SCROLLBAR_UNIT + window_size[0]) / self.ViewScale[1]), ystart * SCROLLBAR_UNIT + y + 1)
 
         # Draw all elements
-        for comment in self.Comments.itervalues():
+        for comment in self.Comments.values():
             if comment != self.SelectedElement and (comment.IsVisible() or printing):
                 comment.Draw(dc)
-        for wire in self.Wires.iterkeys():
+        for wire in self.Wires.keys():
             if wire != self.SelectedElement and (wire.IsVisible() or printing):
                 if not self.Debug or not wire.GetValue():
                     wire.Draw(dc)
         if self.Debug:
-            for wire in self.Wires.iterkeys():
+            for wire in self.Wires.keys():
                 if wire != self.SelectedElement and (wire.IsVisible() or printing) and wire.GetValue():
                     wire.Draw(dc)
-        for block in self.Blocks.itervalues():
+        for block in self.Blocks.values():
             if block != self.SelectedElement and (block.IsVisible() or printing):
                 block.Draw(dc)
 
@@ -3680,12 +3683,15 @@
                 self.InstanceName.Draw(dc)
             if self.rubberBand.IsShown():
                 self.rubberBand.Draw(dc)
-            dc.EndDrawing()
 
     def OnPaint(self, event):
-        dc = self.GetLogicalDC(True)
+        event.Skip()
+        sx,sy = self.Editor.GetClientSize()
+        if sx <= 0 or sy <= 0 :
+            return
+        dc = wx.MemoryDC(wx.Bitmap(sx,sy))
+        self.PrepareDC(dc)
         self.DoDrawing(dc)
         wx.BufferedPaintDC(self.Editor, dc.GetAsBitmap())
         if self.Debug:
             DebugViewer.RefreshNewData(self)
-        event.Skip()
--- a/etherlab/ConfigEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/ConfigEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,14 +9,14 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import os
 import re
 
 import wx
 import wx.grid
-import wx.gizmos
+import wx.adv
 import wx.lib.buttons
 
 from plcopen.structures import IEC_KEYWORDS, TestIdentifier
@@ -30,7 +30,7 @@
 from etherlab.EtherCATManagementEditor import EtherCATManagementTreebook, MasterStatePanelClass
 # -----------------------------------------------------------------------
 
-[ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3)
+[ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = list(range(3))
 
 
 def GetVariablesTableColnames(position=False):
@@ -81,9 +81,9 @@
         self.VariablesFilter.Bind(wx.EVT_COMBOBOX, self.OnVariablesFilterChanged)
         self.VariablesFilter.Bind(wx.EVT_TEXT_ENTER, self.OnVariablesFilterChanged)
         self.VariablesFilter.Bind(wx.EVT_CHAR, self.OnVariablesFilterKeyDown)
-        self.AddWindow(self.VariablesFilter, flag=wx.GROW)
-
-        self.VariablesGrid = wx.gizmos.TreeListCtrl(parent,
+        self.Add(self.VariablesFilter, flag=wx.GROW)
+
+        self.VariablesGrid = wx.adv.TreeListCtrl(parent,
                                                     style=wx.TR_DEFAULT_STYLE |
                                                     wx.TR_ROW_LINES |
                                                     wx.TR_COLUMN_LINES |
@@ -91,7 +91,7 @@
                                                     wx.TR_FULL_ROW_HIGHLIGHT)
         self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
                                                 self.OnVariablesGridLeftClick)
-        self.AddWindow(self.VariablesGrid, flag=wx.GROW)
+        self.Add(self.VariablesGrid, flag=wx.GROW)
 
         self.Filters = []
         for desc, value in VARIABLES_FILTERS:
@@ -274,10 +274,10 @@
 
         variables_label = wx.StaticText(self.EthercatNodeEditor,
                                         label=_('Variable entries:'))
-        main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP | wx.LEFT | wx.RIGHT)
+        main_sizer.Add(variables_label, border=10, flag=wx.TOP | wx.LEFT | wx.RIGHT)
 
         self.NodeVariables = NodeVariablesSizer(self.EthercatNodeEditor, self.Controler)
-        main_sizer.AddSizer(self.NodeVariables, border=10,
+        main_sizer.Add(self.NodeVariables, border=10,
                             flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.EthercatNodeEditor.SetSizer(main_sizer)
@@ -310,7 +310,7 @@
 
         self.EtherCATManagementTreebook = EtherCATManagementTreebook(self.EtherCATManagementEditor, self.Controler, self)
 
-        self.EtherCATManagermentEditor_Main_Sizer.AddSizer(self.EtherCATManagementTreebook, border=10, flag=wx.GROW)
+        self.EtherCATManagermentEditor_Main_Sizer.Add(self.EtherCATManagementTreebook, border=10, flag=wx.GROW)
 
         self.EtherCATManagementEditor.SetSizer(self.EtherCATManagermentEditor_Main_Sizer)
         return self.EtherCATManagementEditor
@@ -421,7 +421,7 @@
             if values[1] == "location":
                 result = LOCATION_MODEL.match(values[0])
                 if result is not None:
-                    location = map(int, result.group(1).split('.'))
+                    location = list(map(int, result.group(1).split('.')))
                 master_location = self.ParentWindow.GetMasterLocation()
                 if master_location == tuple(location[:len(master_location)]) and \
                    len(location) - len(master_location) == 3:
@@ -449,6 +449,8 @@
 
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
+        return True
 
     def ShowMessage(self, message):
         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK | wx.ICON_ERROR)
@@ -483,7 +485,7 @@
             if values[1] == "location":
                 result = LOCATION_MODEL.match(values[0])
                 if result is not None and len(values) > 5:
-                    location = map(int, result.group(1).split('.'))
+                    location = list(map(int, result.group(1).split('.')))
                     access = values[5]
             elif values[1] == "variable":
                 location = values[0]
@@ -501,6 +503,8 @@
 
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
+            return False
+        return True
 
     def ShowMessage(self, message):
         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK | wx.ICON_ERROR)
@@ -617,7 +621,7 @@
 
         self.MasterStateEditor_Panel = MasterStatePanelClass(self.MasterStateEditor, self.Controler)
 
-        self.MasterStateEditor_Panel_Main_Sizer.AddSizer(self.MasterStateEditor_Panel, border=10, flag=wx.GROW)
+        self.MasterStateEditor_Panel_Main_Sizer.Add(self.MasterStateEditor_Panel, border=10, flag=wx.GROW)
 
         self.MasterStateEditor.SetSizer(self.MasterStateEditor_Panel_Main_Sizer)
         return self.MasterStateEditor
@@ -651,7 +655,7 @@
 
         process_variables_label = wx.StaticText(self.EthercatMasterEditor,
                                                 label=_("Process variables mapped between nodes:"))
-        process_variables_header.AddWindow(process_variables_label, 1,
+        process_variables_header.Add(process_variables_label, 1,
                                            flag=wx.ALIGN_CENTER_VERTICAL)
 
         for name, bitmap, help in [
@@ -661,14 +665,14 @@
                 ("DownVariableButton", "down", _("Move process variable down"))]:
             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            process_variables_header.AddWindow(button, border=5, flag=wx.LEFT)
+            process_variables_header.Add(button, border=5, flag=wx.LEFT)
 
         self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
         self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150))
         self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self))
-        self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                                        self.OnProcessVariablesGridCellChange)
         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK,
                                        self.OnProcessVariablesGridCellLeftClick)
@@ -678,7 +682,7 @@
 
         startup_commands_label = wx.StaticText(self.EthercatMasterEditor,
                                                label=_("Startup service variables assignments:"))
-        startup_commands_header.AddWindow(startup_commands_label, 1,
+        startup_commands_header.Add(startup_commands_label, 1,
                                           flag=wx.ALIGN_CENTER_VERTICAL)
 
         for name, bitmap, help in [
@@ -686,14 +690,14 @@
                 ("DeleteCommandButton", "remove_element", _("Remove startup service variable"))]:
             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28), style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
-            startup_commands_header.AddWindow(button, border=5, flag=wx.LEFT)
+            startup_commands_header.Add(button, border=5, flag=wx.LEFT)
 
         self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
         self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self))
         self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150))
-        self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,
+        self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGING,
                                       self.OnStartupCommandsGridCellChange)
         self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                       self.OnStartupCommandsGridEditorShow)
@@ -702,29 +706,29 @@
 
         main_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Node filter:"))
         staticbox_sizer = wx.StaticBoxSizer(main_staticbox, wx.VERTICAL)
-        self.EthercatMasterEditorSizer.AddSizer(staticbox_sizer, 0, border=10, flag=wx.GROW | wx.ALL)
+        self.EthercatMasterEditorSizer.Add(staticbox_sizer, 0, border=10, flag=wx.GROW | wx.ALL)
 
         main_staticbox_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=0)
         main_staticbox_sizer.AddGrowableCol(0)
         main_staticbox_sizer.AddGrowableRow(2)
         main_staticbox_sizer.AddGrowableRow(4)
         main_staticbox_sizer.AddGrowableRow(5)
-        staticbox_sizer.AddSizer(main_staticbox_sizer, 1, flag=wx.GROW)
-        main_staticbox_sizer.AddWindow(self.NodesFilter, border=5, flag=wx.GROW | wx.ALL)
-        main_staticbox_sizer.AddSizer(process_variables_header, border=5,
+        staticbox_sizer.Add(main_staticbox_sizer, 1, flag=wx.GROW)
+        main_staticbox_sizer.Add(self.NodesFilter, border=5, flag=wx.GROW | wx.ALL)
+        main_staticbox_sizer.Add(process_variables_header, border=5,
                                       flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddWindow(self.ProcessVariablesGrid, 1,
+        main_staticbox_sizer.Add(self.ProcessVariablesGrid, 1,
                                        border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddSizer(startup_commands_header,
+        main_staticbox_sizer.Add(startup_commands_header,
                                       border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
-        main_staticbox_sizer.AddWindow(self.StartupCommandsGrid, 1,
+        main_staticbox_sizer.Add(self.StartupCommandsGrid, 1,
                                        border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
         second_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Nodes variables filter:"))
         second_staticbox_sizer = wx.StaticBoxSizer(second_staticbox, wx.VERTICAL)
-        second_staticbox_sizer.AddSizer(self.NodesVariables, 1, border=5, flag=wx.GROW | wx.ALL)
-
-        main_staticbox_sizer.AddSizer(second_staticbox_sizer, 1,
+        second_staticbox_sizer.Add(self.NodesVariables, 1, border=5, flag=wx.GROW | wx.ALL)
+
+        main_staticbox_sizer.Add(second_staticbox_sizer, 1,
                                       border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
 
         self.EthercatMasterEditor.SetSizer(self.EthercatMasterEditorSizer)
@@ -1113,21 +1117,21 @@
 
         ESI_files_label = wx.StaticText(parent,
                                         label=_("ESI Files:"))
-        self.AddWindow(ESI_files_label, border=10,
+        self.Add(ESI_files_label, border=10,
                        flag=wx.TOP | wx.LEFT | wx.RIGHT)
 
         folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
         folder_tree_sizer.AddGrowableCol(0)
         folder_tree_sizer.AddGrowableRow(0)
-        self.AddSizer(folder_tree_sizer, border=10,
+        self.Add(folder_tree_sizer, border=10,
                       flag=wx.GROW | wx.LEFT | wx.RIGHT)
 
         self.ESIFiles = FolderTree(parent, self.GetPath(), editable=False)
         self.ESIFiles.SetFilter(".xml")
-        folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
+        folder_tree_sizer.Add(self.ESIFiles, flag=wx.GROW)
 
         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
-        folder_tree_sizer.AddSizer(buttons_sizer,
+        folder_tree_sizer.Add(buttons_sizer,
                                    flag=wx.ALIGN_CENTER_VERTICAL)
 
         for idx, (name, bitmap, help, callback) in enumerate(buttons):
@@ -1135,7 +1139,7 @@
                                                     bitmap=GetBitmap(bitmap),
                                                     size=wx.Size(28, 28),
                                                     style=wx.NO_BORDER)
-            button.SetToolTipString(help)
+            button.SetToolTip(help)
             setattr(self, name, button)
             if idx > 0:
                 flag = wx.TOP
@@ -1145,14 +1149,14 @@
                 callback = getattr(self, "On" + name, None)
             if callback is not None:
                 parent.Bind(wx.EVT_BUTTON, callback, button)
-            buttons_sizer.AddWindow(button, border=10, flag=flag)
+            buttons_sizer.Add(button, border=10, flag=flag)
 
         modules_label = wx.StaticText(parent,
                                       label=_("Modules library:"))
-        self.AddSizer(modules_label, border=10,
+        self.Add(modules_label, border=10,
                       flag=wx.LEFT | wx.RIGHT)
 
-        self.ModulesGrid = wx.gizmos.TreeListCtrl(parent,
+        self.ModulesGrid = wx.adv.TreeListCtrl(parent,
                                                   style=wx.TR_DEFAULT_STYLE |
                                                   wx.TR_ROW_LINES |
                                                   wx.TR_COLUMN_LINES |
@@ -1166,7 +1170,7 @@
                               self.OnModulesGridEndLabelEdit)
         self.ModulesGrid.GetHeaderWindow().Bind(wx.EVT_MOTION,
                                                 self.OnModulesGridHeaderMotion)
-        self.AddWindow(self.ModulesGrid, border=10,
+        self.Add(self.ModulesGrid, border=10,
                        flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         for colname, colsize, colalign in zip(
@@ -1240,7 +1244,7 @@
                                _("Choose an XML file"),
                                os.getcwd(), "",
                                _("XML files (*.xml)|*.xml|All files|*.*"),
-                               wx.OPEN)
+                               wx.FD_OPEN)
 
         if dialog.ShowModal() == wx.ID_OK:
             filepath = dialog.GetPath()
@@ -1335,7 +1339,7 @@
         if col > 0 and self.LastToolTipCol != col:
             self.LastToolTipCol = col
             _param, param_infos = self.ModuleLibrary.MODULES_EXTRA_PARAMS[col - 1]
-            wx.CallAfter(self.ModulesGrid.GetHeaderWindow().SetToolTipString,
+            wx.CallAfter(self.ModulesGrid.GetHeaderWindow().SetToolTip,
                          param_infos["description"])
         event.Skip()
 
@@ -1359,13 +1363,14 @@
                 ("DeleteButton", "remove_element", _("Remove file from database"), None)
             ])
         self.DatabaseSizer.SetControlMinSize(wx.Size(0, 0))
-        main_sizer.AddSizer(self.DatabaseSizer, border=10,
+        main_sizer.Add(self.DatabaseSizer, border=10,
                             flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)
 
         button_sizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE)
-        button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
-        button_sizer.GetCancelButton().SetLabel(_("Close"))
-        main_sizer.AddSizer(button_sizer, border=10,
+        # FIXME: find a way to change buttons label compatible with wxPython 4.x
+        # button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
+        # button_sizer.GetCancelButton().SetLabel(_("Close"))
+        main_sizer.Add(button_sizer, border=10,
                             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
 
         self.SetSizer(main_sizer)
--- a/etherlab/EtherCATManagementEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/EtherCATManagementEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -7,15 +7,15 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import os
 import string
 from xml.dom import minidom
 
 import wx
 import wx.grid
-import wx.gizmos
+import wx.adv
 import wx.lib.buttons
 
 # --------------------------------------------------------------------
@@ -135,7 +135,7 @@
             self.SizerDic["SlaveInfosDetailsInnerSizer"].AddMany([self.StaticTextDic[statictext_name],
                                                                   self.TextCtrlDic[textctrl_name]])
 
-        self.SizerDic["SlaveInfosDetailsBox"].AddSizer(self.SizerDic["SlaveInfosDetailsInnerSizer"])
+        self.SizerDic["SlaveInfosDetailsBox"].Add(self.SizerDic["SlaveInfosDetailsInnerSizer"])
 
         self.SyncManagersGrid = CustomGrid(self, size=wx.Size(605, 155), style=wx.VSCROLL)
 
@@ -153,7 +153,7 @@
         for button_name, button_id, button_label, button_tooltipstring, event_method, sub_item in buttons:
             self.ButtonDic[button_name] = wx.Button(self, id=button_id, label=_(button_label))
             self.ButtonDic[button_name].Bind(wx.EVT_BUTTON, event_method)
-            self.ButtonDic[button_name].SetToolTipString(button_tooltipstring)
+            self.ButtonDic[button_name].SetToolTip(button_tooltipstring)
             self.SizerDic["SlaveState_up_sizer"].Add(self.ButtonDic[button_name])
             for statictext_name, statictext_label, textctrl_name in sub_item:
                 self.StaticTextDic[statictext_name] = wx.StaticText(self, label=_(statictext_label))
@@ -166,7 +166,7 @@
                 ("StopTimerButton", "Stop State Monitoring", "Slave State Update Stop", self.CurrentStateThreadStop)]:
             self.ButtonDic[button_name] = wx.Button(self, label=_(button_label))
             self.ButtonDic[button_name].Bind(wx.EVT_BUTTON, event_method)
-            self.ButtonDic[button_name].SetToolTipString(button_tooltipstring)
+            self.ButtonDic[button_name].SetToolTip(button_tooltipstring)
             self.SizerDic["SlaveState_down_sizer"].Add(self.ButtonDic[button_name])
 
         self.SizerDic["SlaveState_sizer"].AddMany([self.SizerDic["SlaveState_up_sizer"],
@@ -331,7 +331,7 @@
         wx.Panel.__init__(self, parent, -1)
 
         self.DatatypeDescription, self.CommunicationObject, self.ManufacturerSpecific, \
-            self.ProfileSpecific, self.Reserved, self.AllSDOData = range(6)
+            self.ProfileSpecific, self.Reserved, self.AllSDOData = list(range(6))
 
         self.Controler = controler
 
@@ -418,7 +418,7 @@
         If the off is selected, the monitor thread gets off.
         @param event: wx.EVT_RADIOBOX object 
         """
-        on, off = range(2)
+        on, off = list(range(2))
 
         if event.GetInt() == on:
             CheckThreadFlag = self.SDOMonitoringThreadOn()
@@ -455,7 +455,7 @@
     def SDOMonitorThreadProc(self):
         while self.SDOMonitoringFlag and self.Controler.GetCTRoot()._connector.PLCStatus != "Started":
             self.SDOValuesList = self.Controler.GetCTRoot()._connector.GetSDOData()
-            LocalData = self.SDOValuesList[0].items()
+            LocalData = list(self.SDOValuesList[0].items())
             LocalData.sort()
             if self.SDOValuesList[1] != self.Controler.GetSlavePos():
                 continue
@@ -508,7 +508,7 @@
         Parse SDO data set that obtain "ESI file"
         @param entries: SDO entry list 
         """  
-        entries_list = entries.items()
+        entries_list = list(entries.items())
         entries_list.sort()
         self.ForDefaultValueFlag = False
         self.CompareValue = ""
@@ -518,7 +518,7 @@
             # exclude entry that isn't in the objects
             check_mapping = entry["PDOMapping"]
             if check_mapping is "T" or check_mapping is "R":
-                if "PDO index" not in entry.keys():
+                if "PDO index" not in list(entry.keys()):
                     continue
 
             idx = "0" + entry["Index"].strip("#")
@@ -793,7 +793,7 @@
             SDOPanel.SDOMonitorGrid.AppendRows(len(SDOPanel.SDOMonitorEntries))
             SDOPanel.SetSDOTraceValues(SDOPanel.SDOMonitorEntries)
             
-            SME_list = SDOPanel.SDOMonitorEntries.items()
+            SME_list = list(SDOPanel.SDOMonitorEntries.items())
             SME_list.sort()
 
             gridRow = 0
@@ -1497,7 +1497,7 @@
         if check_connect_flag:
             status, _log_count = self.Controler.GetCTRoot()._connector.GetPLCstatus()
             if status is not PlcStatus.Started:
-                dialog = wx.FileDialog(self, _("Choose a binary file"), os.getcwd(), "",  _("bin files (*.bin)|*.bin"), wx.OPEN)
+                dialog = wx.FileDialog(self, _("Choose a binary file"), os.getcwd(), "",  _("bin files (*.bin)|*.bin"), wx.FD_OPEN)
 
                 if dialog.ShowModal() == wx.ID_OK:
                     filepath = dialog.GetPath()
@@ -1547,7 +1547,7 @@
         # Config Data: EEPROM Size, PDI Type, Device Emulation
         # Find PDI Type in pdiType dictionary
         cnt_pdi_type = self.Controler.CommonMethod.SmartViewInfosFromXML["pdi_type"]
-        for i in self.PDIType.keys():
+        for i in list(self.PDIType.keys()):
             if cnt_pdi_type == i:
                 cnt_pdi_type = self.PDIType[i][0]
                 break
@@ -1627,7 +1627,7 @@
         eeprom_size = str((int(self.GetWordAddressData(sii_dict.get('Size'), 10))+1)//8*1024)
         # Find PDI Type in pdiType dictionary
         cnt_pdi_type = int(self.GetWordAddressData(sii_dict.get('PDIControl'), 16).split('x')[1][2:4], 16)
-        for i in self.PDIType.keys():
+        for i in list(self.PDIType.keys()):
             if cnt_pdi_type == i:
                 cnt_pdi_type = self.PDIType[i][0]
                 break
@@ -1729,7 +1729,7 @@
 
         wx.Panel.__init__(self, parent, -1, size=(350, 500))
 
-        self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=(350, 500),
+        self.Tree = wx.adv.TreeListCtrl(self, -1, size=(350, 500),
                                            style=(wx.TR_DEFAULT_STYLE |
                                                   wx.TR_FULL_ROW_HIGHLIGHT |
                                                   wx.TR_HIDE_ROOT |
@@ -1896,7 +1896,7 @@
         @param event : wx.EVT_BUTTON object
         """
         dialog = wx.FileDialog(self, _("Choose a binary file"), os.getcwd(), "",
-                               _("bin files (*.bin)|*.bin"), wx.OPEN)
+                               _("bin files (*.bin)|*.bin"), wx.FD_OPEN)
 
         if dialog.ShowModal() == wx.ID_OK:
             filepath = dialog.GetPath()
@@ -2106,7 +2106,7 @@
                                                   ("pdi", "type", self.PDIType),
                                                   ("fmmu", "number", self.FMMUNumber),
                                                   ("sm", "number", self.SMNumber)]:
-                        if property in register.attributes.keys():
+                        if property in list(register.attributes.keys()):
                             if type == "type":
                                 if register.attributes[property].value == value:
                                     self.GetRegisterInfo(reg_info_tree, register)
@@ -2153,7 +2153,7 @@
                                                       ("pdi", "type", self.PDIType),
                                                       ("fmmu", "number", self.FMMUNumber),
                                                       ("sm", "number", self.SMNumber)]:
-                            if property in detail.attributes.keys():
+                            if property in list(detail.attributes.keys()):
                                 if type == "type":
                                     if detail.attributes[property].value == value:
                                         self.GetRegisterDetailInfo(reg_info_tree, reg_index, detail)
@@ -2522,7 +2522,7 @@
 
         reg_sub_grid_data = []
 
-        BIT_RANGE, NAME, DESCRIPTIONS = range(3)
+        BIT_RANGE, NAME, DESCRIPTIONS = list(range(3))
 
         # Check if this register's detail description is exist or not,
         # and create data structure for the detail description table ; sub grid
@@ -2692,7 +2692,7 @@
             self.TextCtrl[key] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
             self.MasterStateSizer['innerMasterState'].AddMany([self.StaticText[key], self.TextCtrl[key]])
 
-        self.MasterStateSizer['masterState'].AddSizer(self.MasterStateSizer['innerMasterState'])
+        self.MasterStateSizer['masterState'].Add(self.MasterStateSizer['innerMasterState'])
 
         # ----------------------- Ethernet Network Card Information ---------------------------------------
         for key, label in [
@@ -2705,7 +2705,7 @@
             self.TextCtrl[key] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
             self.MasterStateSizer['innerDeviceInfo'].AddMany([self.StaticText[key], self.TextCtrl[key]])
 
-        self.MasterStateSizer['deviceInfo'].AddSizer(self.MasterStateSizer['innerDeviceInfo'])
+        self.MasterStateSizer['deviceInfo'].Add(self.MasterStateSizer['innerDeviceInfo'])
 
         # ----------------------- Network Frame Information -----------------------------------------------
         for key, label in [
@@ -2722,13 +2722,13 @@
                 self.TextCtrl[key][index] = wx.TextCtrl(self, size=wx.Size(130, 24), style=wx.TE_READONLY)
                 self.MasterStateSizer['innerFrameInfo'].Add(self.TextCtrl[key][index])
 
-        self.MasterStateSizer['frameInfo'].AddSizer(self.MasterStateSizer['innerFrameInfo'])
+        self.MasterStateSizer['frameInfo'].Add(self.MasterStateSizer['innerFrameInfo'])
 
         # ------------------------------- Slave Information  -----------------------------------------------
         self.SITreeListCtrl = SITreeListCtrl(self, self.Controler) 
         self.MasterStateSizer["innerSlaveInfo"].AddMany([self.SIUpdateButton,
                                                                self.SITreeListCtrl])
-        self.MasterStateSizer["slaveInfo"].AddSizer(
+        self.MasterStateSizer["slaveInfo"].Add(
                 self.MasterStateSizer["innerSlaveInfo"]) 
 
         # --------------------------------- Main Sizer ----------------------------------------------------
@@ -2743,7 +2743,7 @@
             ("main", [
                     "innerTop", "innerMiddle", "innerBottom"])]:
             for key2 in sub:
-                self.MasterStateSizer[key].AddSizer(self.MasterStateSizer[key2])
+                self.MasterStateSizer[key].Add(self.MasterStateSizer[key2])
 
         self.SetSizer(self.MasterStateSizer["main"])
     
@@ -2798,7 +2798,7 @@
 
         self.Controler=controler
         
-        self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=wx.Size(750,350), 
+        self.Tree = wx.adv.TreeListCtrl(self, -1, size=wx.Size(750,350), 
                                                             style=wx.TR_HAS_BUTTONS
                                                             |wx.TR_HIDE_ROOT
                                                             |wx.TR_ROW_LINES
@@ -2817,7 +2817,7 @@
         """
         Update the data of the slave information.
         """
-        position, not_used, state, not_used, name = range(5)
+        position, not_used, state, not_used, name = list(range(5))
         
         slave_node = []
         slave_info_list = []
@@ -2933,7 +2933,7 @@
                 ec_idx += 1
             
             # set texts in "error" column. 
-            ec_info_list = error_counter.items()
+            ec_info_list = list(error_counter.items())
             ec_info_list.sort()
             
             err_checker = "none"
--- a/etherlab/EthercatCIA402Slave.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/EthercatCIA402Slave.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,7 +9,7 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 
 import wx
@@ -424,7 +424,7 @@
             if variable in check_variable:
                 continue
 
-            var_infos = dict(zip(["name", "index", "subindex", "var_type", "dir"], variable))
+            var_infos = dict(list(zip(["name", "index", "subindex", "var_type", "dir"], variable)))
             var_infos["location"] = location_str
             var_infos["var_size"] = self.GetSizeOfType(var_infos["var_type"])
             var_infos["var_name"] = "__%(dir)s%(var_size)s%(location)s_%(index)d_%(subindex)d" % var_infos
--- a/etherlab/EthercatMaster.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/EthercatMaster.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,7 +9,7 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 from copy import deepcopy
 from functools import reduce
@@ -298,7 +298,7 @@
                     EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
                 if error is None:
                     config_is_saved = True
-            except Exception, e:
+            except Exception as e:
                 error = e.message
             config_xmlfile.close()
             
@@ -651,7 +651,7 @@
             # Test OD
             entries = device.GetEntriesList(limits)
             #entries = self.CTNParent.GetEntriesList()
-            entries_list = entries.items()
+            entries_list = list(entries.items())
             entries_list.sort()
             entries = []
             current_index = None
@@ -817,7 +817,7 @@
                     else:
                         sync_managers.append(LOCATION_VAR_INPUT)
 
-                entries = device.GetEntriesList().items()
+                entries = list(device.GetEntriesList().items())
                 entries.sort()
                 for (index, subindex), entry in entries:
                     var_size = self.GetSizeOfType(entry["Type"])
--- a/etherlab/EthercatSlave.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/EthercatSlave.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,7 +9,7 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 
 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
 from ConfigTreeNode import ConfigTreeNode
--- a/etherlab/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,4 +1,4 @@
-from __future__ import absolute_import
+
 
 from etherlab.etherlab import *
 from util.BitmapLibrary import AddBitmapFolder
--- a/etherlab/etherlab.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/etherlab.py	Wed Oct 04 16:24:00 2023 +0200
@@ -13,7 +13,6 @@
 import os
 import shutil
 import csv
-from builtins import str as text
 
 from lxml import etree
 import wx
@@ -425,7 +424,7 @@
                     #     self.GetCTRoot().logger.write_warning(
                     #         XSDSchemaErrorMessage % (filepath + error))
                 except Exception as exc:
-                    self.modules_infos, error = None, text(exc)
+                    self.modules_infos, error = None, str(exc)
                 xmlfile.close()
 
                 if self.modules_infos is not None:
--- a/etherlab/runtime_etherlab.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/etherlab/runtime_etherlab.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,7 +9,7 @@
 #
 # see copying file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 import signal
 import subprocess
--- a/exemples/python/plc.xml	Fri Aug 18 11:38:20 2023 +0200
+++ b/exemples/python/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -1,1644 +1,1678 @@
-<?xml version='1.0' encoding='utf-8'?>
-<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
-  <fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz:&#10;&#10;   1. How to implement python extensions.&#10;   2. How to implement basic C extension.&#10;   3. How to use C code in IEC POUs.&#10;   4. How to call C functions from python code.&#10;   5. How to avoid race conditions between IEC, C and python code.&#10;   6. How to convert betweet different IEC types.&#10;"/>
-  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-10-19T23:53:08">
-    <coordinateInfo>
-      <pageSize x="1024" y="1024"/>
-      <fbd>
-        <scaling x="5" y="5"/>
-      </fbd>
-      <ld>
-        <scaling x="5" y="5"/>
-      </ld>
-      <sfc>
-        <scaling x="5" y="5"/>
-      </sfc>
-    </coordinateInfo>
-  </contentHeader>
-  <types>
-    <dataTypes>
-      <dataType name="CPLX_TYPE">
-        <baseType>
-          <struct>
-            <variable name="FIRSTBYTE">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-            <variable name="SECONDBYTE">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-          </struct>
-        </baseType>
-      </dataType>
-      <dataType name="StateMachine">
-        <baseType>
-          <enum>
-            <values>
-              <value name="STANDBY"/>
-              <value name="START"/>
-              <value name="STOP"/>
-            </values>
-          </enum>
-        </baseType>
-      </dataType>
-      <dataType name="datatype0">
-        <baseType>
-          <BOOL/>
-        </baseType>
-      </dataType>
-      <dataType name="blups">
-        <baseType>
-          <array>
-            <dimension lower="0" upper="31"/>
-            <baseType>
-              <derived name="CPLX_TYPE"/>
-            </baseType>
-          </array>
-        </baseType>
-      </dataType>
-    </dataTypes>
-    <pous>
-      <pou name="main_pytest" pouType="program">
-        <interface>
-          <localVars>
-            <variable name="mux1_sel">
-              <type>
-                <INT/>
-              </type>
-              <initialValue>
-                <simpleValue value="3"/>
-              </initialValue>
-              <documentation>
-                <xhtml:p><![CDATA[blah]]></xhtml:p>
-              </documentation>
-            </variable>
-            <variable name="mux2_sel">
-              <type>
-                <INT/>
-              </type>
-              <initialValue>
-                <simpleValue value="3"/>
-              </initialValue>
-            </variable>
-            <variable name="pytest_var1">
-              <type>
-                <string/>
-              </type>
-            </variable>
-            <variable name="fefvsd">
-              <type>
-                <derived name="datatype0"/>
-              </type>
-            </variable>
-            <variable name="pytest_var2">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
-            <variable name="py1">
-              <type>
-                <derived name="python_eval"/>
-              </type>
-            </variable>
-            <variable name="Block1">
-              <type>
-                <derived name="python_eval"/>
-              </type>
-            </variable>
-            <variable name="Block2">
-              <type>
-                <derived name="python_eval"/>
-              </type>
-            </variable>
-            <variable name="Block3">
-              <type>
-                <derived name="python_eval"/>
-              </type>
-            </variable>
-            <variable name="pytest_var3">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
-            <variable name="FromC">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-            <variable name="C_Pragma0">
-              <type>
-                <derived name="C_Pragma"/>
-              </type>
-            </variable>
-          </localVars>
-          <externalVars>
-            <variable name="TestInput">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-            <variable name="TestOutput">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-          </externalVars>
-          <localVars>
-            <variable name="FromInput">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-            <variable name="Test_BCD">
-              <type>
-                <WORD/>
-              </type>
-              <initialValue>
-                <simpleValue value="151"/>
-              </initialValue>
-            </variable>
-            <variable name="Test_BCD_WRONG">
-              <type>
-                <WORD/>
-              </type>
-              <initialValue>
-                <simpleValue value="154"/>
-              </initialValue>
-            </variable>
-            <variable name="Test_BCD_CONVERTED">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
-            <variable name="Test_BCD_RESULT">
-              <type>
-                <UINT/>
-              </type>
-            </variable>
-            <variable name="Test_BCD_WRONG_RESULT">
-              <type>
-                <UINT/>
-              </type>
-            </variable>
-            <variable name="Test_DT">
-              <type>
-                <DT/>
-              </type>
-              <initialValue>
-                <simpleValue value="DT#2013-02-23-22:35:46"/>
-              </initialValue>
-            </variable>
-            <variable name="Test_TOD">
-              <type>
-                <TOD/>
-              </type>
-            </variable>
-            <variable name="Test_TOD_STRING">
-              <type>
-                <string/>
-              </type>
-            </variable>
-            <variable name="Test_Date">
-              <type>
-                <DATE/>
-              </type>
-            </variable>
-            <variable name="Test_String">
-              <type>
-                <string/>
-              </type>
-              <initialValue>
-                <simpleValue value="test"/>
-              </initialValue>
-            </variable>
-            <variable name="Test_Bool">
-              <type>
-                <BOOL/>
-              </type>
-            </variable>
-          </localVars>
-          <externalVars>
-            <variable name="Global_RS">
-              <type>
-                <derived name="RS"/>
-              </type>
-            </variable>
-            <variable name="TUTU">
-              <type>
-                <INT/>
-              </type>
-            </variable>
-            <variable name="TOTO">
-              <type>
-                <INT/>
-              </type>
-            </variable>
-            <variable name="Test_Python_Var">
-              <type>
-                <INT/>
-              </type>
-            </variable>
-            <variable name="Second_Python_Var">
-              <type>
-                <INT/>
-              </type>
-            </variable>
-            <variable name="Grumpf">
-              <type>
-                <string/>
-              </type>
-            </variable>
-          </externalVars>
-          <localVars>
-            <variable name="RTC0">
-              <type>
-                <derived name="RTC"/>
-              </type>
-            </variable>
-          </localVars>
-          <externalVars>
-            <variable name="SomeVarName">
-              <type>
-                <DINT/>
-              </type>
-            </variable>
-          </externalVars>
-        </interface>
-        <body>
-          <FBD>
-            <inVariable localId="4" height="30" width="160" executionOrderId="0" negated="false">
-              <position x="295" y="450"/>
-              <connectionPointOut>
-                <relPosition x="160" y="15"/>
-              </connectionPointOut>
-              <expression>'666'</expression>
-            </inVariable>
-            <block localId="5" width="125" height="80" typeName="python_eval" instanceName="py1" executionOrderId="0">
-              <position x="686" y="400"/>
-              <inputVariables>
-                <variable formalParameter="TRIG">
-                  <connectionPointIn>
-                    <relPosition x="0" y="35"/>
-                    <connection refLocalId="7" formalParameter="OUT">
-                      <position x="686" y="435"/>
-                      <position x="285" y="435"/>
-                      <position x="285" y="480"/>
-                      <position x="250" y="480"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="CODE">
-                  <connectionPointIn>
-                    <relPosition x="0" y="65"/>
-                    <connection refLocalId="4">
-                      <position x="686" y="465"/>
-                      <position x="455" y="465"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="ACK">
-                  <connectionPointOut>
-                    <relPosition x="125" y="35"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="RESULT">
-                  <connectionPointOut>
-                    <relPosition x="125" y="65"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <block localId="7" width="70" height="45" typeName="NOT" executionOrderId="0">
-              <position x="180" y="450"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="3">
-                      <position x="180" y="480"/>
-                      <position x="155" y="480"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="70" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inOutVariable localId="3" height="30" width="120" executionOrderId="0" negatedOut="false" negatedIn="false">
-              <position x="35" y="465"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="7" formalParameter="OUT">
-                  <position x="35" y="480"/>
-                  <position x="25" y="480"/>
-                  <position x="25" y="440"/>
-                  <position x="270" y="440"/>
-                  <position x="270" y="480"/>
-                  <position x="250" y="480"/>
-                </connection>
-              </connectionPointIn>
-              <connectionPointOut>
-                <relPosition x="120" y="15"/>
-              </connectionPointOut>
-              <expression>pytest_var2</expression>
-            </inOutVariable>
-            <block localId="8" width="125" height="80" typeName="python_eval" instanceName="Block1" executionOrderId="0">
-              <position x="686" y="545"/>
-              <inputVariables>
-                <variable formalParameter="TRIG">
-                  <connectionPointIn>
-                    <relPosition x="0" y="35"/>
-                    <connection refLocalId="7" formalParameter="OUT">
-                      <position x="686" y="580"/>
-                      <position x="285" y="580"/>
-                      <position x="285" y="480"/>
-                      <position x="250" y="480"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="CODE">
-                  <connectionPointIn>
-                    <relPosition x="0" y="65"/>
-                    <connection refLocalId="9">
-                      <position x="686" y="610"/>
-                      <position x="665" y="610"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="ACK">
-                  <connectionPointOut>
-                    <relPosition x="125" y="35"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="RESULT">
-                  <connectionPointOut>
-                    <relPosition x="125" y="65"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="9" height="30" width="370" executionOrderId="0" negated="false">
-              <position x="295" y="595"/>
-              <connectionPointOut>
-                <relPosition x="370" y="15"/>
-              </connectionPointOut>
-              <expression>'sys.stdout.write("FBID :"+str(FBID)+"\n")'</expression>
-            </inVariable>
-            <inVariable localId="11" height="30" width="290" executionOrderId="0" negated="false">
-              <position x="295" y="735"/>
-              <connectionPointOut>
-                <relPosition x="290" y="15"/>
-              </connectionPointOut>
-              <expression>'PLCBinary.Simple_C_Call(5678)'</expression>
-            </inVariable>
-            <block localId="12" width="125" height="80" typeName="python_eval" instanceName="Block2" executionOrderId="0">
-              <position x="686" y="687"/>
-              <inputVariables>
-                <variable formalParameter="TRIG">
-                  <connectionPointIn>
-                    <relPosition x="0" y="33"/>
-                    <connection refLocalId="7" formalParameter="OUT">
-                      <position x="686" y="720"/>
-                      <position x="285" y="720"/>
-                      <position x="285" y="480"/>
-                      <position x="250" y="480"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="CODE">
-                  <connectionPointIn>
-                    <relPosition x="0" y="63"/>
-                    <connection refLocalId="11">
-                      <position x="686" y="750"/>
-                      <position x="585" y="750"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="ACK">
-                  <connectionPointOut>
-                    <relPosition x="125" y="33"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="RESULT">
-                  <connectionPointOut>
-                    <relPosition x="125" y="63"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="14" height="30" width="290" executionOrderId="0" negated="false">
-              <position x="290" y="885"/>
-              <connectionPointOut>
-                <relPosition x="290" y="15"/>
-              </connectionPointOut>
-              <expression>'MyPythonFunc(42)'</expression>
-            </inVariable>
-            <block localId="15" width="125" height="80" typeName="python_eval" instanceName="Block3" executionOrderId="0">
-              <position x="686" y="837"/>
-              <inputVariables>
-                <variable formalParameter="TRIG">
-                  <connectionPointIn>
-                    <relPosition x="0" y="33"/>
-                    <connection refLocalId="7" formalParameter="OUT">
-                      <position x="686" y="870"/>
-                      <position x="285" y="870"/>
-                      <position x="285" y="480"/>
-                      <position x="250" y="480"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="CODE">
-                  <connectionPointIn>
-                    <relPosition x="0" y="63"/>
-                    <connection refLocalId="14">
-                      <position x="686" y="900"/>
-                      <position x="580" y="900"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="ACK">
-                  <connectionPointOut>
-                    <relPosition x="125" y="33"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="RESULT">
-                  <connectionPointOut>
-                    <relPosition x="125" y="63"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <comment localId="16" height="90" width="680">
-              <position x="35" y="275"/>
-              <content>
-                <xhtml:p><![CDATA[This part of the example test that, despite of 2T period clock stimulating TRIG pin of pyth_eval blocks, blocks keep executing one after the other, in respect of execution order.]]></xhtml:p>
-              </content>
-            </comment>
-            <block localId="17" width="80" height="120" typeName="MUX" executionOrderId="0">
-              <position x="1101" y="790"/>
-              <inputVariables>
-                <variable formalParameter="K">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="18">
-                      <position x="1101" y="820"/>
-                      <position x="1076" y="820"/>
-                      <position x="1076" y="810"/>
-                      <position x="1060" y="810"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN0">
-                  <connectionPointIn>
-                    <relPosition x="0" y="50"/>
-                    <connection refLocalId="5" formalParameter="RESULT">
-                      <position x="1101" y="840"/>
-                      <position x="941" y="840"/>
-                      <position x="941" y="465"/>
-                      <position x="811" y="465"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN1">
-                  <connectionPointIn>
-                    <relPosition x="0" y="70"/>
-                    <connection refLocalId="8" formalParameter="RESULT">
-                      <position x="1101" y="860"/>
-                      <position x="926" y="860"/>
-                      <position x="926" y="610"/>
-                      <position x="811" y="610"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN2">
-                  <connectionPointIn>
-                    <relPosition x="0" y="90"/>
-                    <connection refLocalId="12" formalParameter="RESULT">
-                      <position x="1101" y="880"/>
-                      <position x="911" y="880"/>
-                      <position x="911" y="750"/>
-                      <position x="811" y="750"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN3">
-                  <connectionPointIn>
-                    <relPosition x="0" y="110"/>
-                    <connection refLocalId="15" formalParameter="RESULT">
-                      <position x="1101" y="900"/>
-                      <position x="811" y="900"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="80" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <outVariable localId="19" height="35" width="125" executionOrderId="0" negated="false">
-              <position x="1271" y="805"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="17" formalParameter="OUT">
-                  <position x="1271" y="820"/>
-                  <position x="1181" y="820"/>
-                </connection>
-              </connectionPointIn>
-              <expression>pytest_var1</expression>
-            </outVariable>
-            <block localId="21" width="80" height="120" typeName="MUX" executionOrderId="0">
-              <position x="1106" y="385"/>
-              <inputVariables>
-                <variable formalParameter="K">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="22">
-                      <position x="1106" y="415"/>
-                      <position x="1076" y="415"/>
-                      <position x="1076" y="405"/>
-                      <position x="1055" y="405"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN0">
-                  <connectionPointIn>
-                    <relPosition x="0" y="50"/>
-                    <connection refLocalId="5" formalParameter="ACK">
-                      <position x="1106" y="435"/>
-                      <position x="811" y="435"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN1">
-                  <connectionPointIn>
-                    <relPosition x="0" y="70"/>
-                    <connection refLocalId="8" formalParameter="ACK">
-                      <position x="1106" y="455"/>
-                      <position x="841" y="455"/>
-                      <position x="841" y="580"/>
-                      <position x="811" y="580"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN2">
-                  <connectionPointIn>
-                    <relPosition x="0" y="90"/>
-                    <connection refLocalId="12" formalParameter="ACK">
-                      <position x="1106" y="475"/>
-                      <position x="856" y="475"/>
-                      <position x="856" y="720"/>
-                      <position x="811" y="720"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN3">
-                  <connectionPointIn>
-                    <relPosition x="0" y="110"/>
-                    <connection refLocalId="15" formalParameter="ACK">
-                      <position x="1106" y="495"/>
-                      <position x="871" y="495"/>
-                      <position x="871" y="870"/>
-                      <position x="811" y="870"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="80" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="22" height="30" width="74" executionOrderId="0" negated="false">
-              <position x="981" y="390"/>
-              <connectionPointOut>
-                <relPosition x="74" y="15"/>
-              </connectionPointOut>
-              <expression>mux1_sel</expression>
-            </inVariable>
-            <outVariable localId="23" height="35" width="125" executionOrderId="0" negated="false">
-              <position x="1271" y="400"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="21" formalParameter="OUT">
-                  <position x="1271" y="415"/>
-                  <position x="1186" y="415"/>
-                </connection>
-              </connectionPointIn>
-              <expression>pytest_var3</expression>
-            </outVariable>
-            <outVariable localId="25" height="30" width="60" executionOrderId="0" negated="false">
-              <position x="320" y="1075"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="26" formalParameter="OUT">
-                  <position x="320" y="1090"/>
-                  <position x="265" y="1090"/>
-                </connection>
-              </connectionPointIn>
-              <expression>FromC</expression>
-            </outVariable>
-            <inVariable localId="1" height="30" width="30" executionOrderId="0" negated="false">
-              <position x="105" y="1075"/>
-              <connectionPointOut>
-                <relPosition x="30" y="15"/>
-              </connectionPointOut>
-              <expression>23</expression>
-            </inVariable>
-            <block localId="26" width="80" height="45" typeName="C_Pragma" instanceName="C_Pragma0" executionOrderId="0">
-              <position x="185" y="1060"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="1">
-                      <position x="185" y="1090"/>
-                      <position x="135" y="1090"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="80" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="27" height="30" width="90" executionOrderId="0" negated="false">
-              <position x="100" y="1190"/>
-              <connectionPointOut>
-                <relPosition x="90" y="15"/>
-              </connectionPointOut>
-              <expression>TestInput</expression>
-            </inVariable>
-            <outVariable localId="28" height="30" width="105" executionOrderId="0" negated="false">
-              <position x="195" y="1125"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="2">
-                  <position x="195" y="1140"/>
-                  <position x="140" y="1140"/>
-                </connection>
-              </connectionPointIn>
-              <expression>TestOutput</expression>
-            </outVariable>
-            <outVariable localId="29" height="30" width="85" executionOrderId="0" negated="false">
-              <position x="215" y="1190"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="27">
-                  <position x="215" y="1205"/>
-                  <position x="190" y="1205"/>
-                </connection>
-              </connectionPointIn>
-              <expression>FromInput</expression>
-            </outVariable>
-            <inVariable localId="2" height="30" width="30" executionOrderId="0" negated="false">
-              <position x="110" y="1125"/>
-              <connectionPointOut>
-                <relPosition x="30" y="15"/>
-              </connectionPointOut>
-              <expression>10</expression>
-            </inVariable>
-            <comment localId="30" height="105" width="465">
-              <position x="50" y="925"/>
-              <content>
-                <xhtml:p><![CDATA[You will be ready to use beremiz with C and Python when you will understand why "FromInput" is equal  to 75.
-Happy hacking! ]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="31" height="90" width="345">
-              <position x="295" y="485"/>
-              <content>
-                <xhtml:p><![CDATA[Sleep here is bad. It blocks other py_eval instances. Whith a wxGlade GUI, GUI freeze for a second.]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="6" height="80" width="345">
-              <position x="295" y="630"/>
-              <content>
-                <xhtml:p><![CDATA[Prints FBID to stdout of PLC runtime. FBID is a unique reference to py_eval instance.]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="10" height="85" width="345">
-              <position x="295" y="770"/>
-              <content>
-                <xhtml:p><![CDATA[Simple_C_Call is declared in C_File "1.x:c_code". See python ctypes manual for details on typing.]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="32" height="145" width="235">
-              <position x="25" y="505"/>
-              <content>
-                <xhtml:p><![CDATA[Fast clock, at least faster that sleep(1). See what happens when python takes time to answer : PLC continues.]]></xhtml:p>
-              </content>
-            </comment>
-            <outVariable localId="33" height="30" width="133" executionOrderId="0" negated="false">
-              <position x="580" y="1564"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="35" formalParameter="OUT">
-                  <position x="580" y="1580"/>
-                  <position x="371" y="1580"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_BCD_RESULT</expression>
-            </outVariable>
-            <inVariable localId="34" height="30" width="75" executionOrderId="0" negated="false">
-              <position x="60" y="1564"/>
-              <connectionPointOut>
-                <relPosition x="75" y="16"/>
-              </connectionPointOut>
-              <expression>Test_BCD</expression>
-            </inVariable>
-            <block localId="35" width="106" height="60" typeName="BCD_TO_UINT" executionOrderId="0">
-              <position x="265" y="1539"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="41"/>
-                    <connection refLocalId="34">
-                      <position x="265" y="1580"/>
-                      <position x="135" y="1580"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="106" y="41"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="36" height="30" width="66" executionOrderId="0" negated="false">
-              <position x="60" y="1774"/>
-              <connectionPointOut>
-                <relPosition x="66" y="16"/>
-              </connectionPointOut>
-              <expression>Test_DT</expression>
-            </inVariable>
-            <block localId="37" width="255" height="45" typeName="DATE_AND_TIME_TO_TIME_OF_DAY" executionOrderId="0">
-              <position x="265" y="1759"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="31"/>
-                    <connection refLocalId="36">
-                      <position x="265" y="1790"/>
-                      <position x="125" y="1790"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="255" y="31"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <block localId="38" width="195" height="45" typeName="DATE_AND_TIME_TO_DATE" executionOrderId="0">
-              <position x="265" y="1834"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="31"/>
-                    <connection refLocalId="36">
-                      <position x="265" y="1865"/>
-                      <position x="242" y="1865"/>
-                      <position x="242" y="1790"/>
-                      <position x="125" y="1790"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="195" y="31"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <outVariable localId="40" height="30" width="82" executionOrderId="0" negated="false">
-              <position x="580" y="1849"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="38" formalParameter="OUT">
-                  <position x="580" y="1865"/>
-                  <position x="460" y="1865"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_Date</expression>
-            </outVariable>
-            <outVariable localId="42" height="30" width="98" executionOrderId="0" negated="false">
-              <position x="465" y="1944"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="46" formalParameter="OUT">
-                  <position x="465" y="1960"/>
-                  <position x="395" y="1960"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_String</expression>
-            </outVariable>
-            <outVariable localId="43" height="30" width="82" executionOrderId="0" negated="false">
-              <position x="465" y="2014"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="44" formalParameter="OUT">
-                  <position x="465" y="2030"/>
-                  <position x="400" y="2030"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_Bool</expression>
-            </outVariable>
-            <block localId="44" width="135" height="45" typeName="STRING_TO_BOOL" executionOrderId="0">
-              <position x="265" y="1999"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="31"/>
-                    <connection refLocalId="45">
-                      <position x="265" y="2030"/>
-                      <position x="115" y="2030"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="135" y="31"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="45" height="30" width="58" executionOrderId="0" negated="false">
-              <position x="60" y="2014"/>
-              <connectionPointOut>
-                <relPosition x="58" y="16"/>
-              </connectionPointOut>
-              <expression>'True'</expression>
-            </inVariable>
-            <block localId="46" width="130" height="45" typeName="INT_TO_STRING" executionOrderId="0">
-              <position x="265" y="1929"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="31"/>
-                    <connection refLocalId="58">
-                      <position x="265" y="1960"/>
-                      <position x="205" y="1960"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="130" y="31"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="50" height="30" width="106" executionOrderId="0" negated="false">
-              <position x="75" y="2275"/>
-              <connectionPointOut>
-                <relPosition x="106" y="15"/>
-              </connectionPointOut>
-              <expression>Global_RS.Q1</expression>
-            </inVariable>
-            <block localId="51" width="70" height="85" typeName="AND" executionOrderId="0">
-              <position x="240" y="2255"/>
-              <inputVariables>
-                <variable formalParameter="IN1" negated="true">
-                  <connectionPointIn>
-                    <relPosition x="0" y="35"/>
-                    <connection refLocalId="50">
-                      <position x="240" y="2290"/>
-                      <position x="180" y="2290"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN2">
-                  <connectionPointIn>
-                    <relPosition x="0" y="70"/>
-                    <connection refLocalId="52">
-                      <position x="240" y="2325"/>
-                      <position x="180" y="2325"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="70" y="35"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="52" height="30" width="105" executionOrderId="0" negated="false">
-              <position x="75" y="2310"/>
-              <connectionPointOut>
-                <relPosition x="105" y="15"/>
-              </connectionPointOut>
-              <expression>BOOL#TRUE</expression>
-            </inVariable>
-            <outVariable localId="13" height="30" width="105" executionOrderId="0" negated="false">
-              <position x="385" y="2275"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="51" formalParameter="OUT">
-                  <position x="385" y="2290"/>
-                  <position x="310" y="2290"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Global_RS.S</expression>
-            </outVariable>
-            <outVariable localId="20" height="30" width="106" executionOrderId="0" negated="false">
-              <position x="385" y="2390"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="41" formalParameter="OUT">
-                  <position x="385" y="2405"/>
-                  <position x="310" y="2405"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Global_RS.R1</expression>
-            </outVariable>
-            <inVariable localId="24" height="30" width="106" executionOrderId="0" negated="false">
-              <position x="75" y="2390"/>
-              <connectionPointOut>
-                <relPosition x="106" y="15"/>
-              </connectionPointOut>
-              <expression>Global_RS.Q1</expression>
-            </inVariable>
-            <block localId="41" width="70" height="85" typeName="OR" executionOrderId="0">
-              <position x="240" y="2370"/>
-              <inputVariables>
-                <variable formalParameter="IN1">
-                  <connectionPointIn>
-                    <relPosition x="0" y="35"/>
-                    <connection refLocalId="24">
-                      <position x="240" y="2405"/>
-                      <position x="180" y="2405"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN2">
-                  <connectionPointIn>
-                    <relPosition x="0" y="70"/>
-                    <connection refLocalId="48">
-                      <position x="240" y="2440"/>
-                      <position x="180" y="2440"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="70" y="35"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="48" height="30" width="105" executionOrderId="0" negated="false">
-              <position x="75" y="2425"/>
-              <connectionPointOut>
-                <relPosition x="105" y="15"/>
-              </connectionPointOut>
-              <expression>BOOL#FALSE</expression>
-            </inVariable>
-            <outVariable localId="54" height="30" width="135" executionOrderId="0" negated="false">
-              <position x="930" y="1774"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="55" formalParameter="OUT">
-                  <position x="930" y="1790"/>
-                  <position x="855" y="1790"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_TOD_STRING</expression>
-            </outVariable>
-            <block localId="55" width="125" height="45" typeName="TOD_TO_STRING" executionOrderId="0">
-              <position x="730" y="1759"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="31"/>
-                    <connection refLocalId="39">
-                      <position x="730" y="1790"/>
-                      <position x="655" y="1790"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="125" y="31"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inOutVariable localId="39" height="30" width="75" executionOrderId="0" negatedOut="false" negatedIn="false">
-              <position x="580" y="1774"/>
-              <connectionPointIn>
-                <relPosition x="0" y="16"/>
-                <connection refLocalId="37" formalParameter="OUT">
-                  <position x="580" y="1790"/>
-                  <position x="520" y="1790"/>
-                </connection>
-              </connectionPointIn>
-              <connectionPointOut>
-                <relPosition x="75" y="16"/>
-              </connectionPointOut>
-              <expression>Test_TOD</expression>
-            </inOutVariable>
-            <inVariable localId="49" height="30" width="30" executionOrderId="0" negated="false">
-              <position x="160" y="2510"/>
-              <connectionPointOut>
-                <relPosition x="30" y="15"/>
-              </connectionPointOut>
-              <expression>42</expression>
-            </inVariable>
-            <outVariable localId="57" height="30" width="50" executionOrderId="0" negated="false">
-              <position x="240" y="2510"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="49">
-                  <position x="240" y="2525"/>
-                  <position x="190" y="2525"/>
-                </connection>
-              </connectionPointIn>
-              <expression>TOTO</expression>
-            </outVariable>
-            <outVariable localId="56" height="30" width="50" executionOrderId="0" negated="false">
-              <position x="240" y="2550"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="49">
-                  <position x="240" y="2565"/>
-                  <position x="215" y="2565"/>
-                  <position x="215" y="2525"/>
-                  <position x="190" y="2525"/>
-                </connection>
-              </connectionPointIn>
-              <expression>TUTU</expression>
-            </outVariable>
-            <inVariable localId="58" height="30" width="146" executionOrderId="0" negated="false">
-              <position x="60" y="1944"/>
-              <connectionPointOut>
-                <relPosition x="146" y="16"/>
-              </connectionPointOut>
-              <expression>Second_Python_Var</expression>
-            </inVariable>
-            <inVariable localId="59" height="30" width="30" executionOrderId="0" negated="false">
-              <position x="100" y="1385"/>
-              <connectionPointOut>
-                <relPosition x="30" y="15"/>
-              </connectionPointOut>
-              <expression>1</expression>
-            </inVariable>
-            <block localId="61" typeName="function0" executionOrderId="0" height="45" width="111">
-              <position x="760" y="1170"/>
-              <inputVariables>
-                <variable formalParameter="LocalVar0">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="62">
-                      <position x="760" y="1200"/>
-                      <position x="723" y="1200"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="111" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <inVariable localId="62" executionOrderId="0" height="30" width="58" negated="false">
-              <position x="665" y="1185"/>
-              <connectionPointOut>
-                <relPosition x="58" y="15"/>
-              </connectionPointOut>
-              <expression>fefvsd</expression>
-            </inVariable>
-            <outVariable localId="63" executionOrderId="0" height="30" width="58" negated="false">
-              <position x="905" y="1185"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="61" formalParameter="OUT">
-                  <position x="905" y="1200"/>
-                  <position x="871" y="1200"/>
-                </connection>
-              </connectionPointIn>
-              <expression>fefvsd</expression>
-            </outVariable>
-            <comment localId="53" height="80" width="420">
-              <position x="75" y="2160"/>
-              <content>
-                <xhtml:p><![CDATA[Shows global variables access from resource configuration (res_pytest) and from project's configuration.]]></xhtml:p>
-              </content>
-            </comment>
-            <inVariable localId="18" height="30" width="74" executionOrderId="0" negated="false">
-              <position x="986" y="795"/>
-              <connectionPointOut>
-                <relPosition x="74" y="15"/>
-              </connectionPointOut>
-              <expression>mux2_sel</expression>
-            </inVariable>
-            <comment localId="60" height="45" width="930">
-              <position x="60" y="1480"/>
-              <content>
-                <xhtml:p><![CDATA[Here is shown how to convert values between different types (BCD, DT, TOD, STRING and others) using standard functions.]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="64" height="55" width="300">
-              <position x="665" y="1095"/>
-              <content>
-                <xhtml:p><![CDATA[Example of usage of user-defined function.]]></xhtml:p>
-              </content>
-            </comment>
-            <comment localId="65" height="45" width="410">
-              <position x="55" y="1315"/>
-              <content>
-                <xhtml:p><![CDATA[Shows access variable defined in python extension. ]]></xhtml:p>
-              </content>
-            </comment>
-            <inVariable localId="66" height="30" width="137" executionOrderId="0" negated="false">
-              <position x="60" y="1685"/>
-              <connectionPointOut>
-                <relPosition x="137" y="15"/>
-              </connectionPointOut>
-              <expression>Test_BCD_WRONG</expression>
-            </inVariable>
-            <block localId="67" width="106" height="100" typeName="BCD_TO_UINT" executionOrderId="0">
-              <position x="265" y="1620"/>
-              <inputVariables>
-                <variable formalParameter="EN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="40"/>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="80"/>
-                    <connection refLocalId="66">
-                      <position x="265" y="1700"/>
-                      <position x="255" y="1700"/>
-                      <position x="255" y="1700"/>
-                      <position x="345" y="1700"/>
-                      <position x="345" y="1700"/>
-                      <position x="197" y="1700"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="ENO">
-                  <connectionPointOut>
-                    <relPosition x="106" y="40"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="106" y="80"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <outVariable localId="68" height="30" width="196" executionOrderId="0" negated="false">
-              <position x="580" y="1685"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="67" formalParameter="OUT">
-                  <position x="580" y="1700"/>
-                  <position x="371" y="1700"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_BCD_WRONG_RESULT</expression>
-            </outVariable>
-            <comment localId="69" height="165" width="375">
-              <position x="795" y="1590"/>
-              <content>
-                <xhtml:p><![CDATA[Incorrect BCD number is not converted to UINT.
-
-151 (16#97) is good BCD number , but 
-154 (16#9A) is not.  
-
-Try this out and look at value of  Test_BCD_CONVERTED variable.
-
-
-]]></xhtml:p>
-              </content>
-            </comment>
-            <outVariable localId="70" height="30" width="185" executionOrderId="0" negated="false">
-              <position x="580" y="1645"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="67" formalParameter="ENO">
-                  <position x="580" y="1660"/>
-                  <position x="370" y="1660"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_BCD_CONVERTED</expression>
-            </outVariable>
-            <comment localId="71" height="215" width="680">
-              <position x="35" y="30"/>
-              <content>
-                <xhtml:p><![CDATA[This example shows many features in Beremiz:
-
-   1. How to implement python extensions.
-   2. How to implement basic C extension.
-   3. How to use C code in IEC POUs.
-   4. How to call C functions from python code.
-   5. How to avoid race conditions between IEC, C and python code.
-   6. How to convert betweet different IEC types.
-]]></xhtml:p>
-              </content>
-            </comment>
-            <outVariable localId="72" executionOrderId="0" height="30" width="60" negated="false">
-              <position x="1065" y="1970"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="76" formalParameter="OUT">
-                  <position x="1065" y="1985"/>
-                  <position x="1025" y="1985"/>
-                  <position x="1025" y="1995"/>
-                  <position x="985" y="1995"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Grumpf</expression>
-            </outVariable>
-            <inVariable localId="73" executionOrderId="0" height="30" width="85" negated="false">
-              <position x="625" y="1940"/>
-              <connectionPointOut>
-                <relPosition x="85" y="15"/>
-              </connectionPointOut>
-              <expression>BOOL#TRUE</expression>
-            </inVariable>
-            <inVariable localId="74" executionOrderId="0" height="30" width="70" negated="false">
-              <position x="625" y="1975"/>
-              <connectionPointOut>
-                <relPosition x="70" y="15"/>
-              </connectionPointOut>
-              <expression>Test_DT</expression>
-            </inVariable>
-            <block localId="75" typeName="RTC" instanceName="RTC0" executionOrderId="0" height="90" width="65">
-              <position x="760" y="1925"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="35"/>
-                    <connection refLocalId="73">
-                      <position x="760" y="1960"/>
-                      <position x="735" y="1960"/>
-                      <position x="735" y="1955"/>
-                      <position x="710" y="1955"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="PDT">
-                  <connectionPointIn>
-                    <relPosition x="0" y="70"/>
-                    <connection refLocalId="74">
-                      <position x="760" y="1995"/>
-                      <position x="727" y="1995"/>
-                      <position x="727" y="1990"/>
-                      <position x="695" y="1990"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="Q">
-                  <connectionPointOut>
-                    <relPosition x="65" y="35"/>
-                  </connectionPointOut>
-                </variable>
-                <variable formalParameter="CDT">
-                  <connectionPointOut>
-                    <relPosition x="65" y="70"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <block localId="76" typeName="DT_TO_STRING" executionOrderId="0" height="40" width="110">
-              <position x="875" y="1965"/>
-              <inputVariables>
-                <variable formalParameter="IN">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="75" formalParameter="CDT">
-                      <position x="875" y="1995"/>
-                      <position x="825" y="1995"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="110" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <block localId="77" typeName="ADD" executionOrderId="0" height="60" width="65">
-              <position x="170" y="1370"/>
-              <inputVariables>
-                <variable formalParameter="IN1">
-                  <connectionPointIn>
-                    <relPosition x="0" y="30"/>
-                    <connection refLocalId="59">
-                      <position x="170" y="1400"/>
-                      <position x="130" y="1400"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-                <variable formalParameter="IN2">
-                  <connectionPointIn>
-                    <relPosition x="0" y="50"/>
-                    <connection refLocalId="78">
-                      <position x="170" y="1420"/>
-                      <position x="160" y="1420"/>
-                      <position x="160" y="1450"/>
-                      <position x="390" y="1450"/>
-                      <position x="390" y="1400"/>
-                      <position x="380" y="1400"/>
-                    </connection>
-                  </connectionPointIn>
-                </variable>
-              </inputVariables>
-              <inOutVariables/>
-              <outputVariables>
-                <variable formalParameter="OUT">
-                  <connectionPointOut>
-                    <relPosition x="65" y="30"/>
-                  </connectionPointOut>
-                </variable>
-              </outputVariables>
-            </block>
-            <outVariable localId="47" executionOrderId="0" height="30" width="130" negated="false">
-              <position x="625" y="1335"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="79">
-                  <position x="625" y="1350"/>
-                  <position x="590" y="1350"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_Python_Var</expression>
-            </outVariable>
-            <inVariable localId="79" executionOrderId="0" height="25" width="30" negated="false">
-              <position x="560" y="1340"/>
-              <connectionPointOut>
-                <relPosition x="30" y="10"/>
-              </connectionPointOut>
-              <expression>23</expression>
-            </inVariable>
-            <inOutVariable localId="78" executionOrderId="0" height="30" width="100" negatedOut="false" negatedIn="false">
-              <position x="280" y="1385"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="77" formalParameter="OUT">
-                  <position x="280" y="1400"/>
-                  <position x="235" y="1400"/>
-                </connection>
-              </connectionPointIn>
-              <connectionPointOut>
-                <relPosition x="100" y="15"/>
-              </connectionPointOut>
-              <expression>SomeVarName</expression>
-            </inOutVariable>
-          </FBD>
-        </body>
-      </pou>
-      <pou name="C_Pragma" pouType="functionBlock">
-        <interface>
-          <outputVars>
-            <variable name="OUT">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-          </outputVars>
-          <inputVars>
-            <variable name="IN">
-              <type>
-                <SINT/>
-              </type>
-            </variable>
-          </inputVars>
-          <localVars>
-            <variable name="COORDS">
-              <type>
-                <array>
-                  <dimension lower="0" upper="5"/>
-                  <baseType>
-                    <SINT/>
-                  </baseType>
-                </array>
-              </type>
-              <initialValue>
-                <arrayValue>
-                  <value>
-                    <simpleValue value="54"/>
-                  </value>
-                  <value>
-                    <simpleValue value="55"/>
-                  </value>
-                  <value>
-                    <simpleValue value="56"/>
-                  </value>
-                  <value>
-                    <simpleValue value="57"/>
-                  </value>
-                  <value>
-                    <simpleValue value="58"/>
-                  </value>
-                  <value>
-                    <simpleValue value="59"/>
-                  </value>
-                </arrayValue>
-              </initialValue>
-            </variable>
-            <variable name="SMURF">
-              <type>
-                <derived name="CPLX_TYPE"/>
-              </type>
-            </variable>
-          </localVars>
-          <externalVars>
-            <variable name="Global_RS">
-              <type>
-                <derived name="RS"/>
-              </type>
-            </variable>
-            <variable name="Dudiduda">
-              <type>
-                <derived name="blups"/>
-              </type>
-            </variable>
-          </externalVars>
-        </interface>
-        <body>
-          <ST>
-            <xhtml:p><![CDATA[(* hereafter is a C pragma accessing FB interface in a clean way *)
-{{
-  char toPLC;
-  char fromPLC = GetFbVar(IN);
-  extern int PLC_C_Call(char, char *);
-  if(PLC_C_Call(fromPLC, &toPLC)){
-    SetFbVar(OUT, toPLC);
-  }
-  if(0){
-    /* that code demonstrate C access to complex types */
-    char somebyte = GetFbVar(COORDS, .table[3]);
-    SetFbVar(SMURF, somebyte, .FIRSTBYTE);
-    SetFbVar(COORDS, somebyte, .table[4]);
-  }
-}}
-(* If you do not use GetFbVar and SetFbVar macros, expect unexpected behaviour*)
-Global_RS();
-
-(* testing access to global struct array *)
-Dudiduda[2].FIRSTBYTE := 0;
-]]></xhtml:p>
-          </ST>
-        </body>
-      </pou>
-      <pou name="norm" pouType="function">
-        <interface>
-          <returnType>
-            <REAL/>
-          </returnType>
-          <inputVars>
-            <variable name="IN1">
-              <type>
-                <REAL/>
-              </type>
-            </variable>
-            <variable name="IN2">
-              <type>
-                <REAL/>
-              </type>
-            </variable>
-          </inputVars>
-        </interface>
-        <body>
-          <ST>
-            <xhtml:p><![CDATA[NORM := SQRT(IN1 * IN1 + IN2 * IN2);]]></xhtml:p>
-          </ST>
-        </body>
-      </pou>
-      <pou name="function0" pouType="function">
-        <interface>
-          <returnType>
-            <derived name="datatype0"/>
-          </returnType>
-          <inputVars>
-            <variable name="LocalVar0">
-              <type>
-                <derived name="datatype0"/>
-              </type>
-            </variable>
-          </inputVars>
-        </interface>
-        <body>
-          <ST>
-            <xhtml:p><![CDATA[function0 := LocalVar0;
-]]></xhtml:p>
-          </ST>
-        </body>
-      </pou>
-    </pous>
-  </types>
-  <instances>
-    <configurations>
-      <configuration name="config">
-        <resource name="res_pytest">
-          <task name="pytest_task" priority="0" interval="T#500ms"/>
-          <globalVars>
-            <variable name="TOTO">
-              <type>
-                <INT/>
-              </type>
-            </variable>
-          </globalVars>
-          <pouInstance name="pytest_instance" typeName="main_pytest"/>
-        </resource>
-        <globalVars>
-          <variable name="Global_RS">
-            <type>
-              <derived name="RS"/>
-            </type>
-          </variable>
-          <variable name="Dudiduda">
-            <type>
-              <derived name="blups"/>
-            </type>
-          </variable>
-          <variable name="TUTU">
-            <type>
-              <INT/>
-            </type>
-          </variable>
-        </globalVars>
-      </configuration>
-    </configurations>
-  </instances>
-</project>
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz:&#10;&#10;   1. How to implement python extensions.&#10;   2. How to implement basic C extension.&#10;   3. How to use C code in IEC POUs.&#10;   4. How to call C functions from python code.&#10;   5. How to avoid race conditions between IEC, C and python code.&#10;   6. How to convert betweet different IEC types.&#10;"/>
+  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2023-09-28T17:24:49">
+    <coordinateInfo>
+      <pageSize x="1024" y="1024"/>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="5" y="5"/>
+      </ld>
+      <sfc>
+        <scaling x="5" y="5"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes>
+      <dataType name="CPLX_TYPE">
+        <baseType>
+          <struct>
+            <variable name="FIRSTBYTE">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+            <variable name="SECONDBYTE">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+          </struct>
+        </baseType>
+      </dataType>
+      <dataType name="StateMachine">
+        <baseType>
+          <enum>
+            <values>
+              <value name="STANDBY"/>
+              <value name="START"/>
+              <value name="STOP"/>
+            </values>
+          </enum>
+        </baseType>
+      </dataType>
+      <dataType name="datatype0">
+        <baseType>
+          <BOOL/>
+        </baseType>
+      </dataType>
+      <dataType name="blups">
+        <baseType>
+          <array>
+            <dimension lower="0" upper="31"/>
+            <baseType>
+              <derived name="CPLX_TYPE"/>
+            </baseType>
+          </array>
+        </baseType>
+      </dataType>
+    </dataTypes>
+    <pous>
+      <pou name="main_pytest" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="mux1_sel">
+              <type>
+                <INT/>
+              </type>
+              <initialValue>
+                <simpleValue value="3"/>
+              </initialValue>
+              <documentation>
+                <xhtml:p><![CDATA[blah]]></xhtml:p>
+              </documentation>
+            </variable>
+            <variable name="mux2_sel">
+              <type>
+                <INT/>
+              </type>
+              <initialValue>
+                <simpleValue value="3"/>
+              </initialValue>
+            </variable>
+            <variable name="pytest_var1">
+              <type>
+                <string/>
+              </type>
+            </variable>
+            <variable name="fefvsd">
+              <type>
+                <derived name="datatype0"/>
+              </type>
+            </variable>
+            <variable name="pytest_var2">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="py1">
+              <type>
+                <derived name="python_eval"/>
+              </type>
+            </variable>
+            <variable name="Block1">
+              <type>
+                <derived name="python_eval"/>
+              </type>
+            </variable>
+            <variable name="Block2">
+              <type>
+                <derived name="python_eval"/>
+              </type>
+            </variable>
+            <variable name="Block3">
+              <type>
+                <derived name="python_eval"/>
+              </type>
+            </variable>
+            <variable name="pytest_var3">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="FromC">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+            <variable name="C_Pragma0">
+              <type>
+                <derived name="C_Pragma"/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="TestInput">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+            <variable name="TestOutput">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="FromInput">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+            <variable name="Test_BCD">
+              <type>
+                <WORD/>
+              </type>
+              <initialValue>
+                <simpleValue value="151"/>
+              </initialValue>
+            </variable>
+            <variable name="Test_BCD_WRONG">
+              <type>
+                <WORD/>
+              </type>
+              <initialValue>
+                <simpleValue value="154"/>
+              </initialValue>
+            </variable>
+            <variable name="Test_BCD_CONVERTED">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="Test_BCD_RESULT">
+              <type>
+                <UINT/>
+              </type>
+            </variable>
+            <variable name="Test_BCD_WRONG_RESULT">
+              <type>
+                <UINT/>
+              </type>
+            </variable>
+            <variable name="Test_DT">
+              <type>
+                <DT/>
+              </type>
+              <initialValue>
+                <simpleValue value="DT#2013-02-23-22:35:46"/>
+              </initialValue>
+            </variable>
+            <variable name="Test_TOD">
+              <type>
+                <TOD/>
+              </type>
+            </variable>
+            <variable name="Test_TOD_STRING">
+              <type>
+                <string/>
+              </type>
+            </variable>
+            <variable name="Test_Date">
+              <type>
+                <DATE/>
+              </type>
+            </variable>
+            <variable name="Test_String">
+              <type>
+                <string/>
+              </type>
+              <initialValue>
+                <simpleValue value="test"/>
+              </initialValue>
+            </variable>
+            <variable name="Test_Bool">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="Global_RS">
+              <type>
+                <derived name="RS"/>
+              </type>
+            </variable>
+            <variable name="TUTU">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="TOTO">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="Test_Python_Var">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="Second_Python_Var">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+            <variable name="Grumpf">
+              <type>
+                <string/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="RTC0">
+              <type>
+                <derived name="RTC"/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="SomeVarName">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
+          </externalVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="4" height="30" width="315" executionOrderId="0" negated="false">
+              <position x="200" y="390"/>
+              <connectionPointOut>
+                <relPosition x="315" y="15"/>
+              </connectionPointOut>
+              <expression>'MyPrintFunction("Hello world\n")'</expression>
+            </inVariable>
+            <block localId="5" width="125" height="80" typeName="python_eval" instanceName="py1" executionOrderId="0">
+              <position x="686" y="400"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="7" formalParameter="OUT">
+                      <position x="686" y="435"/>
+                      <position x="285" y="435"/>
+                      <position x="285" y="480"/>
+                      <position x="250" y="480"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="65"/>
+                    <connection refLocalId="80" formalParameter="OUT">
+                      <position x="686" y="465"/>
+                      <position x="653" y="465"/>
+                      <position x="653" y="485"/>
+                      <position x="630" y="485"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="125" y="35"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="65"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="7" width="70" height="45" typeName="NOT" executionOrderId="0">
+              <position x="180" y="450"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="3">
+                      <position x="180" y="480"/>
+                      <position x="155" y="480"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="70" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inOutVariable localId="3" height="30" width="120" executionOrderId="0" negatedOut="false" negatedIn="false">
+              <position x="35" y="465"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="7" formalParameter="OUT">
+                  <position x="35" y="480"/>
+                  <position x="25" y="480"/>
+                  <position x="25" y="440"/>
+                  <position x="270" y="440"/>
+                  <position x="270" y="480"/>
+                  <position x="250" y="480"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="120" y="15"/>
+              </connectionPointOut>
+              <expression>pytest_var2</expression>
+            </inOutVariable>
+            <block localId="8" width="125" height="80" typeName="python_eval" instanceName="Block1" executionOrderId="0">
+              <position x="686" y="545"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="7" formalParameter="OUT">
+                      <position x="686" y="580"/>
+                      <position x="285" y="580"/>
+                      <position x="285" y="480"/>
+                      <position x="250" y="480"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="65"/>
+                    <connection refLocalId="9">
+                      <position x="686" y="610"/>
+                      <position x="665" y="610"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="125" y="35"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="65"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="9" height="30" width="370" executionOrderId="0" negated="false">
+              <position x="295" y="595"/>
+              <connectionPointOut>
+                <relPosition x="370" y="15"/>
+              </connectionPointOut>
+              <expression>'MyPrintFunction("FBID :"+str(FBID)+"\n")'</expression>
+            </inVariable>
+            <inVariable localId="11" height="30" width="290" executionOrderId="0" negated="false">
+              <position x="295" y="735"/>
+              <connectionPointOut>
+                <relPosition x="290" y="15"/>
+              </connectionPointOut>
+              <expression>'PLCBinary.Simple_C_Call(5678)'</expression>
+            </inVariable>
+            <block localId="12" width="125" height="80" typeName="python_eval" instanceName="Block2" executionOrderId="0">
+              <position x="686" y="687"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="33"/>
+                    <connection refLocalId="7" formalParameter="OUT">
+                      <position x="686" y="720"/>
+                      <position x="285" y="720"/>
+                      <position x="285" y="480"/>
+                      <position x="250" y="480"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="63"/>
+                    <connection refLocalId="11">
+                      <position x="686" y="750"/>
+                      <position x="585" y="750"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="125" y="33"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="63"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="14" height="30" width="290" executionOrderId="0" negated="false">
+              <position x="290" y="885"/>
+              <connectionPointOut>
+                <relPosition x="290" y="15"/>
+              </connectionPointOut>
+              <expression>'MyPythonFunc(42)'</expression>
+            </inVariable>
+            <block localId="15" width="125" height="80" typeName="python_eval" instanceName="Block3" executionOrderId="0">
+              <position x="686" y="837"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="33"/>
+                    <connection refLocalId="7" formalParameter="OUT">
+                      <position x="686" y="870"/>
+                      <position x="285" y="870"/>
+                      <position x="285" y="480"/>
+                      <position x="250" y="480"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="63"/>
+                    <connection refLocalId="14">
+                      <position x="686" y="900"/>
+                      <position x="580" y="900"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="125" y="33"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="63"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <comment localId="16" height="90" width="680">
+              <position x="35" y="275"/>
+              <content>
+                <xhtml:p><![CDATA[This part of the example test that, despite of 2T period clock stimulating TRIG pin of pyth_eval blocks, blocks keep executing one after the other, in respect of execution order.]]></xhtml:p>
+              </content>
+            </comment>
+            <block localId="17" width="80" height="120" typeName="MUX" executionOrderId="0">
+              <position x="1101" y="790"/>
+              <inputVariables>
+                <variable formalParameter="K">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="18">
+                      <position x="1101" y="820"/>
+                      <position x="1076" y="820"/>
+                      <position x="1076" y="810"/>
+                      <position x="1060" y="810"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="5" formalParameter="RESULT">
+                      <position x="1101" y="840"/>
+                      <position x="941" y="840"/>
+                      <position x="941" y="465"/>
+                      <position x="811" y="465"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="8" formalParameter="RESULT">
+                      <position x="1101" y="860"/>
+                      <position x="926" y="860"/>
+                      <position x="926" y="610"/>
+                      <position x="811" y="610"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="90"/>
+                    <connection refLocalId="12" formalParameter="RESULT">
+                      <position x="1101" y="880"/>
+                      <position x="911" y="880"/>
+                      <position x="911" y="750"/>
+                      <position x="811" y="750"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="110"/>
+                    <connection refLocalId="15" formalParameter="RESULT">
+                      <position x="1101" y="900"/>
+                      <position x="811" y="900"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="80" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="19" height="35" width="125" executionOrderId="0" negated="false">
+              <position x="1271" y="805"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="17" formalParameter="OUT">
+                  <position x="1271" y="820"/>
+                  <position x="1181" y="820"/>
+                </connection>
+              </connectionPointIn>
+              <expression>pytest_var1</expression>
+            </outVariable>
+            <block localId="21" width="80" height="120" typeName="MUX" executionOrderId="0">
+              <position x="1106" y="385"/>
+              <inputVariables>
+                <variable formalParameter="K">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="22">
+                      <position x="1106" y="415"/>
+                      <position x="1076" y="415"/>
+                      <position x="1076" y="405"/>
+                      <position x="1055" y="405"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="5" formalParameter="ACK">
+                      <position x="1106" y="435"/>
+                      <position x="811" y="435"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="8" formalParameter="ACK">
+                      <position x="1106" y="455"/>
+                      <position x="841" y="455"/>
+                      <position x="841" y="580"/>
+                      <position x="811" y="580"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="90"/>
+                    <connection refLocalId="12" formalParameter="ACK">
+                      <position x="1106" y="475"/>
+                      <position x="856" y="475"/>
+                      <position x="856" y="720"/>
+                      <position x="811" y="720"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="110"/>
+                    <connection refLocalId="15" formalParameter="ACK">
+                      <position x="1106" y="495"/>
+                      <position x="871" y="495"/>
+                      <position x="871" y="870"/>
+                      <position x="811" y="870"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="80" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="22" height="30" width="74" executionOrderId="0" negated="false">
+              <position x="981" y="390"/>
+              <connectionPointOut>
+                <relPosition x="74" y="15"/>
+              </connectionPointOut>
+              <expression>mux1_sel</expression>
+            </inVariable>
+            <outVariable localId="23" height="35" width="125" executionOrderId="0" negated="false">
+              <position x="1271" y="400"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="21" formalParameter="OUT">
+                  <position x="1271" y="415"/>
+                  <position x="1186" y="415"/>
+                </connection>
+              </connectionPointIn>
+              <expression>pytest_var3</expression>
+            </outVariable>
+            <outVariable localId="25" height="30" width="60" executionOrderId="0" negated="false">
+              <position x="320" y="1075"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="26" formalParameter="OUT">
+                  <position x="320" y="1090"/>
+                  <position x="265" y="1090"/>
+                </connection>
+              </connectionPointIn>
+              <expression>FromC</expression>
+            </outVariable>
+            <inVariable localId="1" height="30" width="30" executionOrderId="0" negated="false">
+              <position x="105" y="1075"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>23</expression>
+            </inVariable>
+            <block localId="26" width="80" height="45" typeName="C_Pragma" instanceName="C_Pragma0" executionOrderId="0">
+              <position x="185" y="1060"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="185" y="1090"/>
+                      <position x="135" y="1090"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="80" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="27" height="30" width="90" executionOrderId="0" negated="false">
+              <position x="100" y="1190"/>
+              <connectionPointOut>
+                <relPosition x="90" y="15"/>
+              </connectionPointOut>
+              <expression>TestInput</expression>
+            </inVariable>
+            <outVariable localId="28" height="30" width="105" executionOrderId="0" negated="false">
+              <position x="195" y="1125"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="2">
+                  <position x="195" y="1140"/>
+                  <position x="140" y="1140"/>
+                </connection>
+              </connectionPointIn>
+              <expression>TestOutput</expression>
+            </outVariable>
+            <outVariable localId="29" height="30" width="85" executionOrderId="0" negated="false">
+              <position x="215" y="1190"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="27">
+                  <position x="215" y="1205"/>
+                  <position x="190" y="1205"/>
+                </connection>
+              </connectionPointIn>
+              <expression>FromInput</expression>
+            </outVariable>
+            <inVariable localId="2" height="30" width="30" executionOrderId="0" negated="false">
+              <position x="110" y="1125"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>10</expression>
+            </inVariable>
+            <comment localId="30" height="105" width="465">
+              <position x="50" y="925"/>
+              <content>
+                <xhtml:p><![CDATA[You will be ready to use beremiz with C and Python when you will understand why "FromInput" is equal  to 75.
+Happy hacking! ]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="6" height="80" width="345">
+              <position x="295" y="630"/>
+              <content>
+                <xhtml:p><![CDATA[Prints FBID to stdout of PLC runtime. FBID is a unique reference to py_eval instance.]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="10" height="85" width="345">
+              <position x="295" y="770"/>
+              <content>
+                <xhtml:p><![CDATA[Simple_C_Call is declared in C_File "1.x:c_code". See python ctypes manual for details on typing.]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="32" height="145" width="235">
+              <position x="25" y="505"/>
+              <content>
+                <xhtml:p><![CDATA[Fast clock, at least faster that sleep(1). See what happens when python takes time to answer : PLC continues.]]></xhtml:p>
+              </content>
+            </comment>
+            <outVariable localId="33" height="30" width="133" executionOrderId="0" negated="false">
+              <position x="580" y="1564"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="35" formalParameter="OUT">
+                  <position x="580" y="1580"/>
+                  <position x="371" y="1580"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_BCD_RESULT</expression>
+            </outVariable>
+            <inVariable localId="34" height="30" width="75" executionOrderId="0" negated="false">
+              <position x="60" y="1564"/>
+              <connectionPointOut>
+                <relPosition x="75" y="16"/>
+              </connectionPointOut>
+              <expression>Test_BCD</expression>
+            </inVariable>
+            <block localId="35" width="106" height="60" typeName="BCD_TO_UINT" executionOrderId="0">
+              <position x="265" y="1539"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="41"/>
+                    <connection refLocalId="34">
+                      <position x="265" y="1580"/>
+                      <position x="135" y="1580"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="106" y="41"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="36" height="30" width="66" executionOrderId="0" negated="false">
+              <position x="60" y="1774"/>
+              <connectionPointOut>
+                <relPosition x="66" y="16"/>
+              </connectionPointOut>
+              <expression>Test_DT</expression>
+            </inVariable>
+            <block localId="37" width="255" height="45" typeName="DATE_AND_TIME_TO_TIME_OF_DAY" executionOrderId="0">
+              <position x="265" y="1759"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="31"/>
+                    <connection refLocalId="36">
+                      <position x="265" y="1790"/>
+                      <position x="125" y="1790"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="255" y="31"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="38" width="195" height="45" typeName="DATE_AND_TIME_TO_DATE" executionOrderId="0">
+              <position x="265" y="1834"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="31"/>
+                    <connection refLocalId="36">
+                      <position x="265" y="1865"/>
+                      <position x="242" y="1865"/>
+                      <position x="242" y="1790"/>
+                      <position x="125" y="1790"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="195" y="31"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="40" height="30" width="82" executionOrderId="0" negated="false">
+              <position x="580" y="1849"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="38" formalParameter="OUT">
+                  <position x="580" y="1865"/>
+                  <position x="460" y="1865"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_Date</expression>
+            </outVariable>
+            <outVariable localId="42" height="30" width="98" executionOrderId="0" negated="false">
+              <position x="465" y="1944"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="46" formalParameter="OUT">
+                  <position x="465" y="1960"/>
+                  <position x="395" y="1960"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_String</expression>
+            </outVariable>
+            <outVariable localId="43" height="30" width="82" executionOrderId="0" negated="false">
+              <position x="465" y="2014"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="44" formalParameter="OUT">
+                  <position x="465" y="2030"/>
+                  <position x="400" y="2030"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_Bool</expression>
+            </outVariable>
+            <block localId="44" width="135" height="45" typeName="STRING_TO_BOOL" executionOrderId="0">
+              <position x="265" y="1999"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="31"/>
+                    <connection refLocalId="45">
+                      <position x="265" y="2030"/>
+                      <position x="115" y="2030"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="135" y="31"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="45" height="30" width="58" executionOrderId="0" negated="false">
+              <position x="60" y="2014"/>
+              <connectionPointOut>
+                <relPosition x="58" y="16"/>
+              </connectionPointOut>
+              <expression>'True'</expression>
+            </inVariable>
+            <block localId="46" width="130" height="45" typeName="INT_TO_STRING" executionOrderId="0">
+              <position x="265" y="1929"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="31"/>
+                    <connection refLocalId="58">
+                      <position x="265" y="1960"/>
+                      <position x="205" y="1960"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="130" y="31"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="50" height="30" width="106" executionOrderId="0" negated="false">
+              <position x="75" y="2275"/>
+              <connectionPointOut>
+                <relPosition x="106" y="15"/>
+              </connectionPointOut>
+              <expression>Global_RS.Q1</expression>
+            </inVariable>
+            <block localId="51" width="70" height="85" typeName="AND" executionOrderId="0">
+              <position x="240" y="2255"/>
+              <inputVariables>
+                <variable formalParameter="IN1" negated="true">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="50">
+                      <position x="240" y="2290"/>
+                      <position x="180" y="2290"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="52">
+                      <position x="240" y="2325"/>
+                      <position x="180" y="2325"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="70" y="35"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="52" height="30" width="105" executionOrderId="0" negated="false">
+              <position x="75" y="2310"/>
+              <connectionPointOut>
+                <relPosition x="105" y="15"/>
+              </connectionPointOut>
+              <expression>BOOL#TRUE</expression>
+            </inVariable>
+            <outVariable localId="13" height="30" width="105" executionOrderId="0" negated="false">
+              <position x="385" y="2275"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="51" formalParameter="OUT">
+                  <position x="385" y="2290"/>
+                  <position x="310" y="2290"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Global_RS.S</expression>
+            </outVariable>
+            <outVariable localId="20" height="30" width="106" executionOrderId="0" negated="false">
+              <position x="385" y="2390"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="41" formalParameter="OUT">
+                  <position x="385" y="2405"/>
+                  <position x="310" y="2405"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Global_RS.R1</expression>
+            </outVariable>
+            <inVariable localId="24" height="30" width="106" executionOrderId="0" negated="false">
+              <position x="75" y="2390"/>
+              <connectionPointOut>
+                <relPosition x="106" y="15"/>
+              </connectionPointOut>
+              <expression>Global_RS.Q1</expression>
+            </inVariable>
+            <block localId="41" width="70" height="85" typeName="OR" executionOrderId="0">
+              <position x="240" y="2370"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="24">
+                      <position x="240" y="2405"/>
+                      <position x="180" y="2405"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="48">
+                      <position x="240" y="2440"/>
+                      <position x="180" y="2440"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="70" y="35"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="48" height="30" width="105" executionOrderId="0" negated="false">
+              <position x="75" y="2425"/>
+              <connectionPointOut>
+                <relPosition x="105" y="15"/>
+              </connectionPointOut>
+              <expression>BOOL#FALSE</expression>
+            </inVariable>
+            <outVariable localId="54" height="30" width="135" executionOrderId="0" negated="false">
+              <position x="930" y="1774"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="55" formalParameter="OUT">
+                  <position x="930" y="1790"/>
+                  <position x="855" y="1790"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_TOD_STRING</expression>
+            </outVariable>
+            <block localId="55" width="125" height="45" typeName="TOD_TO_STRING" executionOrderId="0">
+              <position x="730" y="1759"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="31"/>
+                    <connection refLocalId="39">
+                      <position x="730" y="1790"/>
+                      <position x="655" y="1790"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="31"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inOutVariable localId="39" height="30" width="75" executionOrderId="0" negatedOut="false" negatedIn="false">
+              <position x="580" y="1774"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="37" formalParameter="OUT">
+                  <position x="580" y="1790"/>
+                  <position x="520" y="1790"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="75" y="16"/>
+              </connectionPointOut>
+              <expression>Test_TOD</expression>
+            </inOutVariable>
+            <inVariable localId="49" height="30" width="30" executionOrderId="0" negated="false">
+              <position x="160" y="2510"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>42</expression>
+            </inVariable>
+            <outVariable localId="57" height="30" width="50" executionOrderId="0" negated="false">
+              <position x="240" y="2510"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="49">
+                  <position x="240" y="2525"/>
+                  <position x="190" y="2525"/>
+                </connection>
+              </connectionPointIn>
+              <expression>TOTO</expression>
+            </outVariable>
+            <outVariable localId="56" height="30" width="50" executionOrderId="0" negated="false">
+              <position x="240" y="2550"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="49">
+                  <position x="240" y="2565"/>
+                  <position x="215" y="2565"/>
+                  <position x="215" y="2525"/>
+                  <position x="190" y="2525"/>
+                </connection>
+              </connectionPointIn>
+              <expression>TUTU</expression>
+            </outVariable>
+            <inVariable localId="58" height="30" width="146" executionOrderId="0" negated="false">
+              <position x="60" y="1944"/>
+              <connectionPointOut>
+                <relPosition x="146" y="16"/>
+              </connectionPointOut>
+              <expression>Second_Python_Var</expression>
+            </inVariable>
+            <inVariable localId="59" height="30" width="30" executionOrderId="0" negated="false">
+              <position x="100" y="1385"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>1</expression>
+            </inVariable>
+            <block localId="61" typeName="function0" executionOrderId="0" height="45" width="111">
+              <position x="760" y="1170"/>
+              <inputVariables>
+                <variable formalParameter="LocalVar0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="62">
+                      <position x="760" y="1200"/>
+                      <position x="723" y="1200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="111" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="62" executionOrderId="0" height="30" width="58" negated="false">
+              <position x="665" y="1185"/>
+              <connectionPointOut>
+                <relPosition x="58" y="15"/>
+              </connectionPointOut>
+              <expression>fefvsd</expression>
+            </inVariable>
+            <outVariable localId="63" executionOrderId="0" height="30" width="58" negated="false">
+              <position x="905" y="1185"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="61" formalParameter="OUT">
+                  <position x="905" y="1200"/>
+                  <position x="871" y="1200"/>
+                </connection>
+              </connectionPointIn>
+              <expression>fefvsd</expression>
+            </outVariable>
+            <comment localId="53" height="80" width="420">
+              <position x="75" y="2160"/>
+              <content>
+                <xhtml:p><![CDATA[Shows global variables access from resource configuration (res_pytest) and from project's configuration.]]></xhtml:p>
+              </content>
+            </comment>
+            <inVariable localId="18" height="30" width="74" executionOrderId="0" negated="false">
+              <position x="986" y="795"/>
+              <connectionPointOut>
+                <relPosition x="74" y="15"/>
+              </connectionPointOut>
+              <expression>mux2_sel</expression>
+            </inVariable>
+            <comment localId="60" height="45" width="930">
+              <position x="60" y="1480"/>
+              <content>
+                <xhtml:p><![CDATA[Here is shown how to convert values between different types (BCD, DT, TOD, STRING and others) using standard functions.]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="64" height="55" width="300">
+              <position x="665" y="1095"/>
+              <content>
+                <xhtml:p><![CDATA[Example of usage of user-defined function.]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="65" height="45" width="410">
+              <position x="55" y="1315"/>
+              <content>
+                <xhtml:p><![CDATA[Shows access variable defined in python extension. ]]></xhtml:p>
+              </content>
+            </comment>
+            <inVariable localId="66" height="30" width="137" executionOrderId="0" negated="false">
+              <position x="60" y="1685"/>
+              <connectionPointOut>
+                <relPosition x="137" y="15"/>
+              </connectionPointOut>
+              <expression>Test_BCD_WRONG</expression>
+            </inVariable>
+            <block localId="67" width="106" height="100" typeName="BCD_TO_UINT" executionOrderId="0">
+              <position x="265" y="1620"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="40"/>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="80"/>
+                    <connection refLocalId="66">
+                      <position x="265" y="1700"/>
+                      <position x="255" y="1700"/>
+                      <position x="255" y="1700"/>
+                      <position x="345" y="1700"/>
+                      <position x="345" y="1700"/>
+                      <position x="197" y="1700"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="106" y="40"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="106" y="80"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="68" height="30" width="196" executionOrderId="0" negated="false">
+              <position x="580" y="1685"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="67" formalParameter="OUT">
+                  <position x="580" y="1700"/>
+                  <position x="371" y="1700"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_BCD_WRONG_RESULT</expression>
+            </outVariable>
+            <comment localId="69" height="165" width="375">
+              <position x="795" y="1590"/>
+              <content>
+                <xhtml:p><![CDATA[Incorrect BCD number is not converted to UINT.
+
+151 (16#97) is good BCD number , but 
+154 (16#9A) is not.  
+
+Try this out and look at value of  Test_BCD_CONVERTED variable.
+
+
+]]></xhtml:p>
+              </content>
+            </comment>
+            <outVariable localId="70" height="30" width="185" executionOrderId="0" negated="false">
+              <position x="580" y="1645"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="67" formalParameter="ENO">
+                  <position x="580" y="1660"/>
+                  <position x="370" y="1660"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_BCD_CONVERTED</expression>
+            </outVariable>
+            <comment localId="71" height="215" width="680">
+              <position x="35" y="30"/>
+              <content>
+                <xhtml:p><![CDATA[This example shows many features in Beremiz:
+
+   1. How to implement python extensions.
+   2. How to implement basic C extension.
+   3. How to use C code in IEC POUs.
+   4. How to call C functions from python code.
+   5. How to avoid race conditions between IEC, C and python code.
+   6. How to convert betweet different IEC types.
+]]></xhtml:p>
+              </content>
+            </comment>
+            <outVariable localId="72" executionOrderId="0" height="30" width="60" negated="false">
+              <position x="1065" y="1970"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="76" formalParameter="OUT">
+                  <position x="1065" y="1985"/>
+                  <position x="1025" y="1985"/>
+                  <position x="1025" y="1995"/>
+                  <position x="985" y="1995"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Grumpf</expression>
+            </outVariable>
+            <inVariable localId="73" executionOrderId="0" height="30" width="85" negated="false">
+              <position x="625" y="1940"/>
+              <connectionPointOut>
+                <relPosition x="85" y="15"/>
+              </connectionPointOut>
+              <expression>BOOL#TRUE</expression>
+            </inVariable>
+            <inVariable localId="74" executionOrderId="0" height="30" width="70" negated="false">
+              <position x="625" y="1975"/>
+              <connectionPointOut>
+                <relPosition x="70" y="15"/>
+              </connectionPointOut>
+              <expression>Test_DT</expression>
+            </inVariable>
+            <block localId="75" typeName="RTC" instanceName="RTC0" executionOrderId="0" height="90" width="65">
+              <position x="760" y="1925"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="73">
+                      <position x="760" y="1960"/>
+                      <position x="735" y="1960"/>
+                      <position x="735" y="1955"/>
+                      <position x="710" y="1955"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PDT">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="74">
+                      <position x="760" y="1995"/>
+                      <position x="727" y="1995"/>
+                      <position x="727" y="1990"/>
+                      <position x="695" y="1990"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="65" y="35"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="CDT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="70"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="76" typeName="DT_TO_STRING" executionOrderId="0" height="40" width="110">
+              <position x="875" y="1965"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="75" formalParameter="CDT">
+                      <position x="875" y="1995"/>
+                      <position x="825" y="1995"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="110" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="77" typeName="ADD" executionOrderId="0" height="60" width="65">
+              <position x="170" y="1370"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="59">
+                      <position x="170" y="1400"/>
+                      <position x="130" y="1400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="78">
+                      <position x="170" y="1420"/>
+                      <position x="160" y="1420"/>
+                      <position x="160" y="1450"/>
+                      <position x="390" y="1450"/>
+                      <position x="390" y="1400"/>
+                      <position x="380" y="1400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="47" executionOrderId="0" height="30" width="130" negated="false">
+              <position x="625" y="1335"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="79">
+                  <position x="625" y="1350"/>
+                  <position x="590" y="1350"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_Python_Var</expression>
+            </outVariable>
+            <inVariable localId="79" executionOrderId="0" height="27" width="30" negated="false">
+              <position x="560" y="1340"/>
+              <connectionPointOut>
+                <relPosition x="30" y="15"/>
+              </connectionPointOut>
+              <expression>23</expression>
+            </inVariable>
+            <inOutVariable localId="78" executionOrderId="0" height="30" width="100" negatedOut="false" negatedIn="false">
+              <position x="280" y="1385"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="77" formalParameter="OUT">
+                  <position x="280" y="1400"/>
+                  <position x="235" y="1400"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="100" y="15"/>
+              </connectionPointOut>
+              <expression>SomeVarName</expression>
+            </inOutVariable>
+            <block localId="80" typeName="MOVE" executionOrderId="0" height="40" width="60">
+              <position x="570" y="455"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="82">
+                      <position x="578" y="485"/>
+                      <position x="532" y="485"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="60" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <connector name="Connection0" localId="81" height="30" width="130">
+              <position x="545" y="390"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="4">
+                  <position x="545" y="405"/>
+                  <position x="515" y="405"/>
+                </connection>
+              </connectionPointIn>
+            </connector>
+            <continuation name="Connection0" localId="82" height="30" width="130">
+              <position x="410" y="470"/>
+              <connectionPointOut>
+                <relPosition x="130" y="15"/>
+              </connectionPointOut>
+            </continuation>
+          </FBD>
+        </body>
+      </pou>
+      <pou name="C_Pragma" pouType="functionBlock">
+        <interface>
+          <outputVars>
+            <variable name="OUT">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+          </outputVars>
+          <inputVars>
+            <variable name="IN">
+              <type>
+                <SINT/>
+              </type>
+            </variable>
+          </inputVars>
+          <localVars>
+            <variable name="COORDS">
+              <type>
+                <array>
+                  <dimension lower="0" upper="5"/>
+                  <baseType>
+                    <SINT/>
+                  </baseType>
+                </array>
+              </type>
+              <initialValue>
+                <arrayValue>
+                  <value>
+                    <simpleValue value="54"/>
+                  </value>
+                  <value>
+                    <simpleValue value="55"/>
+                  </value>
+                  <value>
+                    <simpleValue value="56"/>
+                  </value>
+                  <value>
+                    <simpleValue value="57"/>
+                  </value>
+                  <value>
+                    <simpleValue value="58"/>
+                  </value>
+                  <value>
+                    <simpleValue value="59"/>
+                  </value>
+                </arrayValue>
+              </initialValue>
+            </variable>
+            <variable name="SMURF">
+              <type>
+                <derived name="CPLX_TYPE"/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="Global_RS">
+              <type>
+                <derived name="RS"/>
+              </type>
+            </variable>
+            <variable name="Dudiduda">
+              <type>
+                <derived name="blups"/>
+              </type>
+            </variable>
+          </externalVars>
+        </interface>
+        <body>
+          <ST>
+            <xhtml:p><![CDATA[(* hereafter is a C pragma accessing FB interface in a clean way *)
+{{
+  char toPLC;
+  char fromPLC = GetFbVar(IN);
+  extern int PLC_C_Call(char, char *);
+  if(PLC_C_Call(fromPLC, &toPLC)){
+    SetFbVar(OUT, toPLC);
+  }
+  if(0){
+    /* that code demonstrate C access to complex types */
+    char somebyte = GetFbVar(COORDS, .table[3]);
+    SetFbVar(SMURF, somebyte, .FIRSTBYTE);
+    SetFbVar(COORDS, somebyte, .table[4]);
+  }
+}}
+(* If you do not use GetFbVar and SetFbVar macros, expect unexpected behaviour*)
+Global_RS();
+
+(* testing access to global struct array *)
+Dudiduda[2].FIRSTBYTE := 0;
+]]></xhtml:p>
+          </ST>
+        </body>
+      </pou>
+      <pou name="norm" pouType="function">
+        <interface>
+          <returnType>
+            <REAL/>
+          </returnType>
+          <inputVars>
+            <variable name="IN1">
+              <type>
+                <REAL/>
+              </type>
+            </variable>
+            <variable name="IN2">
+              <type>
+                <REAL/>
+              </type>
+            </variable>
+          </inputVars>
+        </interface>
+        <body>
+          <ST>
+            <xhtml:p><![CDATA[NORM := SQRT(IN1 * IN1 + IN2 * IN2);]]></xhtml:p>
+          </ST>
+        </body>
+      </pou>
+      <pou name="function0" pouType="function">
+        <interface>
+          <returnType>
+            <derived name="datatype0"/>
+          </returnType>
+          <inputVars>
+            <variable name="LocalVar0">
+              <type>
+                <derived name="datatype0"/>
+              </type>
+            </variable>
+          </inputVars>
+        </interface>
+        <body>
+          <ST>
+            <xhtml:p><![CDATA[function0 := LocalVar0;
+]]></xhtml:p>
+          </ST>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="res_pytest">
+          <task name="pytest_task" priority="0" interval="T#500ms"/>
+          <globalVars>
+            <variable name="TOTO">
+              <type>
+                <INT/>
+              </type>
+            </variable>
+          </globalVars>
+          <pouInstance name="pytest_instance" typeName="main_pytest"/>
+        </resource>
+        <globalVars>
+          <variable name="Global_RS">
+            <type>
+              <derived name="RS"/>
+            </type>
+          </variable>
+          <variable name="Dudiduda">
+            <type>
+              <derived name="blups"/>
+            </type>
+          </variable>
+          <variable name="TUTU">
+            <type>
+              <INT/>
+            </type>
+          </variable>
+        </globalVars>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- a/exemples/python/py_ext_0@py_ext/pyfile.xml	Fri Aug 18 11:38:20 2023 +0200
+++ b/exemples/python/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -6,19 +6,19 @@
   </variables>
   <globals>
     <xhtml:p><![CDATA[
-print "All python PLC globals variables :", PLCGlobalsDesc
-print "Current extention name :", __ext_name__
+print("All python PLC globals variables :", PLCGlobalsDesc)
+print("Current extention name :", __ext_name__)
 
 def MyFunc(*args):
-    print args
+    print(args)
 
 def MyOtherFunc(*args):
-    print "other", args
+    print("other", args)
 
 def SomeChange(*args):
-    print "count",OnChange.SomeVarName.count
-    print "first",OnChange.SomeVarName.first
-    print "last",OnChange.SomeVarName.last
+    print("count",OnChange.SomeVarName.count)
+    print("first",OnChange.SomeVarName.first)
+    print("last",OnChange.SomeVarName.last)
 
 
 ]]></xhtml:p>
--- a/exemples/python/python@py_ext/pyfile.xml	Fri Aug 18 11:38:20 2023 +0200
+++ b/exemples/python/python@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -1,69 +1,79 @@
-<?xml version='1.0' encoding='utf-8'?>
-<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml">
-  <variables>
-    <variable name="Test_Python_Var" type="INT" initial="4"/>
-    <variable name="Second_Python_Var" type="INT" initial="5"/>
-  </variables>
-  <globals>
-    <xhtml:p><![CDATA[
-import time,sys,ctypes
-Python_to_C_Call = PLCBinary.Python_to_C_Call
-Python_to_C_Call.restype = ctypes.c_int
-Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
-
-def MyPythonFunc(arg):
-    i = ctypes.c_int()
-    if(Python_to_C_Call(arg, i)):
-        res = i.value
-        print "toC:", arg, "from C:", res, "FBID:", FBID
-    else:
-        print "Failed Python_to_C_Call failed"
-        res = None
-    print "Python read PLC global :",PLCGlobals.Test_Python_Var
-    print "Python read PLC global Grumpf :",PLCGlobals.Grumpf
-    PLCGlobals.Second_Python_Var = 789
-    sys.stdout.flush()
-    return res
-
-async_error_test_code = """
-def badaboom():
-    tuple()[0]
-
-import wx
-def badaboomwx():
-    wx.CallAfter(badaboom)
-
-from threading import Timer
-a = Timer(3, badaboom)
-a.start()
-
-b = Timer(6, badaboomwx)
-b.start()
-"""
-]]></xhtml:p>
-  </globals>
-  <init>
-    <xhtml:p><![CDATA[
-global x, y
-x = 2
-y = 5
-print "py_runtime init:", x, ",", y
-]]></xhtml:p>
-  </init>
-  <cleanup>
-    <xhtml:p><![CDATA[
-print "py_runtime cleanup"
-]]></xhtml:p>
-  </cleanup>
-  <start>
-    <xhtml:p><![CDATA[
-global x, y
-print "py_runtime start", x * x + y * y
-]]></xhtml:p>
-  </start>
-  <stop>
-    <xhtml:p><![CDATA[
-print "py_runtime stop"
-]]></xhtml:p>
-  </stop>
-</PyFile>
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml">
+  <variables>
+    <variable name="Test_Python_Var" type="INT" initial="4"/>
+    <variable name="Second_Python_Var" type="INT" initial="5"/>
+  </variables>
+  <globals>
+    <xhtml:p><![CDATA[
+import time,sys,ctypes
+Python_to_C_Call = PLCBinary.Python_to_C_Call
+Python_to_C_Call.restype = ctypes.c_int
+Python_to_C_Call.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
+
+
+def MyPrintFunction(msg):
+    if sys.stdout:
+        sys.stdout.write(msg)
+        sys.stdout.flush()
+    else:
+        PLCObject.LogMessage(msg)
+
+def MyPythonFunc(arg):
+    i = ctypes.c_int()
+    if(Python_to_C_Call(arg, i)):
+        res = i.value
+        print("toC:", arg, "from C:", res, "FBID:", FBID)
+    else:
+        print("Failed Python_to_C_Call failed")
+        res = None
+    print("Python read PLC global :",PLCGlobals.Test_Python_Var)
+    print("Python read PLC global Grumpf :",PLCGlobals.Grumpf)
+    PLCGlobals.Second_Python_Var = 789
+
+    if sys.stdout:
+        sys.stdout.flush()
+    return res
+
+async_error_test_code = """
+def badaboom():
+    tuple()[0]
+
+import wx
+def badaboomwx():
+    wx.CallAfter(badaboom)
+
+from threading import Timer
+a = Timer(3, badaboom)
+a.start()
+
+b = Timer(6, badaboomwx)
+b.start()
+"""
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+global x, y
+x = 2
+y = 5
+print("py_runtime init:", x, ",", y)
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+print("py_runtime cleanup")
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+global x, y
+print("py_runtime start", x * x + y * y)
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+print("py_runtime stop")
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- a/exemples/svghmi_traffic_light/svghmi_0@svghmi/svghmi.svg	Fri Aug 18 11:38:20 2023 +0200
+++ b/exemples/svghmi_traffic_light/svghmi_0@svghmi/svghmi.svg	Wed Oct 04 16:24:00 2023 +0200
@@ -1251,14 +1251,14 @@
      inkscape:pageopacity="0.0"
      inkscape:pageshadow="2"
      inkscape:zoom="1.979899"
-     inkscape:cx="205.65994"
-     inkscape:cy="103.00174"
+     inkscape:cx="52.116754"
+     inkscape:cy="96.940825"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="false"
      units="px"
-     inkscape:window-width="1600"
-     inkscape:window-height="836"
+     inkscape:window-width="3840"
+     inkscape:window-height="2096"
      inkscape:window-x="0"
      inkscape:window-y="27"
      inkscape:window-maximized="1"
@@ -1274,7 +1274,7 @@
         <dc:format>image/svg+xml</dc:format>
         <dc:type
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
+        <dc:title />
       </cc:Work>
     </rdf:RDF>
   </metadata>
@@ -1283,6 +1283,14 @@
      inkscape:groupmode="layer"
      id="layer1"
      transform="translate(37.474617,-760.93329)">
+    <rect
+       style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+       id="rect250"
+       width="320"
+       height="240"
+       x="-37.474617"
+       y="760.93329"
+       inkscape:label="HMI:Page:Home" />
     <path
        style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#282828;fill-opacity:1;stroke:none;stroke-width:2.04116011;marker:none;enable-background:accumulate"
        d="m 114.28125,14.28125 v 130 h 18.9375 v 93.5625 h 5.71875 V 176.4375 h 8.90625 v 15.71875 h 36.4375 v -32.5 h -36.4375 v 12.125 h -8.90625 v -27.5 h 21.78125 v -130 z"
@@ -1529,13 +1537,5 @@
          x="62.818459"
          y="812.17749"
          style="font-size:6.17188501px;line-height:1.25;font-family:sans-serif">ON</tspan></text>
-    <rect
-       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
-       id="rect250"
-       width="320"
-       height="240"
-       x="-37.474617"
-       y="760.93329"
-       inkscape:label="HMI:Page:Home" />
   </g>
 </svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fake_wx.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+from types import ModuleType
+
+# TODO use gettext instead
+def get_translation(txt):
+    return txt
+
+
+class FakeObject:
+    def __init__(self, *args, **kwargs):
+        if "__classname__" in kwargs:
+            self.__classname__ = kwargs["__classname__"]
+
+    def __getattr__(self,name):
+        if name.startswith('__'):
+            raise AttributeError(name)
+        return FakeObject(__classname__=self.__classname__+"."+name)
+
+    def __call__(self, *args, **kwargs):
+        return FakeObject(__classname__=self.__classname__+"()")
+
+    def __getitem__(self, key):
+        return FakeObject(__classname__=self.__classname__+"["+repr(key)+"]")
+
+    def __str__(self):
+        return self.__classname__
+
+    def __or__(self, other):
+        return FakeObject(__classname__=self.__classname__+"|"+other.__classname__)
+    
+    def __hash__(self) -> int:
+        return id(self)
+
+    def __cmp__(self,other):
+        return True
+    __lt__=__cmp__
+    __le__=__cmp__
+    __eq__=__cmp__
+    __ne__=__cmp__
+    __gt__=__cmp__
+    __ge__=__cmp__
+
+class FakeClass:
+    def __init__(self, *args, **kwargs):
+        print(("DUMMY Class __init__ !",self.__name__,args,kwargs))
+
+
+class FakeModule(ModuleType):
+    def __init__(self, name, classes):
+        self.__modname__ = name
+        self.__objects__ = dict([(desc, type(desc, (FakeClass,), {}))
+            if type(desc)==str else desc for desc in classes])
+        ModuleType(name)
+
+    def __getattr__(self,name):
+        if name.startswith('__'):
+            raise AttributeError(name)
+  
+        if name in self.__objects__:
+            return self.__objects__[name]
+  
+        obj = FakeObject(__classname__=self.__modname__+"."+name)
+        self.__objects__[name] = obj
+        return obj
+
+
+# Keep track of already faked modules to catch those
+# that are already present in sys.modules from start
+# (i.e. mpl_toolkits for exemple)
+already_patched = {}
+
+for name, classes in [
+    # list given for each module name contains name string for a FakeClass,
+    # otherwise a tuple (name, object) for arbitrary object/function/class
+    ('wx',[
+        'Panel', 'PyCommandEvent', 'Dialog', 'PopupWindow', 'TextEntryDialog',
+        'Notebook', 'ListCtrl', 'TextDropTarget', 'PyControl', 'TextCtrl', 
+        'SplitterWindow', 'Frame', 'Printout', 'StaticBitmap', 'DropTarget',
+        ('GetTranslation', get_translation)]),
+    ('wx.lib.agw.advancedsplash',[]),
+    ('wx.dataview',['DataViewIndexListModel', 'PyDataViewIndexListModel']),
+    ('wx.lib.buttons',['GenBitmapTextButton']),
+    ('wx.adv',['EditableListBox']),
+    ('wx.grid',[
+        'Grid', 'PyGridTableBase', 'GridCellEditor', 'GridCellTextEditor',
+        'GridCellChoiceEditor']),
+    ('wx.lib.agw.customtreectrl',['CustomTreeCtrl']),
+    ('wx.lib.gizmos',[]),
+    ('wx.lib.intctrl',['IntCtrl']),
+    ('matplotlib.pyplot',[]),
+    ('matplotlib.backends.backend_wxagg',['FigureCanvasWxAgg']),
+    ('wx.stc',['StyledTextCtrl']),
+    ('wx.lib.scrolledpanel',[]),
+    ('wx.lib.mixins.listctrl',['ColumnSorterMixin', 'ListCtrlAutoWidthMixin']),
+    ('wx.dataview',['PyDataViewIndexListModel']),
+    ('matplotlib.backends.backend_agg',[]),
+    ('wx.aui',[]),
+    ('wx.html',['HtmlWindow']),
+    ('mpl_toolkits.mplot3d',[])]:
+    modpath = None
+    parentmod = None
+    for identifier in name.split("."):
+        modpath = (modpath + "." + identifier) if modpath else identifier
+        mod = sys.modules.get(modpath, None)
+
+        if mod is None or modpath not in already_patched:
+            mod = FakeModule(modpath, classes)
+            sys.modules[modpath] = mod
+            already_patched[modpath] = True
+
+        if parentmod is not None:
+            parentmod.__objects__[identifier] = mod
+
+        parentmod = mod
+
+import builtins
+
+builtins.__dict__['_'] = get_translation
+
--- a/graphics/DebugDataConsumer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/DebugDataConsumer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+
+
 import datetime
 
 
--- a/graphics/FBD_Objects.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/FBD_Objects.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,10 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 import wx
-from six.moves import xrange
 
 from graphics.GraphicCommons import *
 from plcopen.structures import *
@@ -78,7 +75,7 @@
         return block
 
     def GetConnectorTranslation(self, element):
-        return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
+        return dict(list(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs)))
 
     def Flush(self):
         for input in self.Inputs:
@@ -122,10 +119,10 @@
     # Returns if the point given is in the bounding box
     def HitTest(self, pt, connectors=True):
         if self.Name != "":
-            test_text = self.GetTextBoundingBox().InsideXY(pt.x, pt.y)
+            test_text = self.GetTextBoundingBox().Contains(pt.x, pt.y)
         else:
             test_text = False
-        test_block = self.GetBlockBoundingBox(connectors).InsideXY(pt.x, pt.y)
+        test_block = self.GetBlockBoundingBox(connectors).Contains(pt.x, pt.y)
         return test_text or test_block
 
     # Returns the bounding box of the name outside the block
@@ -165,7 +162,7 @@
             linesize = max((self.Size[1] - BLOCK_LINE_SIZE) // lines, BLOCK_LINE_SIZE)
             # Update inputs and outputs positions
             position = BLOCK_LINE_SIZE + linesize // 2
-            for i in xrange(lines):
+            for i in range(lines):
                 if scaling is not None:
                     ypos = round_scaling(self.Pos.y + position, scaling[1]) - self.Pos.y
                 else:
@@ -256,7 +253,7 @@
                 outputs = [output for output in blocktype["outputs"]]
                 if blocktype["extensible"]:
                     start = int(inputs[-1][0].replace("IN", ""))
-                    for dummy in xrange(self.Extension - len(blocktype["inputs"])):
+                    for dummy in range(self.Extension - len(blocktype["inputs"])):
                         start += 1
                         inputs.append(("IN%d" % start, inputs[-1][1], inputs[-1][2]))
                 comment = blocktype["comment"]
@@ -392,7 +389,7 @@
 #            pos = event.GetLogicalPosition(dc)
 #            for input in self.Inputs:
 #                rect = input.GetRedrawRect()
-#                if rect.InsideXY(pos.x, pos.y):
+#                if rect.Contains(pos.x, pos.y):
 #                    print "Find input"
 #                    tip = wx.TipWindow(self.Parent, "Test")
 #                    tip.SetBoundingRect(rect)
@@ -451,7 +448,7 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            highlight_items = self.Highlights.items()
+            highlight_items = list(self.Highlights.items())
             for name, highlights in highlight_items:
                 highlights = ClearHighlights(highlights, highlight_type)
                 if len(highlights) == 0:
@@ -771,6 +768,7 @@
         Graphic_Element.Draw(self, dc)
         dc.SetPen(MiterPen(wx.BLACK))
         dc.SetBrush(wx.WHITE_BRUSH)
+        dc.SetTextForeground(wx.BLACK)
 
         if getattr(dc, "printing", False):
             name_size = dc.GetTextExtent(self.Name)
@@ -1011,6 +1009,7 @@
         Graphic_Element.Draw(self, dc)
         dc.SetPen(MiterPen(wx.BLACK))
         dc.SetBrush(wx.WHITE_BRUSH)
+        dc.SetTextForeground(wx.BLACK)
 
         if getattr(dc, "printing", False):
             name_size = dc.GetTextExtent(self.Name)
--- a/graphics/GraphicCommons.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/GraphicCommons.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,12 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 from math import *
-from future.builtins import round
-from six import string_types
-from six.moves import xrange
 
 import wx
 from graphics.ToolTipProducer import ToolTipProducer
@@ -68,15 +63,15 @@
 SFC_ACTION_MIN_SIZE = (100, 30)         # Minimum size of an action block line
 
 # Type definition constants for graphic elements
-[INPUT, OUTPUT, INOUT] = range(3)
-[CONNECTOR, CONTINUATION] = range(2)
-[LEFTRAIL, RIGHTRAIL] = range(2)
-[CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = range(4)
-[COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET, COIL_RISING, COIL_FALLING] = range(6)
-[SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = range(4)
+[INPUT, OUTPUT, INOUT] = list(range(3))
+[CONNECTOR, CONTINUATION] = list(range(2))
+[LEFTRAIL, RIGHTRAIL] = list(range(2))
+[CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = list(range(4))
+[COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET, COIL_RISING, COIL_FALLING] = list(range(6))
+[SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = list(range(4))
 
 # Constants for defining the type of dragging that has been selected
-[HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = range(5)
+[HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = list(range(5))
 
 # List of value for resize handle that are valid
 VALID_HANDLES = [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
@@ -87,10 +82,10 @@
 # Contants for defining which mode is selected for each view
 [MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT,
  MODE_COIL, MODE_CONTACT, MODE_POWERRAIL, MODE_INITIALSTEP, MODE_STEP,
- MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = range(15)
+ MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = list(range(15))
 
 # Contants for defining alignment types for graphic group
-[ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = range(6)
+[ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = list(range(6))
 
 # Contants for defining which drawing mode is selected for app
 [FREEDRAWING_MODE, DRIVENDRAWING_MODE] = [1, 2]
@@ -99,8 +94,8 @@
 HIGHLIGHTCOLOR = wx.CYAN
 
 # Define highlight types
-ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0), wx.RED)
-SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0), wx.WHITE)
+ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0).GetIM(), wx.RED.GetIM())
+SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0).GetIM(), wx.WHITE.GetIM())
 
 # Define highlight refresh inhibition period in second
 REFRESH_HIGHLIGHT_PERIOD = 0.1
@@ -142,6 +137,10 @@
     return vector
 
 
+def ivector(*a,**k):
+    return tuple(map(round, vector(*a,**k)))
+
+
 def norm(v):
     """
     Calculate the norm of a given vector
@@ -188,8 +187,9 @@
     """
     pos = event.GetLogicalPosition(dc)
     if scaling:
-        pos.x = round(pos.x / scaling[0]) * scaling[0]
-        pos.y = round(pos.y / scaling[1]) * scaling[1]
+        sx,sy=tuple(map(round,scaling))
+        pos.x = round(pos.x / sx) * sx
+        pos.y = round(pos.y / sy) * sy
     return pos
 
 
@@ -206,7 +206,7 @@
 
 
 def MiterPen(colour, width=1, style=wx.SOLID):
-    pen = wx.Pen(colour, width, style)
+    pen = wx.Pen(colour, round(width), style)
     pen.SetJoin(wx.JOIN_MITER)
     pen.SetCap(wx.CAP_PROJECTING)
     return pen
@@ -299,7 +299,7 @@
             distances.append((sqrt((self.Pos.x + connector_pos.x - position.x) ** 2 +
                                    (self.Pos.y + connector_pos.y - position.y) ** 2),
                               connector))
-        distances.sort()
+        distances.sort(key=lambda n: n[0])
         if len(distances) > 0:
             return distances[0][1]
         return None
@@ -319,8 +319,8 @@
 
     # Changes the block position
     def SetPosition(self, x, y):
-        self.Pos.x = x
-        self.Pos.y = y
+        self.Pos.x = int(x)
+        self.Pos.y = int(y)
         self.RefreshConnected()
         self.RefreshBoundingBox()
 
@@ -330,8 +330,8 @@
 
     # Changes the element size
     def SetSize(self, width, height):
-        self.Size.SetWidth(width)
-        self.Size.SetHeight(height)
+        self.Size.SetWidth(int(width))
+        self.Size.SetHeight(int(height))
         self.RefreshConnectors()
         self.RefreshBoundingBox()
 
@@ -388,11 +388,11 @@
             rect = self.BoundingBox
         else:
             rect = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
-        return rect.InsideXY(pt.x, pt.y)
+        return rect.Contains(pt.x, pt.y)
 
     # Returns if the point given is in the bounding box
     def IsInSelection(self, rect):
-        return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
+        return rect.Contains(self.BoundingBox.x, self.BoundingBox.y) and rect.Contains(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
 
     # Override this method for refreshing the bounding box
     def RefreshBoundingBox(self):
@@ -436,19 +436,19 @@
         pos = event.GetPosition()
         pt = wx.Point(*self.Parent.CalcUnscrolledPosition(pos.x, pos.y))
 
-        left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
-        center = (self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2
-        right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
-
-        top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
-        middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE // 2
-        bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
+        left = round((self.BoundingBox.x - 2) * scalex - HANDLE_SIZE)
+        center = round((self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2)
+        right = round((self.BoundingBox.x + self.BoundingBox.width + 2) * scalex)
+
+        top = round((self.BoundingBox.y - 2) * scaley - HANDLE_SIZE)
+        middle = round((self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE // 2)
+        bottom = round((self.BoundingBox.y + self.BoundingBox.height + 2) * scaley)
 
         extern_rect = wx.Rect(left, top, right + HANDLE_SIZE - left, bottom + HANDLE_SIZE - top)
         intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
 
         # Verify that this element is selected
-        if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
+        if self.Selected and extern_rect.Contains(pt.x, pt.y) and not intern_rect.Contains(pt.x, pt.y):
             # Find if point is on a handle horizontally
             if left <= pt.x < left + HANDLE_SIZE:
                 handle_x = 1
@@ -684,13 +684,13 @@
                 dc.SetPen(MiterPen(wx.BLACK))
                 dc.SetBrush(wx.BLACK_BRUSH)
 
-                left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
-                center = (self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2
-                right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
-
-                top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
-                middle = (self.BoundingBox.y + self.BoundingBox.height // 2) * scaley - HANDLE_SIZE // 2
-                bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
+                left = round((self.BoundingBox.x - 2) * scalex - HANDLE_SIZE)
+                center = round((self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2)
+                right = round((self.BoundingBox.x + self.BoundingBox.width + 2) * scalex)
+
+                top = round((self.BoundingBox.y - 2) * scaley - HANDLE_SIZE)
+                middle = round((self.BoundingBox.y + self.BoundingBox.height // 2) * scaley - HANDLE_SIZE // 2)
+                bottom = round((self.BoundingBox.y + self.BoundingBox.height + 2) * scaley)
 
                 for x, y in [(left, top), (center, top), (right, top),
                              (left, middle), (right, middle),
@@ -717,10 +717,6 @@
         self.RefreshWireExclusion()
         self.RefreshBoundingBox()
 
-    # Destructor
-    def __del__(self):
-        self.Elements = []
-
     def GetDefinition(self):
         blocks = []
         wires = []
@@ -1084,7 +1080,7 @@
                 y -= 5
                 height += 5
         rect = wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
-        if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
+        if self.ValueSize is None and isinstance(self.ComputedValue, str):
             self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
         if self.ValueSize is not None:
             width, height = self.ValueSize
@@ -1401,7 +1397,7 @@
             width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
             height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
             rect = wx.Rect(x, y, width, height)
-            inside = rect.InsideXY(pt.x, pt.y)
+            inside = rect.Contains(pt.x, pt.y)
 
         return inside
 
@@ -1481,7 +1477,7 @@
                 else:
                     dc.SetPen(MiterPen(wx.GREEN))
             elif self.Value == "undefined":
-                dc.SetPen(MiterPen(wx.NamedColour("orange")))
+                dc.SetPen(MiterPen(wx.Colour("orange")))
             elif self.Forced:
                 dc.SetPen(MiterPen(wx.BLUE))
             else:
@@ -1541,8 +1537,8 @@
 
         if self.Value is not None and not isinstance(self.Value, bool) and self.Value != "undefined":
             dc.SetFont(self.ParentBlock.Parent.GetMiniFont())
-            dc.SetTextForeground(wx.NamedColour("purple"))
-            if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
+            dc.SetTextForeground(wx.Colour("purple"))
+            if self.ValueSize is None and isinstance(self.ComputedValue, str):
                 self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
             if self.ValueSize is not None:
                 width, height = self.ValueSize
@@ -1610,7 +1606,7 @@
             rect = rect.Union(self.StartConnected.GetRedrawRect(movex, movey))
         if self.EndConnected:
             rect = rect.Union(self.EndConnected.GetRedrawRect(movex, movey))
-        if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
+        if self.ValueSize is None and isinstance(self.ComputedValue, str):
             self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
         if self.ValueSize is not None:
             width, height = self.ValueSize
@@ -1918,7 +1914,7 @@
     # Returns if the point given is on one of the wire segments
     def HitTest(self, pt, connectors=True):
         test = False
-        for i in xrange(len(self.Points) - 1):
+        for i in range(len(self.Points) - 1):
             rect = wx.Rect(0, 0, 0, 0)
             if i == 0 and self.StartConnected is not None:
                 x1 = self.Points[i].x - self.Segments[0][0] * CONNECTOR_SIZE
@@ -1933,7 +1929,7 @@
             # Calculate a rectangle around the segment
             rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
                            abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
-            test |= rect.InsideXY(pt.x, pt.y)
+            test |= rect.Contains(pt.x, pt.y)
         return test
 
     # Returns the wire start or end point if the point given is on one of them
@@ -1941,19 +1937,19 @@
         # Test the wire start point
         rect = wx.Rect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
                        2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
-        if rect.InsideXY(pt.x, pt.y):
+        if rect.Contains(pt.x, pt.y):
             return 0
         # Test the wire end point
         if len(self.Points) > 1:
             rect = wx.Rect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
                            2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
-            if rect.InsideXY(pt.x, pt.y):
+            if rect.Contains(pt.x, pt.y):
                 return -1
         return None
 
     # Returns the wire segment if the point given is on it
     def TestSegment(self, pt, all=False):
-        for i in xrange(len(self.Segments)):
+        for i in range(len(self.Segments)):
             # If wire is not in a Ladder Diagram, first and last segments are excluded
             if all or 0 < i < len(self.Segments) - 1:
                 x1, y1 = self.Points[i].x, self.Points[i].y
@@ -1961,7 +1957,7 @@
                 # Calculate a rectangle around the segment
                 rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
                                abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
-                if rect.InsideXY(pt.x, pt.y):
+                if rect.Contains(pt.x, pt.y):
                     return i, self.Segments[i]
         return None
 
@@ -1984,13 +1980,13 @@
                 lx, ly = x, y
 
             # Calculate the start and end directions
-            self.StartPoint = [None, vector(self.Points[0], self.Points[1])]
-            self.EndPoint = [None, vector(self.Points[-1], self.Points[-2])]
+            self.StartPoint = [None, ivector(self.Points[0], self.Points[1])]
+            self.EndPoint = [None, ivector(self.Points[-1], self.Points[-2])]
             # Calculate the start and end points
-            self.StartPoint[0] = wx.Point(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0],
-                                          self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
-            self.EndPoint[0] = wx.Point(self.Points[-1].x + CONNECTOR_SIZE * self.EndPoint[1][0],
-                                        self.Points[-1].y + CONNECTOR_SIZE * self.EndPoint[1][1])
+            self.StartPoint[0] = wx.Point(self.Points[0].x + round(CONNECTOR_SIZE * self.StartPoint[1][0]),
+                                          self.Points[0].y + round(CONNECTOR_SIZE * self.StartPoint[1][1]))
+            self.EndPoint[0] = wx.Point(self.Points[-1].x + round(CONNECTOR_SIZE * self.EndPoint[1][0]),
+                                        self.Points[-1].y + round(CONNECTOR_SIZE * self.EndPoint[1][1]))
             self.Points[0] = self.StartPoint[0]
             self.Points[-1] = self.EndPoint[0]
             # Calculate the segments directions
@@ -2001,7 +1997,7 @@
                 if i > lp - 2:
                     break
 
-                segment = vector(self.Points[i], self.Points[i + 1])
+                segment = ivector(self.Points[i], self.Points[i + 1])
 
                 # merge segment if requested
                 if merge_segments and 0 < i and \
@@ -2014,7 +2010,7 @@
 
                 # remove corner when two segments are in opposite direction
                 if i < lp - 2:
-                    next = vector(self.Points[i + 1], self.Points[i + 2])
+                    next = ivector(self.Points[i + 1], self.Points[i + 2])
                     if is_null_vector(add_vectors(segment, next)):
                         self.Points.pop(i+1)
                         continue
@@ -2035,10 +2031,10 @@
     # Returns a list of the position of all wire points
     def GetPoints(self, invert=False):
         points = self.VerifyPoints()
-        points[0] = wx.Point(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0],
-                             points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
-        points[-1] = wx.Point(points[-1].x - CONNECTOR_SIZE * self.EndPoint[1][0],
-                              points[-1].y - CONNECTOR_SIZE * self.EndPoint[1][1])
+        points[0] = wx.Point(points[0].x - round(CONNECTOR_SIZE * self.StartPoint[1][0]),
+                             points[0].y - round(CONNECTOR_SIZE * self.StartPoint[1][1]))
+        points[-1] = wx.Point(points[-1].x - round(CONNECTOR_SIZE * self.EndPoint[1][0]),
+                              points[-1].y - round(CONNECTOR_SIZE * self.EndPoint[1][1]))
         # An inversion of the list is asked
         if invert:
             points.reverse()
@@ -2077,10 +2073,10 @@
     def GeneratePoints(self, realpoints=True):
         i = 0
         # Calculate the start enad end points with the minimum segment size in the right direction
-        end = wx.Point(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
-                       self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
-        start = wx.Point(self.StartPoint[0].x + self.StartPoint[1][0] * MIN_SEGMENT_SIZE,
-                         self.StartPoint[0].y + self.StartPoint[1][1] * MIN_SEGMENT_SIZE)
+        end = wx.Point(self.EndPoint[0].x + round(self.EndPoint[1][0] * MIN_SEGMENT_SIZE),
+                       self.EndPoint[0].y + round(self.EndPoint[1][1] * MIN_SEGMENT_SIZE))
+        start = wx.Point(self.StartPoint[0].x + round(self.StartPoint[1][0] * MIN_SEGMENT_SIZE),
+                         self.StartPoint[0].y + round(self.StartPoint[1][1] * MIN_SEGMENT_SIZE))
         # Evaluate the point till it's the last
         while i < len(self.Points) - 1:
             # The next point is the last
@@ -2233,7 +2229,7 @@
         i = 1
         while i < len(points) - 1:
             if points[i] == points[i + 1] and segments[i - 1] == segments[i + 1]:
-                for dummy in xrange(2):
+                for dummy in range(2):
                     points.pop(i)
                     segments.pop(i)
             else:
@@ -2295,8 +2291,8 @@
             # during a resize dragging
             for i, point in enumerate(self.RealPoints):
                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
-                    point[0] = point[0] * width / max(lastwidth, 1)
-                    point[1] = point[1] * height / max(lastheight, 1)
+                    point[0] = int(point[0] * width / max(lastwidth, 1))
+                    point[1] = int(point[1] * height / max(lastheight, 1))
             # Calculate the correct position of the points from real points
             for i, point in enumerate(self.RealPoints):
                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
@@ -2459,7 +2455,7 @@
         handle_type, handle = self.Handle
         if handle_type == HANDLE_SEGMENT:
             segment, _dir = handle
-            for dummy in xrange(2):
+            for dummy in range(2):
                 self.Points.pop(segment)
                 self.Segments.pop(segment)
             self.GeneratePoints()
@@ -2680,9 +2676,9 @@
         if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
             dc.DrawCircle(round(self.Points[0].x * scalex),
                           round(self.Points[0].y * scaley),
-                          (POINT_RADIUS + 1) * scalex + 2)
+                          round((POINT_RADIUS + 1) * scalex + 2))
         if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
-            dc.DrawCircle(self.Points[-1].x * scalex, self.Points[-1].y * scaley, (POINT_RADIUS + 1) * scalex + 2)
+            dc.DrawCircle(round(self.Points[-1].x * scalex), round(self.Points[-1].y * scaley), round((POINT_RADIUS + 1) * scalex + 2))
         # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
         if len(self.Points) > 1:
             points = [wx.Point(round((self.Points[0].x - self.Segments[0][0]) * scalex),
@@ -2717,8 +2713,8 @@
                 dc.SetPen(MiterPen(wx.GREEN))
                 dc.SetBrush(wx.GREEN_BRUSH)
         elif self.Value == "undefined":
-            dc.SetPen(MiterPen(wx.NamedColour("orange")))
-            dc.SetBrush(wx.Brush(wx.NamedColour("orange")))
+            dc.SetPen(MiterPen(wx.Colour("orange")))
+            dc.SetBrush(wx.Brush(wx.Colour("orange")))
         elif self.Forced:
             dc.SetPen(MiterPen(wx.BLUE))
             dc.SetBrush(wx.BLUE_BRUSH)
@@ -2749,8 +2745,8 @@
                         self.Points[self.SelectedSegment + 1].x + end, self.Points[self.SelectedSegment + 1].y)
         if self.Value is not None and not isinstance(self.Value, bool) and self.Value != "undefined":
             dc.SetFont(self.Parent.GetMiniFont())
-            dc.SetTextForeground(wx.NamedColour("purple"))
-            if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
+            dc.SetTextForeground(wx.Colour("purple"))
+            if self.ValueSize is None and isinstance(self.ComputedValue, str):
                 self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
             if self.ValueSize is not None:
                 width, height = self.ValueSize
@@ -2931,12 +2927,12 @@
         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
         dc.SetLogicalFunction(wx.AND)
 
-        left = (self.Pos.x - 1) * scalex - 2
-        right = (self.Pos.x + self.Size[0] + 1) * scalex + 2
-        top = (self.Pos.y - 1) * scaley - 2
-        bottom = (self.Pos.y + self.Size[1] + 1) * scaley + 2
-        angle_top = (self.Pos.x + self.Size[0] - 9) * scalex + 2
-        angle_right = (self.Pos.y + 9) * scaley - 2
+        left = round((self.Pos.x - 1) * scalex - 2)
+        right = round((self.Pos.x + self.Size[0] + 1) * scalex + 2)
+        top = round((self.Pos.y - 1) * scaley - 2)
+        bottom = round((self.Pos.y + self.Size[1] + 1) * scaley + 2)
+        angle_top = round((self.Pos.x + self.Size[0] - 9) * scalex + 2)
+        angle_right = round((self.Pos.y + 9) * scaley - 2)
 
         polygon = [wx.Point(left, top), wx.Point(angle_top, top),
                    wx.Point(right, angle_right), wx.Point(right, bottom),
--- a/graphics/LD_Objects.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/LD_Objects.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,11 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
+from functools import cmp_to_key
+from operator import eq
 import wx
-from future.builtins import round
-from six.moves import xrange
 
 from graphics.GraphicCommons import *
 from graphics.DebugDataConsumer import DebugDataConsumer
@@ -73,8 +71,8 @@
         return powerrail
 
     def GetConnectorTranslation(self, element):
-        return dict(zip([connector for connector in self.Connectors],
-                        [connector for connector in element.Connectors]))
+        return dict(list(zip([connector for connector in self.Connectors],
+                        [connector for connector in element.Connectors])))
 
     # Returns the RedrawRect
     def GetRedrawRect(self, movex=0, movey=0):
@@ -161,7 +159,7 @@
             for connect in self.Connectors:
                 connect_pos = connect.GetRelPosition()
                 connect.SetPosition(wx.Point(connect_pos.x, connect_pos.y - miny))
-        self.Connectors.sort(lambda x, y: cmp(x.Pos.y, y.Pos.y))
+        self.Connectors.sort(key=cmp_to_key(lambda x, y: eq(x.Pos.y, y.Pos.y)))
         maxy = 0
         for connect in self.Connectors:
             connect_pos = connect.GetRelPosition()
@@ -238,7 +236,7 @@
             self.Type = type
             self.Clean()
             self.Connectors = []
-            for dummy in xrange(connectors):
+            for dummy in range(connectors):
                 self.AddConnector()
             self.RefreshSize()
 
@@ -255,7 +253,7 @@
                 position = connector.GetRelPosition()
                 self.RealConnectors.append(max(0., min((position.y - self.Extensions[0]) / height, 1.)))
         elif len(self.Connectors) > 1:
-            self.RealConnectors = map(lambda x: x * 1 / (len(self.Connectors) - 1), xrange(len(self.Connectors)))
+            self.RealConnectors = [x * 1 / (len(self.Connectors) - 1) for x in range(len(self.Connectors))]
         else:
             self.RealConnectors = [0.5]
         Graphic_Element.OnLeftDown(self, event, dc, scaling)
@@ -626,7 +624,7 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            highlight_items = self.Highlights.items()
+            highlight_items = list(self.Highlights.items())
             for name, highlights in highlight_items:
                 highlights = ClearHighlights(highlights, highlight_type)
                 if len(highlights) == 0:
@@ -687,7 +685,7 @@
         self.Output.Draw(dc)
 
         if not getattr(dc, "printing", False):
-            for name, highlights in self.Highlights.iteritems():
+            for name, highlights in self.Highlights.items():
                 if name == "reference":
                     DrawHighlightedText(dc, self.Name, highlights, name_pos[0], name_pos[1])
                 elif typetext != "":
@@ -948,7 +946,7 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            highlight_items = self.Highlights.items()
+            highlight_items = list(self.Highlights.items())
             for name, highlights in highlight_items:
                 highlights = ClearHighlights(highlights, highlight_type)
                 if len(highlights) == 0:
@@ -1016,7 +1014,7 @@
         self.Output.Draw(dc)
 
         if not getattr(dc, "printing", False):
-            for name, highlights in self.Highlights.iteritems():
+            for name, highlights in self.Highlights.items():
                 if name == "reference":
                     DrawHighlightedText(dc, self.Name, highlights, name_pos[0], name_pos[1])
                 elif typetext != "":
--- a/graphics/RubberBand.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/RubberBand.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from graphics.GraphicCommons import GetScaledEventPosition
@@ -94,7 +94,7 @@
 
         # Change viewer mouse cursor to reflect a rubberband bounding box is
         # edited
-        self.DrawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
+        self.DrawingSurface.SetCursor(wx.Cursor(wx.CURSOR_CROSS))
 
         self.Redraw()
 
@@ -139,6 +139,11 @@
 
         self.Redraw()
 
+    def SetRubberBandPen(self, dc):
+        # Set DC drawing style
+        dc.SetPen(wx.Pen(wx.WHITE, style=wx.DOT))
+        dc.SetLogicalFunction(wx.XOR)
+
     def DrawBoundingBoxes(self, bboxes, dc=None):
         """
         Draw a list of bounding box on Viewer in the order given using XOR
@@ -155,17 +160,16 @@
         scalex, scaley = dc.GetUserScale()
         dc.SetUserScale(1, 1)
 
-        # Set DC drawing style
-        dc.SetPen(wx.Pen(wx.WHITE, style=wx.DOT))
+        self.SetRubberBandPen(dc)
+
         dc.SetBrush(wx.TRANSPARENT_BRUSH)
-        dc.SetLogicalFunction(wx.XOR)
 
         # Draw the bounding boxes using viewer scale factor
         for bbox in bboxes:
             if bbox is not None:
                 dc.DrawRectangle(
-                    bbox.x * scalex, bbox.y * scaley,
-                    bbox.width * scalex, bbox.height * scaley)
+                    round(bbox.x * scalex), round(bbox.y * scaley),
+                    round(bbox.width * scalex), round(bbox.height * scaley))
 
         dc.SetLogicalFunction(wx.COPY)
 
@@ -195,3 +199,29 @@
         """
         # Erase last bbox and draw current bbox
         self.DrawBoundingBoxes([self.CurrentBBox], dc)
+
+
+def PatchRubberBandForGTK3():
+    """
+    GTK3 implementation of DC doesn't support SetLogicalFuntion(XOR)
+    Then Rubberband can't be erased by just redrawing it on the same place
+    So this is a complete refresh instead, eating a lot of CPU.
+    """
+    def Redraw(self, dc=None):
+        self.Viewer.Refresh()
+        self.Draw()
+
+    RubberBand.Redraw = Redraw
+
+    def Erase(self, dc=None):
+        self.Viewer.Refresh()
+
+    RubberBand.Erase = Erase
+
+    def SetRubberBandPen(self, dc):
+        dc.SetPen(wx.Pen(wx.BLACK, style=wx.DOT))
+
+    RubberBand.SetRubberBandPen = SetRubberBandPen
+
+if "gtk3" in wx.PlatformInfo:
+    PatchRubberBandForGTK3()
--- a/graphics/SFC_Objects.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/SFC_Objects.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,12 +23,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
-from future.builtins import round
-
+from functools import cmp_to_key
+from operator import eq
 import wx
-from six.moves import xrange
 
 from graphics.GraphicCommons import *
 from graphics.DebugDataConsumer import DebugDataConsumer
@@ -719,7 +716,7 @@
                                self.Pos.y + (self.Size[1] - text_height) // 2,
                                text_width,
                                text_height)
-            test_text = text_bbx.InsideXY(pt.x, pt.y)
+            test_text = text_bbx.Contains(pt.x, pt.y)
         else:
             test_text = False
         return test_text or Graphic_Element.HitTest(self, pt, connectors)
@@ -976,7 +973,7 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            highlight_items = self.Highlights.items()
+            highlight_items = list(self.Highlights.items())
             for name, highlights in highlight_items:
                 highlights = ClearHighlights(highlights, highlight_type)
                 if len(highlights) == 0:
@@ -1037,7 +1034,7 @@
             self.Condition.Draw(dc)
 
         if not getattr(dc, "printing", False):
-            for name, highlights in self.Highlights.iteritems():
+            for name, highlights in self.Highlights.items():
                 if name == "priority":
                     DrawHighlightedText(dc, str(self.Priority), highlights, priority_pos[0], priority_pos[1])
                 else:
@@ -1067,11 +1064,11 @@
         if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
             self.Inputs = [Connector(self, "", None, wx.Point(self.Size[0] // 2, 0), NORTH, onlyone=True)]
             self.Outputs = []
-            for i in xrange(number):
+            for i in range(number):
                 self.Outputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH, onlyone=True))
         elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
             self.Inputs = []
-            for i in xrange(number):
+            for i in range(number):
                 self.Inputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH, onlyone=True))
             self.Outputs = [Connector(self, "", None, wx.Point(self.Size[0] // 2, self.Size[1]), SOUTH, onlyone=True)]
         self.Value = None
@@ -1124,7 +1121,7 @@
         return divergence
 
     def GetConnectorTranslation(self, element):
-        return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
+        return dict(list(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs)))
 
     # Returns the RedrawRect
     def GetRedrawRect(self, movex=0, movey=0):
@@ -1204,7 +1201,7 @@
 
     # Returns if the point given is in the bounding box
     def HitTest(self, pt, connectors=True):
-        return self.BoundingBox.InsideXY(pt.x, pt.y) or self.TestConnector(pt, exclude=False) is not None
+        return self.BoundingBox.Contains(pt.x, pt.y) or self.TestConnector(pt, exclude=False) is not None
 
     # Refresh the divergence bounding box
     def RefreshBoundingBox(self):
@@ -1244,8 +1241,8 @@
             for output in self.Outputs:
                 output_pos = output.GetRelPosition()
                 output.SetPosition(wx.Point(output_pos.x - minx, output_pos.y))
-        self.Inputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
-        self.Outputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
+        self.Inputs.sort(key=cmp_to_key(lambda x, y: eq(x.Pos.y, y.Pos.y)))
+        self.Outputs.sort(key=cmp_to_key(lambda x, y: eq(x.Pos.y, y.Pos.y)))
         self.Pos.x += minx
         self.Size[0] = maxx - minx
         connector.MoveConnected()
@@ -1592,7 +1589,7 @@
                            self.Pos.y + (self.Size[1] - text_height) // 2,
                            text_width,
                            text_height)
-        return text_bbx.InsideXY(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)
+        return text_bbx.Contains(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)
 
     # Refresh the jump bounding box
     def RefreshBoundingBox(self):
@@ -2004,9 +2001,9 @@
         if highlight_type is None:
             self.Highlights = {}
         else:
-            highlight_items = self.Highlights.items()
+            highlight_items = list(self.Highlights.items())
             for number, action_highlights in highlight_items:
-                action_highlight_items = action_highlights.items()
+                action_highlight_items = list(action_highlights.items())
                 for name, attribute_highlights in action_highlight_items:
                     attribute_highlights = ClearHighlights(attribute_highlights, highlight_type)
                     if len(attribute_highlights) == 0:
@@ -2058,7 +2055,7 @@
 
             if not getattr(dc, "printing", False):
                 action_highlights = self.Highlights.get(i, {})
-                for name, attribute_highlights in action_highlights.iteritems():
+                for name, attribute_highlights in action_highlights.items():
                     if name == "qualifier":
                         DrawHighlightedText(dc, action.qualifier, attribute_highlights, qualifier_pos[0], qualifier_pos[1])
                     elif name == "duration":
--- a/graphics/ToolTipProducer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/graphics/ToolTipProducer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import wx
 
 from controls.CustomToolTip import CustomToolTip, TOOLTIP_WAIT_PERIOD
@@ -55,12 +55,6 @@
                          self.OnToolTipTimer,
                          self.ToolTipTimer)
 
-    def __del__(self):
-        """
-        Destructor
-        """
-        self.DestroyToolTip()
-
     def OnToolTipTimer(self, event):
         """
         Callback for Tool Tip firing timer Event
--- a/i18n/mki18n.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/i18n/mki18n.py	Wed Oct 04 16:24:00 2023 +0200
@@ -79,12 +79,10 @@
 # -------------
 #
 
-from __future__ import absolute_import
-from __future__ import print_function
+
 import os
 import sys
 import re
-from builtins import str as text
 import wx
 
 
@@ -122,10 +120,7 @@
 def getlanguageDict():
     languageDict = {}
     getSupportedLanguageDict('Beremiz')
-    if wx.VERSION >= (3, 0, 0):
-        _app = wx.App()
-    else:
-        _app = wx.PySimpleApp()
+    _app = wx.App()
 
     for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]:
         i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang))
@@ -229,7 +224,7 @@
 
     languageDict = getSupportedLanguageDict(applicationName)
 
-    for langCode in languageDict.keys():
+    for langCode in list(languageDict.keys()):
         if langCode == 'en':
             pass
         else:
@@ -254,7 +249,7 @@
 
     languageDict = getSupportedLanguageDict(applicationName)
 
-    for langCode in languageDict.keys():
+    for langCode in list(languageDict.keys()):
         if langCode == 'en':
             pass
         else:
@@ -308,7 +303,7 @@
 
     languageDict = getSupportedLanguageDict(applicationName)
 
-    for langCode in languageDict.keys():
+    for langCode in list(languageDict.keys()):
         if (langCode == 'en') and (forceEnglish == 0):
             pass
         else:
@@ -414,7 +409,7 @@
     # translate the path separators
     directory = unixpath(directory)
     # build a list of all directory elements
-    aList = filter(lambda x: len(x) > 0, directory.split('/'))
+    aList = [x for x in directory.split('/') if len(x) > 0]
     theLen = len(aList)
     # if the first element is a Windows-style disk drive
     # concatenate it with the first directory
@@ -515,7 +510,7 @@
             makePO(appDirPath, option['domain'], option['verbose'])
             exit_code = 0
         except IOError as e:
-            printUsage(text(e) + '\n   You must write a file app.fil that contains the list of all files to parse.')
+            printUsage(str(e) + '\n   You must write a file app.fil that contains the list of all files to parse.')
     if option['mo']:
         makeMO(appDirPath, option['moTarget'], option['domain'], option['verbose'], option['forceEnglish'])
         exit_code = 0
Binary file images/Build.png has changed
Binary file images/Clean.png has changed
Binary file images/Connect.png has changed
Binary file images/Disconnect.png has changed
Binary file images/Transfer.png has changed
Binary file images/about_brz_logo.png has changed
Binary file images/brz.ico has changed
Binary file images/brz.png has changed
--- a/images/genicons.sh	Fri Aug 18 11:38:20 2023 +0200
+++ b/images/genicons.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -16,8 +16,8 @@
 done
 
 cp ico024.png brz.png
-convert -compress none ico*.png brz.ico
-rm -f ico*.png
+convert -compress none ico???.png brz.ico
+rm -f ico???.png
 
 
 convert -compress none poeico*.png poe.ico
--- a/images/icons.svg	Fri Aug 18 11:38:20 2023 +0200
+++ b/images/icons.svg	Wed Oct 04 16:24:00 2023 +0200
@@ -32,7 +32,7 @@
   </metadata>
   <sodipodi:namedview
      inkscape:window-height="2096"
-     inkscape:window-width="3840"
+     inkscape:window-width="2880"
      inkscape:pageshadow="2"
      inkscape:pageopacity="0"
      guidetolerance="10.0"
@@ -43,17 +43,20 @@
      pagecolor="#ffffff"
      id="base"
      showgrid="true"
-     inkscape:zoom="32"
-     inkscape:cx="1126.6889"
-     inkscape:cy="857.99192"
-     inkscape:window-x="1600"
+     inkscape:zoom="4"
+     inkscape:cx="384.40645"
+     inkscape:cy="510.71306"
+     inkscape:window-x="0"
      inkscape:window-y="27"
-     inkscape:current-layer="svg2"
+     inkscape:current-layer="g19354"
      showguides="true"
      inkscape:guide-bbox="true"
-     inkscape:window-maximized="1"
+     inkscape:window-maximized="0"
      inkscape:measure-start="904.956,703.964"
-     inkscape:measure-end="930.144,704.058">
+     inkscape:measure-end="930.144,704.058"
+     inkscape:object-paths="true"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true">
     <inkscape:grid
        type="xygrid"
        id="grid16717"
@@ -88311,6 +88314,16 @@
        cx="-159.0049"
        cy="-108.7241"
        r="6.9022002" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath16483">
+      <path
+         sodipodi:nodetypes="ccccccccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="use16485"
+         d="m 566.50059,73.36489 c -3.21171,0 -6.18403,1.71679 -7.78988,4.49822 -0.54331,0.94101 -0.88738,1.959 -1.06307,2.99881 h -6.14112 c -2.02768,-0.0286 -2.02768,3.0275 0,2.99882 h 6.14112 c 0.17569,1.03979 0.51976,2.05778 1.06307,2.99881 1.60585,2.78143 4.57817,4.49822 7.78988,4.49822 0.82807,-9e-5 1.49932,-0.67134 1.49941,-1.4994 v -1.49941 h 4.49822 c 2.02743,0.02842 2.02743,-3.027239 0,-2.99882 H 568 v -5.99762 h 4.49822 c 2.02743,0.02842 2.02743,-3.027239 0,-2.99882 H 568 v -1.49941 c -9e-5,-0.82806 -0.67134,-1.49931 -1.49941,-1.4994 z"
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill-opacity:1;fill-rule:nonzero;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    </clipPath>
   </defs>
   <g
      id="g19063"
@@ -89174,7 +89187,7 @@
      inkscape:radius="1"
      inkscape:original="M 70 192.36133 L 70 214.36133 L 87 214.36133 L 87 197.36133 L 82 192.36133 L 70 192.36133 z "
      xlink:href="#path18378"
-     style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+     style="color:#000000;fill:#aaaaaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      id="path18380"
      inkscape:href="#path18378"
      d="m 68,192.375 0,22 19,0 0,-17 -5,-5 -14,0 z" />
@@ -89183,18 +89196,13 @@
      inkscape:connector-curvature="0"
      id="path18378"
      d="m 70,192.36218 0,22 17,0 0,-17 -5,-5 z"
-     style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+     style="color:#000000;fill:#ededed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
   <path
      d="m 80.431156,207.75778 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m -11,-1 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m 9,-9 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m -5,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m -7,-1 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1"
      style="color:#000000;fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      id="path18400"
      inkscape:connector-curvature="0" />
   <path
-     id="path18402"
-     d="m 79.568848,193.4415 0,2 -3,0 0,4 3,0 0,2 4,-4 -4,-4 z m -14,2 0,4 1,0 0,-4 -1,0 z m 2,0 0,4 1,0 0,-4 -1,0 z m 2,0 0,4 2,0 0,-4 -2,0 z m 3,0 0,4 3,0 0,-4 -3,0 z"
-     style="fill:#808080;stroke:none"
-     inkscape:connector-curvature="0" />
-  <path
      id="path19304"
      style="color:#000000;fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      d="m 80,207.36218 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m -11,-1 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m 9,-9 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m -5,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1 m -7,-1 0,1 1,0 0,-1 -1,0 z m 1,1 0,4 1,0 0,-4 -1,0 z m 0,4 -1,0 0,1 1,0 0,-1 z m -1,0 0,-4 -1,0 0,4 1,0 z m 3,-4 1,0 0,-1 1,0 0,5 1,0 0,1 -3,0 0,-1 1,0 0,-3 -1,0 0,-1"
@@ -90133,7 +90141,7 @@
      id="g18993"
      transform="translate(1165.0472,106.05367)">
     <path
-       style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient8148);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+       style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
        id="splash"
        d="m -1031,857.23823 h 476 v 299.99997 h -476 z"
        inkscape:connector-curvature="0" />
@@ -90141,83 +90149,91 @@
        id="g17803"
        transform="matrix(0.2224431,0,0,0.2224431,-580.61956,554.56584)">
       <g
-         id="use16743"
-         mask="url(#mask6467)"
-         transform="translate(-1840,2339.9999)" />
-      <g
          id="g7269"
-         transform="translate(0,89.910633)">
-        <path
-           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:314.89779663px;line-height:125%;font-family:'Arial Black';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;filter:url(#filter17760);enable-background:accumulate"
-           id="path17845"
-           d="m -1470.2813,1725.0291 c -56.0138,0.054 -112.0828,20.5177 -156.0937,61.5937 -85.0794,79.4058 -95.9453,209.1111 -29.5938,301.1563 l 9.7813,69.3437 0.9062,6.4375 5.125,-4 30.5938,-23.9687 c 87.5525,67.3697 213.0935,63.1007 295.9375,-14.2188 18.4642,-17.2329 33.4323,-36.8343 44.875,-57.9062 6.4003,0.736 13.3613,1.0937 20.875,1.0937 24.9087,0 44.0178,-3.5634 57.3437,-10.6875 13.3257,-7.1242 24.6943,-18.8804 34.125,-35.2812 l -61.6562,-5.6875 c -3.8953,4.9202 -7.5237,8.3649 -10.9063,10.3125 -5.5355,3.0752 -11.381,4.5937 -17.5312,4.5937 -2.2646,0 -4.435,-0.18 -6.5,-0.5625 3.5746,-10.6475 6.37,-21.5105 8.3437,-32.5 h 91.8125 v -7.0625 c -10e-5,-21.5262 -3.5522,-39.0091 -10.625,-52.4375 -7.0731,-13.4281 -17.3756,-23.6769 -30.9062,-30.75 -13.3838,-6.9958 -31.5824,-10.5176 -54.5938,-10.5937 -7.0146,-25.9757 -18.6908,-50.9762 -35.0625,-73.6875 l -9.7812,-69.3438 -0.9063,-6.4375 -5.125,4 -30.5937,23.9688 c -41.0402,-31.5796 -90.4197,-47.4228 -139.8438,-47.375 z m 228.125,206.2187 c 6.3617,0.8346 11.6486,3.3459 15.875,7.5313 5.279,5.2278 8.5511,13.9044 9.7813,26 h -24.7813 c 0.5248,-11.1718 0.225,-22.3843 -0.875,-33.5313 z m 118.9731,-33.6623 h 58.582 v 26.754 c 5.6377,-11.583 11.4549,-19.5528 17.4516,-23.9095 5.9965,-4.3563 13.4025,-6.5346 22.2182,-6.5347 9.2253,1e-4 19.3222,2.8703 30.2904,8.6104 l -19.3736,44.5901 c -7.3805,-3.0751 -13.2233,-4.6127 -17.5285,-4.6128 -8.2005,10e-5 -14.5559,3.3828 -19.066,10.1481 -6.458,9.5331 -9.6869,27.3691 -9.6868,53.508 v 54.7381 h -62.8873 z m 320.2793,97.1755 h -125.46709 c 1.12748,10.0456 3.84388,17.5285 8.14921,22.4487 6.04775,7.073 13.94069,10.6094 23.67883,10.6094 6.15024,0 11.99306,-1.5376 17.5285,-4.6128 3.38256,-1.9476 7.02151,-5.3815 10.91686,-10.3018 l 61.65724,5.6891 c -9.43072,16.4009 -20.80885,28.1634 -34.13443,35.2876 -13.3259,7.1241 -32.44322,10.6862 -57.35199,10.6862 -21.62881,0 -38.64475,-3.0495 -51.04789,-9.1486 -12.40324,-6.0991 -22.67944,-15.7859 -30.82862,-29.0604 -8.14922,-13.2745 -12.22382,-28.881 -12.22382,-46.8195 0,-25.5239 8.17483,-46.1788 24.52452,-61.9648 16.34962,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.82222,3.5366 55.35313,10.6093 13.53059,7.0731 23.83241,17.3236 30.9055,30.7517 7.0727,13.4284 10.60915,30.9056 10.60935,52.4318 z m -63.6561,-29.983 c -1.23021,-12.0956 -4.48476,-20.7573 -9.76368,-25.9852 -5.27917,-5.2277 -12.22393,-7.8416 -20.8343,-7.8417 -9.94316,10e-5 -17.88735,3.9466 -23.8326,11.8394 -3.79279,4.9204 -6.20167,12.2496 -7.22666,21.9875 z m 93.17774,-67.1925 h 58.4283 v 23.8326 c 8.40539,-9.9429 16.88773,-17.0158 25.44706,-21.2187 8.55912,-4.2026 18.88657,-6.304 30.98238,-6.3041 13.01809,1e-4 23.31991,2.3065 30.90549,6.9191 7.58526,4.6129 13.78685,11.4808 18.6048,20.6037 9.84037,-10.6605 18.80962,-17.9128 26.90778,-21.7569 8.09773,-3.8438 18.09204,-5.7658 29.98294,-5.7659 17.52823,1e-4 31.21274,5.2023 41.05357,15.6065 9.84026,10.4044 14.76054,26.6772 14.76083,48.8184 v 102.557 h -62.73354 v -93.024 c -2.3e-4,-7.3803 -1.43531,-12.8644 -4.30524,-16.4522 -4.20297,-5.6377 -9.43076,-8.4566 -15.68339,-8.4567 -7.38062,10e-5 -13.32595,2.6652 -17.83601,7.9954 -4.51044,5.3304 -6.76557,13.8897 -6.76538,25.6777 v 84.2598 h -62.73355 v -89.9488 c -1.2e-4,-7.1753 -0.41015,-12.0444 -1.23007,-14.6071 -1.3327,-4.1001 -3.63907,-7.4059 -6.91914,-9.9174 -3.2803,-2.5113 -7.12426,-3.767 -11.5319,-3.7671 -7.1755,10e-5 -13.06958,2.7165 -17.68225,8.1492 -4.61284,5.4329 -6.91922,14.3509 -6.91914,26.754 v 83.3372 h -62.73354 z m 316.74293,-62.1185 h 62.57978 v 42.5911 h -62.57978 z m 0,62.1185 h 62.57978 v 163.2917 h -62.57978 z m 95.79168,0 h 151.45231 v 36.5945 l -82.41466,83.9523 h 87.48869 v 42.7449 H -366.998 v -40.5923 l 78.10941,-79.9545 h -71.95906 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#d19f34;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-           id="use16735"
-           d="m -1336.1632,1788.2263 c 0,0 56.3913,141.5671 -147.8368,147.7737 -204.2612,6.2076 -147.8368,147.7737 -147.8368,147.7737 -37.8479,-37.8317 -61.2676,-90.0855 -61.2676,-147.7737 0,-57.6882 23.4197,-109.942 61.2676,-147.7737 37.8479,-37.8318 90.124,-61.2415 147.8368,-61.2415 57.7128,0 109.9889,23.4097 147.8368,61.2415 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#469837;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-           id="use16737"
-           d="m -1631.8368,2083.7737 c 0,0 -56.3913,-141.5671 147.8368,-147.7737 204.2612,-6.2076 147.8368,-147.7737 147.8368,-147.7737 37.8479,37.8317 61.2676,90.0855 61.2676,147.7737 0,57.6882 -23.4197,109.942 -61.2676,147.7737 -37.8479,37.8318 -90.124,61.2415 -147.8368,61.2415 -57.7128,0 -109.9889,-23.4097 -147.8368,-61.2415 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#4c4c4c;stroke-width:49.86504364;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
-           id="path16739"
-           transform="matrix(0.8023362,0,0,0.8019941,-2094.2929,1769.2301)"
-           d="m 1021.2642,207.94408 c 0,143.9361 -116.68326,260.61936 -260.61936,260.61936 -143.9361,0 -260.61936,-116.68326 -260.61936,-260.61936 0,-143.936098 116.68326,-260.61936 260.61936,-260.61936 143.9361,0 260.61936,116.683262 260.61936,260.61936 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:306.80871582px;line-height:125%;font-family:'Arial Black';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-           id="path16741"
-           d="m -1577.2331,1825.1726 h 127.038 c 21.1728,2e-4 37.4271,5.2435 48.7628,15.7299 11.3353,10.4868 17.0031,23.4703 17.0033,38.9503 -2e-4,12.9836 -4.045,24.1194 -12.1345,33.4074 -5.3933,6.1923 -13.2833,11.086 -23.6698,14.6813 15.7797,3.7953 27.3898,10.312 34.8306,19.5501 7.4402,9.2383 11.1605,20.8485 11.1607,34.8306 -2e-4,11.3855 -2.6468,21.6224 -7.9399,30.7108 -5.2934,9.0884 -12.5342,16.2792 -21.7223,21.5725 -5.6929,3.2958 -14.2819,5.6927 -25.7671,7.1908 -15.2807,1.9975 -25.4177,2.9962 -30.4112,2.9962 h -117.1506 z m 68.4627,86.1401 h 29.5124 c 10.5863,10e-5 17.9519,-1.8225 22.0968,-5.468 4.1445,-3.6452 6.2169,-8.9135 6.217,-15.8049 -1e-4,-6.3916 -2.0725,-11.3853 -6.217,-14.9809 -4.1449,-3.5952 -11.3607,-5.3929 -21.6474,-5.3931 h -29.9618 z m 0,86.29 h 34.6059 c 11.6849,0 19.9244,-2.0723 24.7184,-6.2171 4.7938,-4.1447 7.1907,-9.7126 7.1909,-16.7037 -2e-4,-6.4916 -2.3722,-11.71 -7.116,-15.655 -4.7441,-3.9449 -13.0584,-5.9174 -24.9432,-5.9175 h -34.456 z"
-           inkscape:connector-curvature="0" />
-        <g
-           id="use16745"
-           mask="url(#mask6467)"
-           transform="rotate(180,-563.99995,766)">
-          <path
-             style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447);enable-background:accumulate"
-             id="use6863"
-             d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-             inkscape:connector-curvature="0" />
-          <path
-             style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate"
-             id="use6865"
-             d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-             inkscape:connector-curvature="0" />
-        </g>
-        <g
-           id="g7285"
-           mask="url(#mask6467)"
-           transform="translate(-1839.8676,2340.0508)">
-          <path
-             style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447);enable-background:accumulate"
-             id="path7287"
-             d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-             inkscape:connector-curvature="0" />
-          <path
-             style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate"
-             id="path7289"
-             d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-             inkscape:connector-curvature="0" />
-        </g>
-        <path
-           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:314.89779663px;line-height:125%;font-family:'Arial Black';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#dadada;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10.43299961;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
-           id="text16729"
-           d="m -1166.8587,1976.761 h -125.4671 c 1.1275,10.0456 3.8439,17.5285 8.1492,22.4487 6.0478,7.073 13.9407,10.6094 23.6789,10.6094 6.1502,0 11.993,-1.5376 17.5285,-4.6128 3.3825,-1.9476 7.0215,-5.3815 10.9168,-10.3018 l 61.6573,5.6891 c -9.4307,16.4009 -20.8089,28.1634 -34.1345,35.2876 -13.3259,7.1241 -32.4432,10.6862 -57.3519,10.6862 -21.6289,0 -38.6448,-3.0495 -51.0479,-9.1486 -12.4033,-6.0991 -22.6795,-15.7859 -30.8286,-29.0604 -8.1493,-13.2745 -12.2239,-28.881 -12.2239,-46.8195 0,-25.5239 8.1749,-46.1788 24.5245,-61.9648 16.3497,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.8223,3.5366 55.3532,10.6093 13.5306,7.0731 23.8324,17.3236 30.9055,30.7517 7.0727,13.4284 10.6091,30.9056 10.6093,52.4318 z m -63.6561,-29.983 c -1.2302,-12.0956 -4.4847,-20.7573 -9.7637,-25.9852 -5.2791,-5.2277 -12.2239,-7.8416 -20.8343,-7.8417 -9.9431,10e-5 -17.8873,3.9466 -23.8325,11.8394 -3.7928,4.9204 -6.2017,12.2496 -7.2267,21.9875 z m 93.3316,-67.1925 h 58.582 v 26.754 c 5.6377,-11.583 11.4549,-19.5528 17.4516,-23.9095 5.9965,-4.3563 13.4025,-6.5346 22.2182,-6.5347 9.2253,1e-4 19.3222,2.8703 30.2904,8.6104 l -19.3736,44.5901 c -7.3805,-3.0751 -13.2233,-4.6127 -17.5285,-4.6128 -8.2005,10e-5 -14.5559,3.3828 -19.066,10.1481 -6.458,9.5331 -9.6869,27.3691 -9.6868,53.508 v 54.7381 h -62.8873 z m 320.2793,97.1755 h -125.46709 c 1.12748,10.0456 3.84388,17.5285 8.14921,22.4487 6.04775,7.073 13.94069,10.6094 23.67883,10.6094 6.15024,0 11.99306,-1.5376 17.5285,-4.6128 3.38256,-1.9476 7.02151,-5.3815 10.91686,-10.3018 l 61.65724,5.6891 c -9.43072,16.4009 -20.80885,28.1634 -34.13443,35.2876 -13.3259,7.1241 -32.44322,10.6862 -57.35199,10.6862 -21.62881,0 -38.64475,-3.0495 -51.04789,-9.1486 -12.40324,-6.0991 -22.67944,-15.7859 -30.82862,-29.0604 -8.14922,-13.2745 -12.22382,-28.881 -12.22382,-46.8195 0,-25.5239 8.17483,-46.1788 24.52452,-61.9648 16.34962,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.82222,3.5366 55.35313,10.6093 13.53059,7.0731 23.83241,17.3236 30.9055,30.7517 7.0727,13.4284 10.60915,30.9056 10.60935,52.4318 z m -63.6561,-29.983 c -1.23021,-12.0956 -4.48476,-20.7573 -9.76368,-25.9852 -5.27917,-5.2277 -12.22393,-7.8416 -20.8343,-7.8417 -9.94316,10e-5 -17.88735,3.9466 -23.8326,11.8394 -3.79279,4.9204 -6.20167,12.2496 -7.22666,21.9875 z m 93.17774,-67.1925 h 58.4283 v 23.8326 c 8.40539,-9.9429 16.88773,-17.0158 25.44706,-21.2187 8.55912,-4.2026 18.88657,-6.304 30.98238,-6.3041 13.01809,1e-4 23.31991,2.3065 30.90549,6.9191 7.58526,4.6129 13.78685,11.4808 18.6048,20.6037 9.84037,-10.6605 18.80962,-17.9128 26.90778,-21.7569 8.09773,-3.8438 18.09204,-5.7658 29.98294,-5.7659 17.52823,1e-4 31.21274,5.2023 41.05357,15.6065 9.84026,10.4044 14.76054,26.6772 14.76083,48.8184 v 102.557 h -62.73354 v -93.024 c -2.3e-4,-7.3803 -1.43531,-12.8644 -4.30524,-16.4522 -4.20297,-5.6377 -9.43076,-8.4566 -15.68339,-8.4567 -7.38062,10e-5 -13.32595,2.6652 -17.83601,7.9954 -4.51044,5.3304 -6.76557,13.8897 -6.76538,25.6777 v 84.2598 h -62.73355 v -89.9488 c -1.2e-4,-7.1753 -0.41015,-12.0444 -1.23007,-14.6071 -1.3327,-4.1001 -3.63907,-7.4059 -6.91914,-9.9174 -3.2803,-2.5113 -7.12426,-3.767 -11.5319,-3.7671 -7.1755,10e-5 -13.06958,2.7165 -17.68225,8.1492 -4.61284,5.4329 -6.91922,14.3509 -6.91914,26.754 v 83.3372 h -62.73354 z m 316.74293,-62.1185 h 62.57978 v 42.5911 h -62.57978 z m 0,62.1185 h 62.57978 v 163.2917 h -62.57978 z m 95.79168,0 h 151.45231 v 36.5945 l -82.41466,83.9523 h 87.48869 v 42.7449 H -380.998 v -40.5923 l 78.10941,-79.9545 h -71.95906 z"
-           inkscape:connector-curvature="0" />
-        <text
-           style="font-style:normal;font-weight:normal;font-size:71.35877228px;line-height:0%;font-family:'Bitstream Vera Sans';display:inline;fill:#000000;fill-opacity:1;stroke:none"
-           xml:space="preserve"
-           id="text3172"
-           y="2147.7705"
-           x="-1259.6362"><tspan
-             id="tspan3174"
-             y="2147.7705"
-             x="-1259.6362">Free Software for Automation </tspan></text>
-      </g>
+         transform="translate(0,107.89276)" />
+    </g>
+    <g
+       transform="matrix(8.4066435,0,0,8.4066435,-1377.662,86.510482)"
+       style="stroke-width:0.26217723"
+       id="g52678">
+      <path
+         style="opacity:1;vector-effect:none;fill:#9acd32;fill-opacity:1;stroke:none;stroke-width:0.06936772;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+         d="m 43.7625,109.87305 h 50.998012 l -1.961981,2.54 H 41.80053 Z"
+         id="rect482-3"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc" />
+      <path
+         style="opacity:1;vector-effect:none;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:0.06936772;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+         d="m 46.296709,106.59222 h 50.998 l -1.847508,2.39183 H 44.44919 Z"
+         id="rect480-6"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="ccccc" />
+      <path
+         d="m 46.983389,108.98405 h 2.508251 c 1.05833,0 1.5875,-0.45508 1.5875,-1.25941 0,-0.21872 -0.0388,-0.40569 -0.11642,-0.56092 -0.0705,-0.15522 -0.15522,-0.26811 -0.254,-0.33866 -0.0917,-0.0776 -0.22225,-0.13759 -0.39158,-0.17992 -0.16228,-0.0423 -0.26018,-0.0529 -0.39158,-0.0529 h -0.39159 -2.550581 v -0.88899 h 2.730501 c 0.49389,0 0.88194,0.0388 1.16417,0.11641 0.28222,0.0705 0.53622,0.21873 0.762,0.4445 0.35983,0.34573 0.53975,0.80081 0.53975,1.36525 0,0.23989 -0.0353,0.45862 -0.10583,0.65617 -0.0706,0.1905 -0.14817,0.3422 -0.23284,0.45508 -0.0847,0.11289 -0.20108,0.21873 -0.34925,0.3175 -0.14111,0.0917 -0.24694,0.15523 -0.3175,0.1905 -0.0706,0.0282 -0.16581,0.0635 -0.28575,0.10584 0.13406,0.0353 0.24695,0.0706 0.33867,0.10583 0.0917,0.0353 0.21872,0.10231 0.381,0.20108 0.16933,0.0988 0.30339,0.21167 0.40217,0.33867 0.10583,0.11995 0.19755,0.29281 0.27516,0.51858 0.0847,0.21873 0.127,0.46567 0.127,0.74084 0,0.74789 -0.30691,1.32644 -0.92075,1.73566 -0.16227,0.10584 -0.35277,0.18698 -0.5715,0.24342 -0.21167,0.0564 -0.39158,0.0882 -0.53975,0.0952 -0.14111,0.007 -0.34572,0.0106 -0.61383,0.0106 l -2.783421,1e-5 v -0.93135 h 2.836331 c 0.43745,0 0.79728,-0.0388 1.0795,-0.26457 0.28223,-0.23284 0.42334,-0.56092 0.42334,-0.98425 0,-0.38806 -0.13053,-0.70556 -0.39159,-0.9525 -0.254,-0.24695 -0.6103,-0.33868 -1.06891,-0.33868 h -2.878671 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06936772"
+         id="path415-7"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="csscccscccsccscccccccccsccccccscscscc" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path417-5"
+         transform="matrix(0.06936772,0,0,0.06936772,41.661795,105.56437)"
+         d="M 175.58008,2.0019531 V 14.970703 h 69.11328 V 2.0019531 Z m 0,47.2949219 v 0.002 12.814453 36.617188 13.425784 0.002 h 15.25781 v -0.002 h 55.07617 V 98.730469 H 190.83789 V 62.113281 h 51.26172 V 49.298828 h -51.26172 v -0.002 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1" />
+      <path
+         d="m 60.41363,108.99464 h 2.69874 c 0.47978,0 0.81492,-0.0564 1.00542,-0.16933 0.34572,-0.19756 0.51858,-0.52917 0.51858,-0.99484 0,-0.23283 -0.0423,-0.43391 -0.127,-0.60325 -0.0847,-0.16933 -0.17992,-0.2928 -0.28575,-0.37041 -0.0988,-0.0776 -0.23283,-0.13759 -0.40216,-0.17992 -0.16228,-0.0423 -0.28928,-0.067 -0.381,-0.0741 -0.0847,-0.007 -0.19403,-0.0106 -0.32809,-0.0106 l -2.69874,-1e-5 v -0.889 h 2.73049 c 0.79728,0 1.38289,0.11995 1.75684,0.35984 0.56444,0.35278 0.84666,0.87136 0.84666,1.55575 0,0.54328 -0.17992,0.99483 -0.53975,1.35466 -0.23283,0.23284 -0.53269,0.39864 -0.89958,0.49742 0.37395,0.0988 0.64911,0.254 0.8255,0.46567 0.17639,0.20461 0.28928,0.53975 0.33867,1.00541 0.11994,1.04423 0.27869,1.84503 0.47625,2.40242 h -1.13242 c -0.0988,-0.29633 -0.2152,-0.97014 -0.34925,-2.02142 -0.0635,-0.5715 -0.20105,-0.9525 -0.41275,-1.143 -0.21167,-0.1905 -0.63147,-0.28575 -1.25942,-0.28575 h -2.38124 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06936772"
+         id="path419"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cscssccccccscsscccccccscc" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path421"
+         transform="matrix(0.06936772,0,0,0.06936772,41.661795,105.56437)"
+         d="M 370.5625,2.0019531 V 14.970703 h 69.11328 V 2.0019531 Z m 0,47.2949219 v 0.002 12.814453 36.617188 13.425784 0.002 h 15.25586 v -0.002 h 55.07812 V 98.730469 H 385.81836 V 62.113281 h 51.26367 V 49.298828 h -51.26367 v -0.002 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1" />
+      <path
+         sodipodi:nodetypes="cccccccccc"
+         inkscape:connector-curvature="0"
+         id="path423"
+         d="m 83.95087,105.70311 v 0.88911 h 1.05833 v -0.88911 z m 0,3.28094 v 4.36046 h 1.05833 v -4.36046 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06936772" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path425"
+         d="m 73.98146,106.60281 v -0.89959 h 1.873271 l 1.429369,4.16984 h -1.0883 l -1.18753,-3.27025 z"
+         style="fill:#000000;stroke:none;stroke-width:0.06936772px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path427"
+         d="m 79.07313,108.98405 -1.299141,3.42925 h -0.68162 l 0.328661,0.93121 h 1.005631 l 0.33951,-0.93121 1.250059,-3.42925 z"
+         style="fill:#000000;stroke:none;stroke-width:0.06936772px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path429"
+         d="m 80.22551,105.70311 -0.315741,0.89969 h 1.024753 v 2.38125 0.88883 h 1.037137 v -0.88883 -2.38125 -0.89969 z"
+         style="fill:#000000;stroke:none;stroke-width:0.06936772px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path431"
+         d="m 80.93453,112.41306 -8e-6,0.93133 h 1.037148 v -0.93133 z"
+         style="fill:#000000;stroke:none;stroke-width:0.06936772px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+      <path
+         style="fill:#000000;stroke:none;stroke-width:0.06936772px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 73.981409,108.98405 v 0.78341 0.10542 3.47163 h 1.026811 v -3.47163 -0.10542 -0.78341 z"
+         id="path433"
+         inkscape:connector-curvature="0" />
+      <path
+         sodipodi:nodetypes="ccccccccccccc"
+         inkscape:connector-curvature="0"
+         id="path435"
+         d="m 86.448391,105.70311 v 0.93121 h 5.55625 l 0.727599,-0.93121 z m 2.462379,3.28094 -2.57865,3.30212 v 1.05834 h 5.746929 V 112.4133 H 87.5491 l 2.698541,-3.42925 z"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06936772" />
+      <path
+         style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.06936772;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+         d="m 60.413582,112.41305 h 1.04775 v 0.93134 h -1.04775 z"
+         id="rect474"
+         inkscape:connector-curvature="0" />
     </g>
   </g>
   <text
@@ -90261,8 +90277,9 @@
          height="178.66196"
          x="-1492.3632"
          y="2013.0917"
-         id="ico64"
-         style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" />
+         id="ico064"
+         style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate"
+         inkscape:label="#ico064" />
       <use
          transform="translate(223.29672,0.68429344)"
          id="use15274"
@@ -90276,69 +90293,9 @@
        id="g19356"
        mask="url(#mask6467)"
        transform="translate(-1840,2339.9999)" />
-    <g
-       id="g19358"
-       transform="matrix(0.3669311,0,0,0.3669311,-1086.1197,1386.8468)">
-      <path
-         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:314.89779663px;line-height:125%;font-family:'Arial Black';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;opacity:0.5;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;filter:url(#filter19931);enable-background:accumulate"
-         id="path19360"
-         d="m -1253,2027.2478 c -3.5746,10.6475 -4.3073,15.1469 -15.75,36.2188 -11.4427,21.0719 -26.4108,40.6733 -44.875,57.9062 -82.844,77.3195 -208.385,81.5885 -295.9375,14.2188 l -30.5938,23.9687 -5.125,4 -0.9062,-6.4375 -9.7813,-69.3437 c -66.3515,-92.0452 -55.4856,-221.7505 29.5938,-301.1563 44.0109,-41.076 100.0799,-61.5397 156.0937,-61.5937 49.4241,-0.048 98.8036,15.7954 139.8438,47.375 l 30.5937,-23.9688 5.125,-4 0.9063,6.4375 9.7812,69.3438 c 16.3717,22.7113 28.0479,47.7118 35.0625,73.6875 7.0146,25.9757 5.7125,26.1967 6.8125,37.3437 1.1,11.147 1.3998,22.3595 0.875,33.5313 -0.5248,11.1718 -1.4013,18.9792 -3.375,29.9687 -1.9737,10.9895 -4.7691,21.8525 -8.3437,32.5 z"
-         inkscape:connector-curvature="0" />
-      <path
-         style="display:inline;overflow:visible;visibility:visible;fill:#d19f34;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-         id="path19362"
-         d="m -1336.1632,1788.2263 c 0,0 56.3913,141.5671 -147.8368,147.7737 -204.2612,6.2076 -147.8368,147.7737 -147.8368,147.7737 -37.8479,-37.8317 -61.2676,-90.0855 -61.2676,-147.7737 0,-57.6882 23.4197,-109.942 61.2676,-147.7737 37.8479,-37.8318 90.124,-61.2415 147.8368,-61.2415 57.7128,0 109.9889,23.4097 147.8368,61.2415 z"
-         inkscape:connector-curvature="0" />
-      <path
-         style="display:inline;overflow:visible;visibility:visible;fill:#469837;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-         id="path19364"
-         d="m -1631.8368,2083.7737 c 0,0 -56.3913,-141.5671 147.8368,-147.7737 204.2612,-6.2076 147.8368,-147.7737 147.8368,-147.7737 37.8479,37.8317 61.2676,90.0855 61.2676,147.7737 0,57.6882 -23.4197,109.942 -61.2676,147.7737 -37.8479,37.8318 -90.124,61.2415 -147.8368,61.2415 -57.7128,0 -109.9889,-23.4097 -147.8368,-61.2415 z"
-         inkscape:connector-curvature="0" />
-      <path
-         style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#4c4c4c;stroke-width:49.86504364;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
-         id="path19366"
-         transform="matrix(0.8023362,0,0,0.8019941,-2094.2929,1769.2301)"
-         d="m 1021.2642,207.94408 c 0,143.9361 -116.68326,260.61936 -260.61936,260.61936 -143.9361,0 -260.61936,-116.68326 -260.61936,-260.61936 0,-143.936098 116.68326,-260.61936 260.61936,-260.61936 143.9361,0 260.61936,116.683262 260.61936,260.61936 z"
-         inkscape:connector-curvature="0" />
-      <path
-         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:306.80871582px;line-height:125%;font-family:'Arial Black';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate"
-         id="path19368"
-         d="m -1577.2331,1825.1726 h 127.038 c 21.1728,2e-4 37.4271,5.2435 48.7628,15.7299 11.3353,10.4868 17.0031,23.4703 17.0033,38.9503 -2e-4,12.9836 -4.045,24.1194 -12.1345,33.4074 -5.3933,6.1923 -13.2833,11.086 -23.6698,14.6813 15.7797,3.7953 27.3898,10.312 34.8306,19.5501 7.4402,9.2383 11.1605,20.8485 11.1607,34.8306 -2e-4,11.3855 -2.6468,21.6224 -7.9399,30.7108 -5.2934,9.0884 -12.5342,16.2792 -21.7223,21.5725 -5.6929,3.2958 -14.2819,5.6927 -25.7671,7.1908 -15.2807,1.9975 -25.4177,2.9962 -30.4112,2.9962 h -117.1506 z m 68.4627,86.1401 h 29.5124 c 10.5863,10e-5 17.9519,-1.8225 22.0968,-5.468 4.1445,-3.6452 6.2169,-8.9135 6.217,-15.8049 -1e-4,-6.3916 -2.0725,-11.3853 -6.217,-14.9809 -4.1449,-3.5952 -11.3607,-5.3929 -21.6474,-5.3931 h -29.9618 z m 0,86.29 h 34.6059 c 11.6849,0 19.9244,-2.0723 24.7184,-6.2171 4.7938,-4.1447 7.1907,-9.7126 7.1909,-16.7037 -2e-4,-6.4916 -2.3722,-11.71 -7.116,-15.655 -4.7441,-3.9449 -13.0584,-5.9174 -24.9432,-5.9175 h -34.456 z"
-         inkscape:connector-curvature="0" />
-      <g
-         id="g19370"
-         mask="url(#mask6467)"
-         transform="rotate(180,-563.99995,766)">
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447);enable-background:accumulate"
-           id="path19372"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate"
-           id="path19374"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           inkscape:connector-curvature="0" />
-      </g>
-      <g
-         id="g19376"
-         mask="url(#mask6467)"
-         transform="translate(-1839.8676,2340.0508)">
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447);enable-background:accumulate"
-           id="path19378"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           inkscape:connector-curvature="0" />
-        <path
-           style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate"
-           id="path19380"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           inkscape:connector-curvature="0" />
-      </g>
-    </g>
     <rect
-       style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate"
-       id="ico48"
+       style="display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;stroke:none;stroke-width:0;marker:none;enable-background:accumulate"
+       id="ico048"
        y="2012.4073"
        x="-1715.66"
        height="178.66196"
@@ -90348,7 +90305,7 @@
        transform="matrix(0.5,0,0,0.5,-593.54387,1095.1925)">
       <rect
          style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate"
-         id="ico24"
+         id="ico024"
          y="2013.0917"
          x="-1492.3632"
          height="178.66196"
@@ -90367,7 +90324,7 @@
        transform="matrix(0.6666666,0,0,0.6666666,-493.70173,729.90034)">
       <rect
          style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate"
-         id="ico32"
+         id="ico032"
          y="2013.0917"
          x="-1492.3632"
          height="178.66196"
@@ -90389,7 +90346,7 @@
          height="178.66196"
          x="-1492.3632"
          y="2013.0917"
-         id="ico16"
+         id="ico016"
          style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:0;marker:none;enable-background:accumulate" />
       <use
          transform="translate(223.29672,0.68429344)"
@@ -90400,6 +90357,49 @@
          height="1052.3622"
          xlink:href="#g19358" />
     </g>
+    <g
+       id="g19358"
+       transform="matrix(0.3669311,0,0,0.3669311,-1086.1197,1386.8468)">
+      <g
+         id="g837"
+         transform="matrix(0.48690873,0,0,0.48690873,-1715.6909,1704.8447)"
+         inkscape:label="g837">
+        <rect
+           id="rect2"
+           ry="200"
+           rx="200"
+           height="1000"
+           width="1000"
+           x="0"
+           y="0"
+           style="fill:#eeeeec" />
+        <path
+           id="rect482"
+           d="M 0,526.82227 V 721.9043 H 1000 V 526.82227 Z"
+           style="opacity:1;vector-effect:none;fill:#9acd32;fill-opacity:1;stroke:none;stroke-width:5.32772112;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+           inkscape:connector-curvature="0" />
+        <path
+           id="rect480"
+           d="M 0,274.8418 V 458.54297 H 1000 V 274.8418 Z"
+           style="opacity:1;vector-effect:none;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:5.32772112;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+           inkscape:connector-curvature="0" />
+        <g
+           transform="translate(9.7650495)"
+           id="g145">
+          <path
+             sodipodi:nodetypes="csscccscccsccscccccccccsccccccscscscc"
+             inkscape:connector-curvature="0"
+             id="path415"
+             style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:5.32772112"
+             d="M 39.514141,458.54323 H 232.15795 c 81.28402,0 121.92641,-34.95199 121.92641,-96.72778 0,-16.79858 -2.98,-31.15863 -8.94153,-43.08092 -5.41468,-11.92152 -11.92152,-20.59193 -19.50822,-26.01046 -7.04293,-5.95999 -17.0697,-10.56746 -30.07493,-13.81858 -12.46376,-3.24881 -19.98287,-4.06293 -30.07492,-4.06293 H 235.40906 39.514141 V 206.56454 H 249.22765 c 37.93274,0 67.73655,2.97999 89.41295,8.94075 21.67564,5.41469 41.18387,16.79935 58.52468,34.1394 27.6364,26.55346 41.45498,61.50544 41.45498,104.85671 0,18.42452 -2.71118,35.22387 -8.12817,50.39651 -5.42236,14.63117 -11.38005,26.28234 -17.88305,34.95198 -6.5053,8.67041 -15.44376,16.79935 -26.82381,24.38528 -10.83782,7.04293 -18.96599,11.92229 -24.38528,14.63117 -5.42237,2.16588 -12.73488,4.87706 -21.94676,8.12894 10.29635,2.71119 18.96676,5.42237 26.01123,8.12817 7.04293,2.71119 16.79857,7.85783 29.26233,15.44376 13.00523,7.58824 23.30158,16.25711 30.88828,26.01122 8.12818,9.21265 15.17264,22.48899 21.1334,39.82904 6.50531,16.79935 9.75412,35.76534 9.75412,56.89951 0,57.44097 -23.57193,101.87595 -70.71732,133.3057 -12.46299,8.12893 -27.09416,14.36081 -43.89351,18.69563 -16.25711,4.33175 -30.07493,6.77412 -41.45498,7.31175 -10.83782,0.53763 -26.55269,0.81412 -47.14462,0.81412 l -213.777979,7.7e-4 V 721.90351 H 257.35582 c 33.59792,0 61.23432,-2.98 82.90996,-20.32005 21.6764,-17.88305 32.51422,-43.08092 32.51422,-75.59437 0,-29.80458 -10.02523,-54.18986 -30.07569,-73.15585 C 323.19608,533.86648 295.8308,526.82125 260.6077,526.82125 H 39.514141 Z" />
+          <path
+             style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:1.25;font-family:'Univers LT Std';-inkscape-font-specification:'Univers LT Std, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:5.32772112"
+             d="m 566.23593,206.56384 v 69.09388 h 368.21628 v -69.09388 z m 0,251.97415 v 0.0107 68.27183 195.08616 71.52884 0.0107 h 81.28935 v -0.0107 H 940.95576 V 721.90639 H 647.52528 V 526.82023 H 920.63343 V 458.5484 H 647.52528 v -0.0107 z"
+             id="path417"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+    </g>
   </g>
   <text
      style="font-style:normal;font-weight:normal;font-size:13.88476658px;line-height:0%;font-family:'Bitstream Vera Sans';fill:#000000;fill-opacity:1;stroke:none"
@@ -91172,13 +91172,6 @@
        x="37.5"
        y="473.61218"
        id="tspan16384">Icons</tspan></text>
-  <path
-     sodipodi:type="inkscape:offset"
-     inkscape:radius="1"
-     inkscape:original="M 505 135.34375 L 505 147.34375 L 502 147.34375 L 506 151.34375 L 510 147.34375 L 507 147.34375 L 507 135.34375 L 505 135.34375 z M 498 135.375 L 494 139.375 L 497 139.375 L 497 151.375 L 499 151.375 L 499 139.375 L 502 139.375 L 498 135.375 z "
-     style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-     id="path18475"
-     d="m 504.90625,134.34375 a 1.0001,1.0001 0 0 0 -0.90625,1 l 0,11 -2,0 a 1.0001,1.0001 0 0 0 -0.71875,1.71875 l 4,4 a 1.0001,1.0001 0 0 0 1.4375,0 l 4,-4 A 1.0001,1.0001 0 0 0 510,146.34375 l -2,0 0,-11 a 1.0001,1.0001 0 0 0 -1,-1 l -2,0 a 1.0001,1.0001 0 0 0 -0.0937,0 z m -7.03125,0.0312 a 1.0001,1.0001 0 0 0 -0.59375,0.28125 l -4,4 A 1.0001,1.0001 0 0 0 494,140.375 l 2,0 0,11 a 1.0001,1.0001 0 0 0 1,1 l 2,0 a 1.0001,1.0001 0 0 0 1,-1 l 0,-11 2,0 a 1.0001,1.0001 0 0 0 0.71875,-1.71875 l -4,-4 A 1.0001,1.0001 0 0 0 497.875,134.375 z" />
   <rect
      width="24"
      height="24"
@@ -91187,13 +91180,6 @@
      id="Transfer"
      style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      inkscape:label="#rect16270" />
-  <path
-     sodipodi:type="inkscape:offset"
-     inkscape:radius="1"
-     inkscape:original="M 558 141.375 L 555.4375 144.375 L 550 144.375 L 550 145.375 L 555.4375 145.375 L 558 148.375 L 562 148.34375 L 562 144.875 L 562 141.40625 L 558 141.375 z M 567 141.375 L 563 141.40625 L 563 144.875 L 563 148.34375 L 567 148.375 L 569.5625 145.375 L 574 145.375 L 574 144.375 L 569.5625 144.375 L 567 141.375 z "
-     style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10000000000000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-     id="path18481"
-     d="m 558,141.375 -2.5625,3 -5.4375,0 0,1 5.4375,0 2.5625,3 4,-0.0312 0,-3.46875 0,-3.46875 -4,-0.0312 z m 9,0 -4,0.0312 0,3.46875 0,3.46875 4,0.0312 2.5625,-3 4.4375,0 0,-1 -4.4375,0 -2.5625,-3 z" />
   <rect
      inkscape:label="#rect16270"
      style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
@@ -91202,13 +91188,6 @@
      x="550"
      height="24"
      width="24" />
-  <path
-     sodipodi:type="inkscape:offset"
-     inkscape:radius="1"
-     inkscape:original="M 616 141.375 L 613.4375 144.375 L 608 144.375 L 608 145.375 L 613.4375 145.375 L 616 148.375 L 620 148.34375 L 620 144.875 L 620 141.40625 L 616 141.375 z M 629 141.375 L 625 141.40625 L 625 144.875 L 625 148.34375 L 629 148.375 L 631.5625 145.375 L 636 145.375 L 636 144.375 L 631.5625 144.375 L 629 141.375 z "
-     style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10000000000000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-     id="path18491"
-     d="m 616,141.375 -2.5625,3 -5.4375,0 0,1 5.4375,0 2.5625,3 4,-0.0312 0,-3.46875 0,-3.46875 -4,-0.0312 z m 13,0 -4,0.0312 0,3.46875 0,3.46875 4,0.0312 2.5625,-3 4.4375,0 0,-1 -4.4375,0 -2.5625,-3 z" />
   <rect
      width="24"
      height="24"
@@ -91217,12 +91196,6 @@
      id="Disconnect"
      style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
      inkscape:label="#rect16270" />
-  <path
-     style="fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10000000000000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:8.59499931000000039;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-     d="m 562,141.40625 -4,-0.0441 -2.5625,3.01282 -5.4375,0 0,1 5.4375,0 2.5625,2.98718 4,-0.0184 0,-3.46875 z m 1,0 0,3.46875 0,3.46875 4,0.0184 2.5625,-2.98718 4.4375,0 0,-1 -4.4375,0 L 567,141.36218 z"
-     id="path16742"
-     inkscape:connector-curvature="0"
-     sodipodi:nodetypes="cccccccccccccccccccc" />
   <rect
      inkscape:label="#rect16270"
      style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
@@ -91254,7 +91227,7 @@
      transform="matrix(0.1343319,0,0,0.1343319,885.45598,321.13309)" />
   <path
      style="fill:#0fff09;fill-opacity:1;fill-rule:evenodd;stroke:#1d0000;stroke-width:0.30481818;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="m 666.0806,603.80447 10.83519,5.4176 -10.83519,5.41759 z"
+     d="m 661.47632,598.046 10.83519,5.4176 -10.83519,5.41759 z"
      id="rect16426"
      sodipodi:nodetypes="cccc"
      inkscape:connector-curvature="0" />
@@ -91280,8 +91253,8 @@
      id="rect17210"
      width="10.821424"
      height="10.821424"
-     x="666.58472"
-     y="634.71997" />
+     x="661.57739"
+     y="629.05286" />
   <rect
      inkscape:label="#rect16270"
      style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
@@ -92909,7 +92882,7 @@
        x="-1060.788"
        y="501.19318"
        id="ImportFile"
-       style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+       style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:none;stroke-width:1;marker:none;enable-background:accumulate" />
     <g
        id="g17936"
        transform="translate(-1334.2792,308.62148)">
@@ -92932,7 +92905,7 @@
          inkscape:connector-curvature="0" />
       <path
          d="m 277.24218,192.81529 h 11.5 c 0.683,0.2373 4.541,3.1281 5.5,5 0,5.7292 4e-5,11.271 4e-5,17 h -17 v -22 z"
-         style="fill:url(#linearGradient17977);stroke:url(#linearGradient17979);stroke-width:0.99992001;stroke-linejoin:round"
+         style="display:inline;fill:url(#linearGradient17977);stroke:url(#linearGradient17979);stroke-width:0.99992001;stroke-linejoin:round"
          id="path4160"
          inkscape:connector-curvature="0" />
       <path
@@ -92968,15 +92941,11 @@
            r="34.144001"
            transform="matrix(0.3316761,0,0,0.3316761,48.927852,9.2318583)"
            id="circle29-8"
-           style="fill:#84c225;fill-rule:evenodd;stroke:#5d9d35;stroke-width:2.82220006"
-           sodipodi:ry="34.144001"
-           sodipodi:rx="34.144001"
-           sodipodi:cy="110.081"
-           sodipodi:cx="100.287" />
+           style="fill:#84c225;fill-rule:evenodd;stroke:#5d9d35;stroke-width:2.82220006" />
         <path
-           d="m 84.515333,38.943636 0,3.981494 3.981494,0 0,4.799639 -3.981494,0 0,3.981494 -4.782372,0 0,-3.981494 -3.981494,0 0,-4.799639 3.981494,0 0,-3.981494 4.782372,0"
+           d="m 84.515333,38.943636 v 3.981494 h 3.981494 v 4.799639 h -3.981494 v 3.981494 H 79.732961 V 47.724769 H 75.751467 V 42.92513 h 3.981494 v -3.981494 h 4.782372"
            id="text1332-4"
-           style="font-size:12px;font-style:normal;font-weight:normal;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans"
+           style="font-style:normal;font-weight:normal;font-size:12px;font-family:'Bitstream Vera Sans';display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none"
            inkscape:connector-curvature="0" />
         <path
            d="m 82.190677,35.644078 c 5.025924,0 9.194867,3.673581 9.969229,8.481465 -6.921917,-5.82623 -17.958314,0.09291 -16.467662,9.346307 -2.201037,-1.852678 -3.600446,-4.62745 -3.600446,-7.728889 0,-5.576508 4.522377,-10.098883 10.098879,-10.098883 z"
@@ -93722,54 +93691,26 @@
   </g>
   <path
      id="path18470"
-     style="color:#000000;fill:#ffcc00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-     d="m 506,151.34936 3.99998,-4 -2.99999,-1e-5 -3e-5,-11.99998 -1.99998,-2e-5 -10e-6,12 -2.99998,3e-5 4.00001,3.99998 z m -8,-15.98718 -4,4 3,0 0,12 2,0 0,-12 3,0 -4,-4 z" />
-  <path
-     sodipodi:nodetypes="cccccccccccccccccccc"
+     style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffcc00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate"
+     d="m 507,153.36218 4.99998,-6.01282 -2.99999,-1e-5 -3e-5,-13.99998 -3.99998,-2e-5 -10e-6,14 -2.99998,3e-5 z m -10,-20 -5,6 h 3 v 14 h 4 v -14 h 3 z"
      inkscape:connector-curvature="0"
-     id="path18485"
-     d="m 620,141.40625 -4,-0.0441 -2.5625,3.01282 -5.4375,0 0,1 5.4375,0 2.5625,2.98718 4,-0.0184 0,-3.46875 z m 5,0 0,3.46875 0,3.46875 4,0.0184 2.5625,-2.98718 4.4375,0 0,-1 -4.4375,0 L 629,141.36218 z"
-     style="fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
-  <path
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
-     d="m 620,139.36218 -1,-1"
-     id="path18493"
-     inkscape:connector-curvature="0" />
-  <path
-     inkscape:connector-curvature="0"
-     id="path19263"
-     d="m 625,139.36218 1,-1"
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
-  <path
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
-     d="m 622.52384,138.1738 0,-1.4142"
-     id="path19265"
-     inkscape:connector-curvature="0" />
-  <path
-     inkscape:connector-curvature="0"
-     id="path19267"
-     d="m 620,150.36218 -1,1"
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
-  <path
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
-     d="m 625,150.36218 1,1"
-     id="path19269"
-     inkscape:connector-curvature="0" />
-  <path
-     inkscape:connector-curvature="0"
-     id="path19271"
-     d="m 622.52384,151.55056 0,1.4142"
-     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
-  <path
-     style="fill:#00ff00;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
-     d="M 79 192.375 L 79 194.375 L 76 194.375 L 76 198.375 L 79 198.375 L 79 200.375 L 83 196.375 L 79 192.375 z M 65 194.375 L 65 198.375 L 66 198.375 L 66 194.375 L 65 194.375 z M 67 194.375 L 67 198.375 L 68 198.375 L 68 194.375 L 67 194.375 z M 69 194.375 L 69 198.375 L 71 198.375 L 71 194.375 L 69 194.375 z M 72 194.375 L 72 198.375 L 75 198.375 L 75 194.375 L 72 194.375 z "
-     id="path18382" />
-  <path
-     sodipodi:nodetypes="ccccccccccccccccccccccccccccc"
-     inkscape:connector-curvature="0"
-     id="path18467"
-     d="m 105,193.88061 0,5.01282 2,0 0,-5.01282 z m 0,6.01282 0,4 2,0 0,-4 z m 0,5 0,3 2,0 0,-3 z m 0,4 0,2 2,0 0,-2 z m -2,3 1,1 4,0 1,-1 z m 2,2 1,1 1,-1 z"
-     style="fill:#808080;stroke:none" />
+     sodipodi:nodetypes="cccccccccccccccc" />
+  <g
+     id="g52799"
+     transform="rotate(90,68.506409,196.86859)">
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#808080;stroke:none"
+       d="m 79.943848,189.4415 v 2.9335 h -15.375 v 6.0665 h 15.375 v 3 l 6,-6 z"
+       id="path18402"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       id="path18382"
+       d="m 79.375,188.375 v 2.9335 H 64 v 6.0665 h 15.375 v 3 l 6,-6 z"
+       style="fill:#00ff00;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccccccc" />
+  </g>
   <g
      id="g18416"
      clip-path="none">
@@ -93778,7 +93719,7 @@
        inkscape:radius="1"
        inkscape:original="M 108 192.36133 L 108 214.36133 L 110 214.36133 L 110 203.36133 L 111 203.36133 L 111 213.36133 L 113 213.36133 L 113 201.36133 L 114 201.36133 L 114 214.36133 L 117 214.36133 L 117 202.36133 L 118 202.36133 L 118 215.36133 L 120 215.36133 L 120 201.36133 L 121 201.36133 L 121 212.36133 L 123 212.36133 L 123 202.36133 L 124 202.36133 L 124 214.36133 L 125 214.36133 L 125 197.36133 L 120 192.36133 L 108 192.36133 z "
        xlink:href="#path18406"
-       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate"
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#aaaaaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate"
        id="path18446"
        inkscape:href="#path18406"
        d="m 108,191.36133 a 1.0001,1.0001 0 0 0 -1,1 v 22 a 1.0001,1.0001 0 0 0 1,1 h 2 a 1.0001,1.0001 0 0 0 1,-1 h 2 a 1.0001,1.0001 0 0 0 1,1 h 3 a 1.0001,1.0001 0 0 0 1,1 h 2 a 1.0001,1.0001 0 0 0 1,-1 v -2 h 2 v 1 a 1.0001,1.0001 0 0 0 1,1 h 1 a 1.0001,1.0001 0 0 0 1,-1 v -17 a 1.0001,1.0001 0 0 0 -0.29297,-0.70703 l -5,-5 A 1.0001,1.0001 0 0 0 120,191.36133 Z" />
@@ -93787,7 +93728,7 @@
        inkscape:connector-curvature="0"
        id="path18406"
        d="m 108,192.36218 v 22 h 2 v -11 h 1 v 10 h 2 v -12 h 1 v 13 h 3 v -12 h 1 v 13 h 2 v -14 h 1 v 11 h 2 v -10 h 1 v 12 h 1 v -17 l -5,-5 z"
-       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate" />
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ededed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;marker:none;enable-background:accumulate" />
     <g
        id="g18450"
        clip-path="url(#clipPath18454)">
@@ -93803,12 +93744,6 @@
          id="path18412" />
     </g>
   </g>
-  <path
-     style="fill:#ff0000;stroke:none"
-     d="m 104,193.34936 0,5.01282 2,0 0,-5.01282 z m 0,6.01282 0,4 2,0 0,-4 z m 0,5 0,3 2,0 0,-3 z m 0,4 0,2 2,0 0,-2 z m -2,3 1,1 4,0 1,-1 z m 2,2 1,1 1,-1 z"
-     id="path18458"
-     inkscape:connector-curvature="0"
-     sodipodi:nodetypes="ccccccccccccccccccccccccccccc" />
   <text
      style="font-style:normal;font-weight:normal;font-size:20px;line-height:0%;font-family:'Bitstream Vera Sans';fill:#000000;fill-opacity:1;stroke:none"
      xml:space="preserve"
@@ -93949,86 +93884,15 @@
        style="font-size:51.04000092px">%% about_brz_logo %%</tspan></text>
   <g
      transform="matrix(0.2224431,0,0,0.2224431,608.34301,1025.5992)"
-     id="about_brz_logo"
-     inkscape:label="#g17803-0">
-    <g
-       transform="translate(-1840,2339.9999)"
-       mask="url(#mask6467-7-2)"
-       id="use16743-9" />
-    <g
-       transform="translate(0,89.910633)"
-       id="g7269-3">
-      <path
-         inkscape:connector-curvature="0"
-         d="m -1470.2813,1725.0291 c -56.0138,0.054 -112.0828,20.5177 -156.0937,61.5937 -85.0794,79.4058 -95.9453,209.1111 -29.5938,301.1563 l 9.7813,69.3437 0.9062,6.4375 5.125,-4 30.5938,-23.9687 c 87.5525,67.3697 213.0935,63.1007 295.9375,-14.2188 18.4642,-17.2329 33.4323,-36.8343 44.875,-57.9062 6.4003,0.736 13.3613,1.0937 20.875,1.0937 24.9087,0 44.0178,-3.5634 57.3437,-10.6875 13.3257,-7.1242 24.6943,-18.8804 34.125,-35.2812 l -61.6562,-5.6875 c -3.8953,4.9202 -7.5237,8.3649 -10.9063,10.3125 -5.5355,3.0752 -11.381,4.5937 -17.5312,4.5937 -2.2646,0 -4.435,-0.18 -6.5,-0.5625 3.5746,-10.6475 6.37,-21.5105 8.3437,-32.5 h 91.8125 v -7.0625 c -10e-5,-21.5262 -3.5522,-39.0091 -10.625,-52.4375 -7.0731,-13.4281 -17.3756,-23.6769 -30.9062,-30.75 -13.3838,-6.9958 -31.5824,-10.5176 -54.5938,-10.5937 -7.0146,-25.9757 -18.6908,-50.9762 -35.0625,-73.6875 l -9.7812,-69.3438 -0.9063,-6.4375 -5.125,4 -30.5937,23.9688 c -41.0402,-31.5796 -90.4197,-47.4228 -139.8438,-47.375 z m 228.125,206.2187 c 6.3617,0.8346 11.6486,3.3459 15.875,7.5313 5.279,5.2278 8.5511,13.9044 9.7813,26 h -24.7813 c 0.5248,-11.1718 0.225,-22.3843 -0.875,-33.5313 z m 118.9731,-33.6623 h 58.582 v 26.754 c 5.6377,-11.583 11.4549,-19.5528 17.4516,-23.9095 5.9965,-4.3563 13.4025,-6.5346 22.2182,-6.5347 9.2253,1e-4 19.3222,2.8703 30.2904,8.6104 l -19.3736,44.5901 c -7.3805,-3.0751 -13.2233,-4.6127 -17.5285,-4.6128 -8.2005,10e-5 -14.5559,3.3828 -19.066,10.1481 -6.458,9.5331 -9.6869,27.3691 -9.6868,53.508 v 54.7381 h -62.8873 z m 320.2793,97.1755 h -125.46709 c 1.12748,10.0456 3.84388,17.5285 8.14921,22.4487 6.04775,7.073 13.94069,10.6094 23.67883,10.6094 6.15024,0 11.99306,-1.5376 17.5285,-4.6128 3.38256,-1.9476 7.02151,-5.3815 10.91686,-10.3018 l 61.65724,5.6891 c -9.43072,16.4009 -20.80885,28.1634 -34.13443,35.2876 -13.3259,7.1241 -32.44322,10.6862 -57.35199,10.6862 -21.62881,0 -38.64475,-3.0495 -51.04789,-9.1486 -12.40324,-6.0991 -22.67944,-15.7859 -30.82862,-29.0604 -8.14922,-13.2745 -12.22382,-28.881 -12.22382,-46.8195 0,-25.5239 8.17483,-46.1788 24.52452,-61.9648 16.34962,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.82222,3.5366 55.35313,10.6093 13.53059,7.0731 23.83241,17.3236 30.9055,30.7517 7.0727,13.4284 10.60915,30.9056 10.60935,52.4318 z m -63.6561,-29.983 c -1.23021,-12.0956 -4.48476,-20.7573 -9.76368,-25.9852 -5.27917,-5.2277 -12.22393,-7.8416 -20.8343,-7.8417 -9.94316,10e-5 -17.88735,3.9466 -23.8326,11.8394 -3.79279,4.9204 -6.20167,12.2496 -7.22666,21.9875 z m 93.17774,-67.1925 h 58.4283 v 23.8326 c 8.40539,-9.9429 16.88773,-17.0158 25.44706,-21.2187 8.55912,-4.2026 18.88657,-6.304 30.98238,-6.3041 13.01809,1e-4 23.31991,2.3065 30.90549,6.9191 7.58526,4.6129 13.78685,11.4808 18.6048,20.6037 9.84037,-10.6605 18.80962,-17.9128 26.90778,-21.7569 8.09773,-3.8438 18.09204,-5.7658 29.98294,-5.7659 17.52823,1e-4 31.21274,5.2023 41.05357,15.6065 9.84026,10.4044 14.76054,26.6772 14.76083,48.8184 v 102.557 h -62.73354 v -93.024 c -2.3e-4,-7.3803 -1.43531,-12.8644 -4.30524,-16.4522 -4.20297,-5.6377 -9.43076,-8.4566 -15.68339,-8.4567 -7.38062,10e-5 -13.32595,2.6652 -17.83601,7.9954 -4.51044,5.3304 -6.76557,13.8897 -6.76538,25.6777 v 84.2598 h -62.73355 v -89.9488 c -1.2e-4,-7.1753 -0.41015,-12.0444 -1.23007,-14.6071 -1.3327,-4.1001 -3.63907,-7.4059 -6.91914,-9.9174 -3.2803,-2.5113 -7.12426,-3.767 -11.5319,-3.7671 -7.1755,10e-5 -13.06958,2.7165 -17.68225,8.1492 -4.61284,5.4329 -6.91922,14.3509 -6.91914,26.754 v 83.3372 h -62.73354 z m 316.74293,-62.1185 h 62.57978 v 42.5911 h -62.57978 z m 0,62.1185 h 62.57978 v 163.2917 h -62.57978 z m 95.79168,0 h 151.45231 v 36.5945 l -82.41466,83.9523 h 87.48869 v 42.7449 H -366.998 v -40.5923 l 78.10941,-79.9545 h -71.95906 z"
-         id="path17845-6"
-         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:314.89779663px;line-height:125%;font-family:'Arial Black';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;filter:url(#filter17760-6-7);enable-background:accumulate" />
-      <path
-         inkscape:connector-curvature="0"
-         d="m -1336.1632,1788.2263 c 0,0 56.3913,141.5671 -147.8368,147.7737 -204.2612,6.2076 -147.8368,147.7737 -147.8368,147.7737 -37.8479,-37.8317 -61.2676,-90.0855 -61.2676,-147.7737 0,-57.6882 23.4197,-109.942 61.2676,-147.7737 37.8479,-37.8318 90.124,-61.2415 147.8368,-61.2415 57.7128,0 109.9889,23.4097 147.8368,61.2415 z"
-         id="use16735-0"
-         style="display:inline;overflow:visible;visibility:visible;fill:#d19f34;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate" />
-      <path
-         inkscape:connector-curvature="0"
-         d="m -1631.8368,2083.7737 c 0,0 -56.3913,-141.5671 147.8368,-147.7737 204.2612,-6.2076 147.8368,-147.7737 147.8368,-147.7737 37.8479,37.8317 61.2676,90.0855 61.2676,147.7737 0,57.6882 -23.4197,109.942 -61.2676,147.7737 -37.8479,37.8318 -90.124,61.2415 -147.8368,61.2415 -57.7128,0 -109.9889,-23.4097 -147.8368,-61.2415 z"
-         id="use16737-6"
-         style="display:inline;overflow:visible;visibility:visible;fill:#469837;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate" />
-      <path
-         inkscape:connector-curvature="0"
-         d="m 1021.2642,207.94408 c 0,143.9361 -116.68326,260.61936 -260.61936,260.61936 -143.9361,0 -260.61936,-116.68326 -260.61936,-260.61936 0,-143.936098 116.68326,-260.61936 260.61936,-260.61936 143.9361,0 260.61936,116.683262 260.61936,260.61936 z"
-         transform="matrix(0.8023362,0,0,0.8019941,-2094.2929,1769.2301)"
-         id="path16739-2"
-         style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#4c4c4c;stroke-width:49.86504364;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10.43299961;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
-      <path
-         inkscape:connector-curvature="0"
-         d="m -1577.2331,1825.1726 h 127.038 c 21.1728,2e-4 37.4271,5.2435 48.7628,15.7299 11.3353,10.4868 17.0031,23.4703 17.0033,38.9503 -2e-4,12.9836 -4.045,24.1194 -12.1345,33.4074 -5.3933,6.1923 -13.2833,11.086 -23.6698,14.6813 15.7797,3.7953 27.3898,10.312 34.8306,19.5501 7.4402,9.2383 11.1605,20.8485 11.1607,34.8306 -2e-4,11.3855 -2.6468,21.6224 -7.9399,30.7108 -5.2934,9.0884 -12.5342,16.2792 -21.7223,21.5725 -5.6929,3.2958 -14.2819,5.6927 -25.7671,7.1908 -15.2807,1.9975 -25.4177,2.9962 -30.4112,2.9962 h -117.1506 z m 68.4627,86.1401 h 29.5124 c 10.5863,10e-5 17.9519,-1.8225 22.0968,-5.468 4.1445,-3.6452 6.2169,-8.9135 6.217,-15.8049 -1e-4,-6.3916 -2.0725,-11.3853 -6.217,-14.9809 -4.1449,-3.5952 -11.3607,-5.3929 -21.6474,-5.3931 h -29.9618 z m 0,86.29 h 34.6059 c 11.6849,0 19.9244,-2.0723 24.7184,-6.2171 4.7938,-4.1447 7.1907,-9.7126 7.1909,-16.7037 -2e-4,-6.4916 -2.3722,-11.71 -7.116,-15.655 -4.7441,-3.9449 -13.0584,-5.9174 -24.9432,-5.9175 h -34.456 z"
-         id="path16741-6"
-         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:306.80871582px;line-height:125%;font-family:'Arial Black';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:40;marker:none;enable-background:accumulate" />
-      <g
-         transform="rotate(180,-563.99995,766)"
-         mask="url(#mask6467-7-2)"
-         id="use16745-1">
-        <path
-           inkscape:connector-curvature="0"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           id="use6863-8"
-           style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447-9-6);enable-background:accumulate" />
-        <path
-           inkscape:connector-curvature="0"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           id="use6865-7"
-           style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate" />
-      </g>
-      <g
-         transform="translate(-1839.8676,2340.0508)"
-         mask="url(#mask6467-7-2)"
-         id="g7285-9">
-        <path
-           inkscape:connector-curvature="0"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           id="path7287-2"
-           style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;filter:url(#filter6447-9-6);enable-background:accumulate" />
-        <path
-           inkscape:connector-curvature="0"
-           d="m 507.8125,-566.84375 c -7.54052,0.31127 -14.32442,4.87714 -17.4375,11.84375 -3.32061,7.43106 -1.79456,16.12851 3.84375,22 71.38742,76.4228 67.29917,195.79932 -9.15625,267.15625 C 418.92868,-204.1201 321.00173,-198.52349 249.15625,-248 l 26.65625,-20.875 5.125,-4 -6.03125,-2.40625 -105.9375,-42.59375 -6,-2.4375 0.90625,6.4375 15.9375,113 0.90625,6.4375 5.125,-4 30.59375,-23.96875 c 87.55252,67.36975 213.09347,63.10079 295.9375,-14.21875 92.27769,-86.12407 97.25455,-231.41793 11.09375,-323.65625 -3.63563,-4.00109 -8.72059,-6.38195 -14.125,-6.5625 -0.50858,-0.0174 -1.02855,-0.0208 -1.53125,0 z"
-           id="path7289-0"
-           style="display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:49.86504364;marker:none;enable-background:accumulate" />
-      </g>
-      <path
-         inkscape:connector-curvature="0"
-         d="m -1166.8587,1976.761 h -125.4671 c 1.1275,10.0456 3.8439,17.5285 8.1492,22.4487 6.0478,7.073 13.9407,10.6094 23.6789,10.6094 6.1502,0 11.993,-1.5376 17.5285,-4.6128 3.3825,-1.9476 7.0215,-5.3815 10.9168,-10.3018 l 61.6573,5.6891 c -9.4307,16.4009 -20.8089,28.1634 -34.1345,35.2876 -13.3259,7.1241 -32.4432,10.6862 -57.3519,10.6862 -21.6289,0 -38.6448,-3.0495 -51.0479,-9.1486 -12.4033,-6.0991 -22.6795,-15.7859 -30.8286,-29.0604 -8.1493,-13.2745 -12.2239,-28.881 -12.2239,-46.8195 0,-25.5239 8.1749,-46.1788 24.5245,-61.9648 16.3497,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.8223,3.5366 55.3532,10.6093 13.5306,7.0731 23.8324,17.3236 30.9055,30.7517 7.0727,13.4284 10.6091,30.9056 10.6093,52.4318 z m -63.6561,-29.983 c -1.2302,-12.0956 -4.4847,-20.7573 -9.7637,-25.9852 -5.2791,-5.2277 -12.2239,-7.8416 -20.8343,-7.8417 -9.9431,10e-5 -17.8873,3.9466 -23.8325,11.8394 -3.7928,4.9204 -6.2017,12.2496 -7.2267,21.9875 z m 93.3316,-67.1925 h 58.582 v 26.754 c 5.6377,-11.583 11.4549,-19.5528 17.4516,-23.9095 5.9965,-4.3563 13.4025,-6.5346 22.2182,-6.5347 9.2253,1e-4 19.3222,2.8703 30.2904,8.6104 l -19.3736,44.5901 c -7.3805,-3.0751 -13.2233,-4.6127 -17.5285,-4.6128 -8.2005,10e-5 -14.5559,3.3828 -19.066,10.1481 -6.458,9.5331 -9.6869,27.3691 -9.6868,53.508 v 54.7381 h -62.8873 z m 320.2793,97.1755 h -125.46709 c 1.12748,10.0456 3.84388,17.5285 8.14921,22.4487 6.04775,7.073 13.94069,10.6094 23.67883,10.6094 6.15024,0 11.99306,-1.5376 17.5285,-4.6128 3.38256,-1.9476 7.02151,-5.3815 10.91686,-10.3018 l 61.65724,5.6891 c -9.43072,16.4009 -20.80885,28.1634 -34.13443,35.2876 -13.3259,7.1241 -32.44322,10.6862 -57.35199,10.6862 -21.62881,0 -38.64475,-3.0495 -51.04789,-9.1486 -12.40324,-6.0991 -22.67944,-15.7859 -30.82862,-29.0604 -8.14922,-13.2745 -12.22382,-28.881 -12.22382,-46.8195 0,-25.5239 8.17483,-46.1788 24.52452,-61.9648 16.34962,-15.7857 38.9265,-23.6787 67.7307,-23.6788 23.3712,1e-4 41.82222,3.5366 55.35313,10.6093 13.53059,7.0731 23.83241,17.3236 30.9055,30.7517 7.0727,13.4284 10.60915,30.9056 10.60935,52.4318 z m -63.6561,-29.983 c -1.23021,-12.0956 -4.48476,-20.7573 -9.76368,-25.9852 -5.27917,-5.2277 -12.22393,-7.8416 -20.8343,-7.8417 -9.94316,10e-5 -17.88735,3.9466 -23.8326,11.8394 -3.79279,4.9204 -6.20167,12.2496 -7.22666,21.9875 z m 93.17774,-67.1925 h 58.4283 v 23.8326 c 8.40539,-9.9429 16.88773,-17.0158 25.44706,-21.2187 8.55912,-4.2026 18.88657,-6.304 30.98238,-6.3041 13.01809,1e-4 23.31991,2.3065 30.90549,6.9191 7.58526,4.6129 13.78685,11.4808 18.6048,20.6037 9.84037,-10.6605 18.80962,-17.9128 26.90778,-21.7569 8.09773,-3.8438 18.09204,-5.7658 29.98294,-5.7659 17.52823,1e-4 31.21274,5.2023 41.05357,15.6065 9.84026,10.4044 14.76054,26.6772 14.76083,48.8184 v 102.557 h -62.73354 v -93.024 c -2.3e-4,-7.3803 -1.43531,-12.8644 -4.30524,-16.4522 -4.20297,-5.6377 -9.43076,-8.4566 -15.68339,-8.4567 -7.38062,10e-5 -13.32595,2.6652 -17.83601,7.9954 -4.51044,5.3304 -6.76557,13.8897 -6.76538,25.6777 v 84.2598 h -62.73355 v -89.9488 c -1.2e-4,-7.1753 -0.41015,-12.0444 -1.23007,-14.6071 -1.3327,-4.1001 -3.63907,-7.4059 -6.91914,-9.9174 -3.2803,-2.5113 -7.12426,-3.767 -11.5319,-3.7671 -7.1755,10e-5 -13.06958,2.7165 -17.68225,8.1492 -4.61284,5.4329 -6.91922,14.3509 -6.91914,26.754 v 83.3372 h -62.73354 z m 316.74293,-62.1185 h 62.57978 v 42.5911 h -62.57978 z m 0,62.1185 h 62.57978 v 163.2917 h -62.57978 z m 95.79168,0 h 151.45231 v 36.5945 l -82.41466,83.9523 h 87.48869 v 42.7449 H -380.998 v -40.5923 l 78.10941,-79.9545 h -71.95906 z"
-         id="text16729-2"
-         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:314.89779663px;line-height:125%;font-family:'Arial Black';text-align:start;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#dadada;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10.43299961;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
-      <text
-         x="-1259.6362"
-         y="2147.7705"
-         id="text3172-3"
-         xml:space="preserve"
-         style="font-style:normal;font-weight:normal;font-size:71.35877228px;line-height:0%;font-family:'Bitstream Vera Sans';display:inline;fill:#000000;fill-opacity:1;stroke:none"><tspan
-           x="-1259.6362"
-           y="2147.7705"
-           id="tspan3174-7">Free Software for Automation </tspan></text>
-    </g>
+     id="about_brz_logo">
+    <use
+       transform="matrix(4.4955317,0,0,4.4955317,2592.5922,-2515.4545)"
+       x="0"
+       y="0"
+       xlink:href="#g52678"
+       id="use52780"
+       width="100%"
+       height="100%" />
   </g>
   <g
      transform="translate(960.38982,282.02716)"
@@ -95240,4 +95104,49 @@
        d="m 1106,195.36218 v 1 h -6 v -1 z"
        sodipodi:nodetypes="ccccc" />
   </g>
+  <g
+     id="g52819"
+     transform="rotate(45,111.50001,200.12138)">
+    <path
+       style="fill:#808080;stroke:none"
+       d="m 103.15931,201.66277 h 17.01282 v -2.00001 h -17.01282 z"
+       id="path52811"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       sodipodi:nodetypes="ccccc"
+       inkscape:connector-curvature="0"
+       id="path52805"
+       d="m 112.66573,209.16918 v -17.01282 h -2.00001 v 17.01282 z"
+       style="fill:#808080;stroke:none" />
+    <path
+       style="fill:#ff0000;stroke:none"
+       d="M 112.33428,208.08643 V 191.0736 h -2.00001 v 17.01283 z"
+       id="path52807"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       sodipodi:nodetypes="ccccc"
+       inkscape:connector-curvature="0"
+       id="path52813"
+       d="m 102.82786,200.58002 h 17.01283 v -2.00001 h -17.01283 z"
+       style="fill:#ff0000;stroke:none" />
+  </g>
+  <path
+     sodipodi:nodetypes="ccccccccccccccccccc"
+     inkscape:connector-curvature="0"
+     id="use16453"
+     d="m 565.50059,134.36455 c -3.21171,0 -6.18403,1.71679 -7.78988,4.49822 -0.54331,0.94101 -0.88738,1.959 -1.06307,2.99881 h -4.14112 c -2.02768,-0.0286 -2.02768,3.0275 0,2.99882 h 4.14112 c 0.17569,1.03979 0.51976,2.05778 1.06307,2.99881 1.60585,2.78143 4.57817,4.49822 7.78988,4.49822 0.82807,-9e-5 1.49932,-0.67134 1.49941,-1.4994 v -1.49941 h 4.49822 c 2.02743,0.0284 2.02743,-3.02724 0,-2.99882 H 567 v -5.99762 h 4.49822 c 2.02743,0.0284 2.02743,-3.02724 0,-2.99882 H 567 v -1.49941 c -9e-5,-0.82806 -0.67134,-1.49931 -1.49941,-1.4994 z"
+     style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.49940741;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+  <path
+     style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#008000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.49940741;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+     d="m 625.50059,134.36455 c -3.21171,0 -6.18403,1.71679 -7.78988,4.49822 -0.54331,0.94101 -0.88738,1.959 -1.06307,2.99881 h -4.14112 c -2.02768,-0.0286 -2.02768,3.0275 0,2.99882 h 4.14112 c 0.17569,1.03979 0.51976,2.05778 1.06307,2.99881 1.60585,2.78143 4.57817,4.49822 7.78988,4.49822 0.82807,-9e-5 1.49932,-0.67134 1.49941,-1.4994 v -1.49941 h 4.49822 c 2.02743,0.0284 2.02743,-3.02724 0,-2.99882 H 627 v -5.99762 h 4.49822 c 2.02743,0.0284 2.02743,-3.02724 0,-2.99882 H 627 v -1.49941 c -9e-5,-0.82806 -0.67134,-1.49931 -1.49941,-1.4994 z"
+     id="path16494"
+     inkscape:connector-curvature="0"
+     sodipodi:nodetypes="ccccccccccccccccccc" />
+  <path
+     style="opacity:1;vector-effect:none;fill:#ff1b00;fill-opacity:1;stroke:none;stroke-width:0.74999982;stroke-opacity:1;marker:none"
+     d="m 615.63605,136.99738 a 8.9999978,8.9999978 0 0 0 0,12.7279 8.9999978,8.9999978 0 0 0 12.7279,0 8.9999978,8.9999978 0 0 0 0,-12.7279 8.9999978,8.9999978 0 0 0 -12.7279,0 z m 1.59098,1.59098 a 6.7499981,6.7499981 0 0 1 8.68207,-0.72921 l -9.40608,9.40612 a 6.7499981,6.7499981 0 0 1 0.72401,-8.67691 z m 0.8618,10.27309 9.40609,-9.40612 a 6.7499981,6.7499981 0 0 1 -0.72195,8.67897 6.7499981,6.7499981 0 0 1 -8.68414,0.72715 z"
+     id="path16496"
+     inkscape:connector-curvature="0" />
 </svg>
Binary file images/icoplay24.png has changed
Binary file images/icostop24.png has changed
Binary file images/poe.ico has changed
Binary file images/splash.png has changed
--- a/modbus/mb_utils.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/modbus/mb_utils.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,9 +22,6 @@
 # This code is made available on the understanding that it will not be
 # used in safety-critical situations without a full and competent review.
 
-from __future__ import absolute_import
-from __future__ import division
-from six.moves import xrange
 
 # dictionary implementing:
 # key   - string with the description we want in the request plugin GUI
@@ -48,7 +45,7 @@
 
 # Configuration tree value acces helper, for multiple values
 def GetCTVals(child, indexes):
-    return map(lambda index: GetCTVal(child, index), indexes)
+    return [GetCTVal(child, index) for index in indexes]
 
 
 def GetTCPServerNodePrinted(self, child):
@@ -60,7 +57,7 @@
 {"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", %(slaveid)s, {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}'''
 
     location = ".".join(map(str, child.GetCurrentLocation()))
-    config_name, host, port, slaveid = GetCTVals(child, range(4))
+    config_name, host, port, slaveid = GetCTVals(child, list(range(4)))
     if host == "#ANY#":
         host = ''
     # slaveid = GetCTVal(child, 2)
@@ -90,16 +87,16 @@
     request_dict["locreqstr"] = "_".join(map(str, child.GetCurrentLocation()))
     request_dict["nodeid"] = str(nodeid)
     request_dict["address"] = GetCTVal(child, 2)
-    if int(request_dict["address"]) not in xrange(65536):
+    if int(request_dict["address"]) not in range(65536):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid Start Address in server memory area node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
     request_dict["count"] = GetCTVal(child, 1)
-    if int(request_dict["count"]) not in xrange(1, 65537):
+    if int(request_dict["count"]) not in range(1, 65537):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
-    if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537):
+    if (int(request_dict["address"]) + int(request_dict["count"])) not in range(1, 65537):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
@@ -122,7 +119,7 @@
 {"%(locnodestr)s", "%(config_name)s", "%(device)s", "",%(slaveid)s, {naf_rtu, {.rtu = {NULL, %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}'''
 
     location = ".".join(map(str, child.GetCurrentLocation()))
-    config_name, device, baud, parity, stopbits, slaveid = GetCTVals(child, range(6))
+    config_name, device, baud, parity, stopbits, slaveid = GetCTVals(child, list(range(6)))
 
     node_dict = {"locnodestr": location,
                  "config_name": config_name,
@@ -143,7 +140,7 @@
 {"%(locnodestr)s", "%(config_name)s", "%(device)s", "", {naf_rtu, {.rtu = {NULL, %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period  (ms)*/, %(coms_delay)s /* inter request delay (ms)*/, 0 /* prev_error */}'''
 
     location = ".".join(map(str, child.GetCurrentLocation()))
-    config_name, device, baud, parity, stopbits, coms_period, coms_delay = GetCTVals(child, range(7))
+    config_name, device, baud, parity, stopbits, coms_period, coms_delay = GetCTVals(child, list(range(7)))
 
     node_dict = {"locnodestr": location,
                  "config_name": config_name,
@@ -165,7 +162,7 @@
 {"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period (ms)*/, %(coms_delay)s /* inter request delay (ms)*/, 0 /* prev_error */}'''
 
     location = ".".join(map(str, child.GetCurrentLocation()))
-    config_name, host, port, coms_period, coms_delay = GetCTVals(child, range(5))
+    config_name, host, port, coms_period, coms_delay = GetCTVals(child, list(range(5)))
 
     node_dict = {"locnodestr": location,
                  "config_name": config_name,
@@ -211,19 +208,19 @@
         "iotype": modbus_function_dict[GetCTVal(child, 0)][1],
         "maxcount": modbus_function_dict[GetCTVal(child, 0)][2]}
 
-    if int(request_dict["slaveid"]) not in xrange(256):
+    if int(request_dict["slaveid"]) not in range(256):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid slaveID in TCP client request node %(locreqstr)s (Must be in the range [0..255])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
-    if int(request_dict["address"]) not in xrange(65536):
+    if int(request_dict["address"]) not in range(65536):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid Start Address in TCP client request node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
-    if int(request_dict["count"]) not in xrange(1, 1 + int(request_dict["maxcount"])):
+    if int(request_dict["count"]) not in range(1, 1 + int(request_dict["maxcount"])):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (Must be in the range [1..%(maxcount)s])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
-    if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537):
+    if (int(request_dict["address"]) + int(request_dict["count"])) not in range(1, 65537):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (start_address + nr_channels must be less than 65536)\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
--- a/modbus/modbus.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/modbus/modbus.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,9 +23,7 @@
 # used in safety-critical situations without a full and competent review.
 
 
-from __future__ import absolute_import
 import os
-from six.moves import xrange
 
 from modbus.mb_utils import *
 from ConfigTreeNode import ConfigTreeNode
@@ -94,9 +92,9 @@
             if element["name"] == "ModbusRequest":
                 for child in element["children"]:
                     if child["name"] == "Function":
-                        list = modbus_function_dict.keys()
-                        list.sort()
-                        child["type"] = list
+                        _list = list(modbus_function_dict.keys())
+                        _list.sort()
+                        child["type"] = _list
         return infos
 
     def GetVariableLocationTree(self):
@@ -240,9 +238,9 @@
             if element["name"] == "MemoryArea":
                 for child in element["children"]:
                     if child["name"] == "MemoryAreaType":
-                        list = modbus_memtype_dict.keys()
-                        list.sort()
-                        child["type"] = list
+                        _list = list(modbus_memtype_dict.keys())
+                        _list.sort()
+                        child["type"] = _list
         return infos
 
     def GetVariableLocationTree(self):
@@ -618,7 +616,7 @@
                     if child["name"] == "Stop_Bits":
                         child["type"] = modbus_serial_stopbits_list
                     if child["name"] == "Parity":
-                        child["type"] = modbus_serial_parity_dict.keys()
+                        child["type"] = list(modbus_serial_parity_dict.keys())
         return infos
 
     # Return the number of (modbus library) nodes this specific RTU client will need
@@ -709,7 +707,7 @@
                     if child["name"] == "Stop_Bits":
                         child["type"] = modbus_serial_stopbits_list
                     if child["name"] == "Parity":
-                        child["type"] = modbus_serial_parity_dict.keys()
+                        child["type"] = list(modbus_serial_parity_dict.keys())
         return infos
 
     # Return the number of (modbus library) nodes this specific RTU slave will need
@@ -1026,7 +1024,7 @@
                             start_address = int(GetCTVal(subchild, 2))
                             relative_addr = absloute_address - start_address
                             # test if relative address in request specified range
-                            if relative_addr in xrange(int(GetCTVal(subchild, 1))):
+                            if relative_addr in range(int(GetCTVal(subchild, 1))):
                                 if str(iecvar["NAME"]) not in loc_vars_list:
                                     loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (
                                         server_id, memarea, absloute_address))
@@ -1080,7 +1078,7 @@
                             start_address = int(GetCTVal(subchild, 2))
                             relative_addr = absloute_address - start_address
                             # test if relative address in request specified range
-                            if relative_addr in xrange(int(GetCTVal(subchild, 1))):
+                            if relative_addr in range(int(GetCTVal(subchild, 1))):
                                 if str(iecvar["NAME"]) not in loc_vars_list:
                                     loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (
                                         server_id, memarea, absloute_address))
@@ -1116,7 +1114,7 @@
                         #        two numbers are always '0.0', and the first two identify the request.
                         #        In the following if, we check for this condition by checking
                         #        if there are at least 4 or more number in the location's address.
-                        if (        relative_addr in xrange(int(GetCTVal(subchild, 2)))  # condition (a) explained above
+                        if (        relative_addr in range(int(GetCTVal(subchild, 2)))  # condition (a) explained above
                             and len(iecvar["LOC"]) < 5):                                  # condition (b) explained above
                             if str(iecvar["NAME"]) not in loc_vars_list:
                                 loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
@@ -1177,7 +1175,7 @@
                         #        two numbers are always '0.0', and the first two identify the request.
                         #        In the following if, we check for this condition by checking
                         #        if there are at least 4 or more number in the location's address.
-                        if (        relative_addr in xrange(int(GetCTVal(subchild, 2)))  # condition (a) explained above
+                        if (        relative_addr in range(int(GetCTVal(subchild, 2)))  # condition (a) explained above
                             and len(iecvar["LOC"]) < 5):                                  # condition (b) explained above
                             if str(iecvar["NAME"]) not in loc_vars_list:
                                 loc_vars.append(
--- a/opc_ua/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/opc_ua/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,6 +1,6 @@
 # opcua/__init__.py
 
-from __future__ import absolute_import
+
 
 from .client import OPCUAClient
 
--- a/opc_ua/client.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/opc_ua/client.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,12 +1,12 @@
 # opcua/client.py
 
-from __future__ import absolute_import
+
 
 import os
 
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT
-from .opcua_client_maker import OPCUAClientPanel, OPCUAClientModel, UA_IEC_types
+from .opcua_client_maker import OPCUAClientPanel, OPCUAClientModel, UA_IEC_types, authParams
 
 import util.paths as paths
 
@@ -22,6 +22,9 @@
     ("include",),
     ("arch",)]]
 
+# Tests need to use other default hosts
+OPCUA_DEFAULT_HOST = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+
 class OPCUAClientEditor(ConfTreeNodeEditor):
     CONFNODEEDITOR_TABS = [
         (_("OPC-UA Client"), "CreateOPCUAClient_UI")]
@@ -29,18 +32,57 @@
     def Log(self, msg):
         self.Controler.GetCTRoot().logger.write(msg)
 
-    def UriGetter(self):
-        return self.Controler.GetServerURI() 
-
     def CreateOPCUAClient_UI(self, parent):
-        return OPCUAClientPanel(parent, self.Controler.GetModelData(), self.Log, self.UriGetter)
+        return OPCUAClientPanel(parent, self.Controler.GetModelData(), self.Log, self.Controler.GetConfig)
 
 class OPCUAClient(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:element name="OPCUAClient">
         <xsd:complexType>
-          <xsd:attribute name="Server_URI" type="xsd:string" use="optional" default="opc.tcp://localhost:4840"/>
+          <xsd:sequence>
+            <xsd:element name="AuthType" minOccurs="0">
+              <xsd:complexType>
+                <xsd:choice minOccurs="0">
+                  <xsd:element name="x509">
+                    <xsd:complexType>
+                      <xsd:sequence>
+                        <xsd:element name="Policy">
+                          <xsd:annotation>
+                            <xsd:documentation>Default to Basic256Sha256 if not specified</xsd:documentation>
+                          </xsd:annotation>
+                          <xsd:complexType>
+                            <xsd:choice minOccurs="0">
+                              <xsd:element name="Basic256Sha256"/>
+                              <xsd:element name="Basic128Rsa15"/>
+                              <xsd:element name="Basic256"/>
+                            </xsd:choice>
+                          </xsd:complexType>
+                        </xsd:element>
+                        <xsd:element name="Mode">
+                          <xsd:complexType>
+                            <xsd:choice minOccurs="0">
+                              <xsd:element name="SignAndEncrypt"/>
+                              <xsd:element name="Sign"/>
+                            </xsd:choice>
+                          </xsd:complexType>
+                        </xsd:element>
+                      </xsd:sequence>
+                      <xsd:attribute name="Certificate" type="xsd:string" use="optional" default="certificate.pem"/>
+                      <xsd:attribute name="PrivateKey" type="xsd:string" use="optional" default="private_key.pem"/>
+                    </xsd:complexType>
+                  </xsd:element>
+                  <xsd:element name="UserPassword">
+                    <xsd:complexType>
+                      <xsd:attribute name="User" type="xsd:string" use="optional"/>
+                      <xsd:attribute name="Password" type="xsd:string" use="optional"/>
+                    </xsd:complexType>
+                  </xsd:element>
+                </xsd:choice>
+              </xsd:complexType>
+            </xsd:element>
+          </xsd:sequence>
+          <xsd:attribute name="Server_URI" type="xsd:string" use="optional" default="opc.tcp://"""+OPCUA_DEFAULT_HOST+""":4840"/>
         </xsd:complexType>
       </xsd:element>
     </xsd:schema>
@@ -49,7 +91,7 @@
     EditorType = OPCUAClientEditor
 
     def __init__(self):
-        self.modeldata = OPCUAClientModel(self.Log)
+        self.modeldata = OPCUAClientModel(self.Log, self.CTNMarkModified)
 
         filepath = self.GetFileName()
         if os.path.isfile(filepath):
@@ -60,9 +102,30 @@
 
     def GetModelData(self):
         return self.modeldata
-    
-    def GetServerURI(self):
-        return self.GetParamsAttributes("OPCUAClient.Server_URI")["value"]
+
+    def GetConfig(self):
+        def cfg(path): 
+            try:
+                attr=self.GetParamsAttributes("OPCUAClient."+path)
+            except ValueError:
+                return None
+            return attr["value"]
+
+        AuthType = cfg("AuthType")
+        res = dict(URI=cfg("Server_URI"), AuthType=AuthType)
+
+        paramList = authParams.get(AuthType, None)
+        if paramList:
+            for name,default in paramList:
+                value = cfg("AuthType."+name)
+                if value == "" or value is None:
+                    value = default
+                # cryptomaterial is expected to be in project's user provide file directory
+                if name in ["Certificate","PrivateKey"]:
+                    value = os.path.join(self.GetCTRoot()._getProjectFilesPath(), value)
+                res[name] = value
+
+        return res
 
     def GetFileName(self):
         return os.path.join(self.CTNPath(), 'selected.csv')
@@ -76,16 +139,18 @@
         locstr = "_".join(map(str, current_location))
         c_path = os.path.join(buildpath, "opcua_client__%s.c" % locstr)
 
-        c_code = self.modeldata.GenerateC(c_path, locstr, 
-            self.GetParamsAttributes("OPCUAClient.Server_URI")["value"])
+        c_code = '#include "beremiz.h"\n'
+        c_code += self.modeldata.GenerateC(c_path, locstr, self.GetConfig())
 
-        with open(c_path, 'wb') as c_file:
+        with open(c_path, 'w') as c_file:
             c_file.write(c_code)
 
-        LDFLAGS = [' "' + os.path.join(Open62541LibraryPath, "libopen62541.a") + '"']
+        LDFLAGS = ['"' + os.path.join(Open62541LibraryPath, "libopen62541.a") + '"', '-lcrypto']
 
         CFLAGS = ' '.join(['-I"' + path + '"' for path in Open62541IncludePaths])
 
+        # Note: all user provided files are systematicaly copied, including cryptomaterial
+
         return [(c_path, CFLAGS)], LDFLAGS, True
 
     def GetVariableLocationTree(self):
@@ -93,7 +158,7 @@
         locstr = "_".join(map(str, current_location))
         name = self.BaseParams.getName()
         entries = []
-        for direction, data in self.modeldata.iteritems():
+        for direction, data in self.modeldata.items():
             iec_direction_prefix = {"input": "__I", "output": "__Q"}[direction]
             for row in data:
                 dname, ua_nsidx, ua_nodeid_type, _ua_node_id, ua_type, iec_number = row
--- a/opc_ua/opcua_client_maker.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/opc_ua/opcua_client_maker.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,13 +1,16 @@
-from __future__ import print_function
-from __future__ import absolute_import
+
+
 
 import csv
-
-from opcua import Client
-from opcua import ua
+import asyncio
+import functools
+from threading import Thread
+
+from asyncua import Client
+from asyncua import ua
 
 import wx
-from wx.lib.agw.hypertreelist import HyperTreeList as TreeListCtrl
+import wx.lib.gizmos as gizmos  # Formerly wx.gizmos in Classic
 import wx.dataview as dv
 
 
@@ -38,9 +41,19 @@
 
 directions = ["input", "output"]
 
-class OPCUASubListModel(dv.PyDataViewIndexListModel):
+authParams = {
+    "x509":[
+        ("Certificate", "certificate.der"),
+        ("PrivateKey", "private_key.pem"),
+        ("Policy", "Basic256Sha256"),
+        ("Mode", "SignAndEncrypt")],
+    "UserPassword":[
+        ("User", None),
+        ("Password", None)]}
+
+class OPCUASubListModel(dv.DataViewIndexListModel):
     def __init__(self, data, log):
-        dv.PyDataViewIndexListModel.__init__(self, len(data))
+        dv.DataViewIndexListModel.__init__(self, len(data))
         self.data = data
         self.log = log
 
@@ -140,7 +153,7 @@
             self.dvc.AppendTextColumn(colname,  idx, width=width, mode=dv.DATAVIEW_CELL_EDITABLE)
 
         DropTarget = NodeDropTarget(self)
-        self.dvc.SetDropTarget(DropTarget)
+        self.SetDropTarget(DropTarget)
 
         self.Sizer = wx.BoxSizer(wx.VERTICAL)
 
@@ -172,33 +185,28 @@
         #             splitter.        panel.      splitter
         ClientPanel = self.GetParent().GetParent().GetParent()
         nodes = ClientPanel.GetSelectedNodes()
-        for node in nodes:
-            cname = node.get_node_class().name
-            dname = node.get_display_name().Text
-            if cname != "Variable":
-                self.log("Node {} ignored (not a variable)".format(dname))
+        for node, properties in nodes:
+            if properties.cname != "Variable":
+                self.log("Node {} ignored (not a variable)".format(properties.dname))
                 continue
 
-            tname = node.get_data_type_as_variant_type().name
+            tname = properties.variant_type
             if tname not in UA_IEC_types:
-                self.log("Node {} ignored (unsupported type)".format(dname))
+                self.log("Node {} ignored (unsupported type)".format(properties.dname))
                 continue
 
-            access = node.get_access_level()
             if {"input":ua.AccessLevel.CurrentRead,
-                "output":ua.AccessLevel.CurrentWrite}[self.direction] not in access:
-                self.log("Node {} ignored because of insuficient access rights".format(dname))
+                "output":ua.AccessLevel.CurrentWrite}[self.direction] not in properties.access:
+                self.log("Node {} ignored because of insuficient access rights".format(properties.dname))
                 continue
 
-            nsid = node.nodeid.NamespaceIndex
-            nid =  node.nodeid.Identifier
-            nid_type =  type(nid).__name__
-            iecid = nid
-
-            value = [dname,
-                     nsid,
+            nid_type =  type(properties.nid).__name__
+            iecid = properties.nid
+
+            value = [properties.dname,
+                     properties.nsid,
                      nid_type,
-                     nid,
+                     properties.nid,
                      tname,
                      iecid]
             self.model.AddRow(value)
@@ -229,20 +237,43 @@
     smileidx    = il.Add(wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_OTHER, isz))
 
 
+AsyncUAClientLoop = None
+def AsyncUAClientLoopProc():
+    asyncio.set_event_loop(AsyncUAClientLoop)
+    AsyncUAClientLoop.run_forever()
+
+def ExecuteSychronously(func, timeout=1):
+    def AsyncSychronizer(*args, **kwargs):
+        global AsyncUAClientLoop
+        # create asyncio loop
+        if AsyncUAClientLoop is None:
+            AsyncUAClientLoop = asyncio.new_event_loop()
+            Thread(target=AsyncUAClientLoopProc, daemon=True).start()
+        # schedule work in this loop
+        future = asyncio.run_coroutine_threadsafe(func(*args, **kwargs), AsyncUAClientLoop)
+        # wait max 5sec until connection completed
+        return future.result(timeout)
+    return AsyncSychronizer
+
+def ExecuteSychronouslyWithTimeout(timeout):
+    return functools.partial(ExecuteSychronously,timeout=timeout)
+
+
 class OPCUAClientPanel(wx.SplitterWindow):
-    def __init__(self, parent, modeldata, log, uri_getter):
+    def __init__(self, parent, modeldata, log, config_getter):
         self.log = log
         wx.SplitterWindow.__init__(self, parent, -1)
 
-        self.ordered_nodes = []
+        self.ordered_nps = []
 
         self.inout_panel = wx.Panel(self)
         self.inout_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
         self.inout_sizer.AddGrowableCol(0)
         self.inout_sizer.AddGrowableRow(1)
 
+        self.clientloop = None
         self.client = None
-        self.uri_getter = uri_getter
+        self.config_getter = config_getter
 
         self.connect_button = wx.ToggleButton(self.inout_panel, -1, "Browse Server")
 
@@ -269,24 +300,80 @@
 
     def OnClose(self):
         if self.client is not None:
-            self.client.disconnect()
+            asyncio.run(self.client.disconnect())
             self.client = None
 
     def __del__(self):
         self.OnClose()
 
+    async def GetAsyncUANodeProperties(self, node):
+        properties = type("UANodeProperties",(),dict(
+                nsid = node.nodeid.NamespaceIndex,
+                nid =  node.nodeid.Identifier,
+                dname = (await node.read_display_name()).Text,
+                cname = (await node.read_node_class()).name,
+            ))
+        if properties.cname == "Variable":
+            properties.access = await node.get_access_level()
+            properties.variant_type = (await node.read_data_type_as_variant_type()).name
+        return properties
+
+    @ExecuteSychronouslyWithTimeout(5)
+    async def ConnectAsyncUAClient(self, config):
+        client = Client(config["URI"])
+        
+        AuthType = config["AuthType"]
+        if AuthType=="UserPasword":
+            await client.set_user(config["User"])
+            await client.set_password(config["Password"])
+        elif AuthType=="x509":
+            await client.set_security_string(
+                "{Policy},{Mode},{Certificate},{PrivateKey}".format(**config))
+
+        await client.connect()
+        self.client = client
+
+        # load definition of server specific structures/extension objects
+        await self.client.load_type_definitions()
+
+        # returns root node object and its properties
+        rootnode = self.client.get_root_node()
+        return rootnode, await self.GetAsyncUANodeProperties(rootnode)
+
+    @ExecuteSychronously
+    async def DisconnectAsyncUAClient(self):
+        if self.client is not None:
+            await self.client.disconnect()
+            self.client = None
+
+    @ExecuteSychronously
+    async def GetAsyncUANodeChildren(self, node):
+        children = await node.get_children()
+        return [ (child, await self.GetAsyncUANodeProperties(child)) for child in children]
+
     def OnConnectButton(self, event):
         if self.connect_button.GetValue():
             
+            config = self.config_getter()
+            self.log("OPCUA browser: connecting to {}\n".format(config["URI"]))
+            
+            try :
+                rootnode, rootnodeproperties = self.ConnectAsyncUAClient(config)
+            except Exception as e:
+                self.log("Exception in OPCUA browser: "+repr(e)+"\n")
+                self.client = None
+                self.connect_button.SetValue(False)
+                return
+
             self.tree_panel = wx.Panel(self)
             self.tree_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
             self.tree_sizer.AddGrowableCol(0)
             self.tree_sizer.AddGrowableRow(0)
 
-            self.tree = TreeListCtrl(self.tree_panel, -1, style=0, agwStyle=
-                                            wx.TR_DEFAULT_STYLE
-                                            | wx.TR_MULTIPLE
-                                            | wx.TR_FULL_ROW_HIGHLIGHT
+            self.tree = gizmos.TreeListCtrl(self.tree_panel, -1, style=0, agwStyle=
+                                            gizmos.TR_DEFAULT_STYLE
+                                            | gizmos.TR_MULTIPLE
+                                            | gizmos.TR_FULL_ROW_HIGHLIGHT
                                        )
 
             prepare_image_list()
@@ -298,12 +385,7 @@
 
             self.tree.SetMainColumn(0)
 
-            self.client = Client(self.uri_getter())
-            self.client.connect()
-            self.client.load_type_definitions()  # load definition of server specific structures/extension objects
-            rootnode = self.client.get_root_node()
-
-            rootitem = self.AddNodeItem(self.tree.AddRoot, rootnode)
+            rootitem = self.AddNodeItem(self.tree.AddRoot, rootnode, rootnodeproperties)
 
             # Populate first level so that root can be expanded
             self.CreateSubItems(rootitem)
@@ -315,7 +397,7 @@
 
             self.tree.Expand(rootitem)
 
-            hint = wx.StaticText(self, label = "Drag'n'drop desired variables from tree to Input or Output list")
+            hint = wx.StaticText(self.tree_panel, label = "Drag'n'drop desired variables from tree to Input or Output list")
 
             self.tree_sizer.Add(self.tree, flag=wx.GROW)
             self.tree_sizer.Add(hint, flag=wx.GROW)
@@ -325,29 +407,23 @@
 
             self.SplitVertically(self.tree_panel, self.inout_panel, 500)
         else:
-            self.client.disconnect()
-            self.client = None
+            self.DisconnectAsyncUAClient()
             self.Unsplit(self.tree_panel)
             self.tree_panel.Destroy()
 
-
     def CreateSubItems(self, item):
-        node, browsed = self.tree.GetPyData(item)
+        node, properties, browsed = self.tree.GetPyData(item)
         if not browsed:
-            for subnode in node.get_children():
-                self.AddNodeItem(lambda n: self.tree.AppendItem(item, n), subnode)
-            self.tree.SetPyData(item,(node, True))
-
-    def AddNodeItem(self, item_creation_func, node):
-        nsid = node.nodeid.NamespaceIndex
-        nid =  node.nodeid.Identifier
-        dname = node.get_display_name().Text
-        cname = node.get_node_class().name
-
-        item = item_creation_func(dname)
-
-        if cname == "Variable":
-            access = node.get_access_level()
+            children = self.GetAsyncUANodeChildren(node)
+            for subnode, subproperties in children:
+                self.AddNodeItem(lambda n: self.tree.AppendItem(item, n), subnode, subproperties)
+            self.tree.SetPyData(item,(node, properties, True))
+
+    def AddNodeItem(self, item_creation_func, node, properties):
+        item = item_creation_func(properties.dname)
+
+        if properties.cname == "Variable":
+            access = properties.access
             normalidx = fileidx
             r = ua.AccessLevel.CurrentRead in access
             w = ua.AccessLevel.CurrentWrite in access
@@ -359,14 +435,14 @@
                 ext = "WO"  # not sure this one exist
             else:
                 ext = "no access"  # not sure this one exist
-            cname = "Var "+node.get_data_type_as_variant_type().name+" (" + ext + ")"
+            cname = "Var "+properties.variant_type+" (" + ext + ")"
         else:
             normalidx = fldridx
 
-        self.tree.SetPyData(item,(node, False))
-        self.tree.SetItemText(item, cname, 1)
-        self.tree.SetItemText(item, str(nsid), 2)
-        self.tree.SetItemText(item, type(nid).__name__+": "+str(nid), 3)
+        self.tree.SetPyData(item,(node, properties, False))
+        self.tree.SetItemText(item, properties.cname, 1)
+        self.tree.SetItemText(item, str(properties.nsid), 2)
+        self.tree.SetItemText(item, type(properties.nid).__name__+": "+str(properties.nid), 3)
         self.tree.SetItemImage(item, normalidx, which = wx.TreeItemIcon_Normal)
         self.tree.SetItemImage(item, fldropenidx, which = wx.TreeItemIcon_Expanded)
 
@@ -384,28 +460,28 @@
         items = self.tree.GetSelections()
         items_pydata = [self.tree.GetPyData(item) for item in items]
 
-        nodes = [node for node, _unused in items_pydata]
+        nps = [(node,properties) for node, properties, unused in items_pydata]
 
         # append new nodes to ordered list
-        for node in nodes:
-            if node not in self.ordered_nodes:
-                self.ordered_nodes.append(node)
+        for np in nps:
+            if np not in self.ordered_nps:
+                self.ordered_nps.append(np)
 
         # filter out vanished items
-        self.ordered_nodes = [
-            node 
-            for node in self.ordered_nodes 
-            if node in nodes]
+        self.ordered_nps = [
+            np 
+            for np in self.ordered_nps 
+            if np in nps]
 
     def GetSelectedNodes(self):
-        return self.ordered_nodes 
+        return self.ordered_nps 
 
     def OnTreeBeginDrag(self, event):
         """
         Called when a drag is started in tree
         @param event: wx.TreeEvent
         """
-        if self.ordered_nodes:
+        if self.ordered_nps:
             # Just send a recognizable mime-type, drop destination
             # will get python data from parent
             data = wx.CustomDataObject(OPCUAClientDndMagicWord)
@@ -419,12 +495,13 @@
         
 
 class OPCUAClientList(list):
-    def __init__(self, log = lambda m:None):
+    def __init__(self, log, change_callback):
         super(OPCUAClientList, self).__init__(self)
         self.log = log
+        self.change_callback = change_callback
 
     def append(self, value):
-        v = dict(zip(lstcolnames, value))
+        v = dict(list(zip(lstcolnames, value)))
 
         if type(v["IEC"]) != int:
             if len(self) == 0:
@@ -446,118 +523,238 @@
             self.log("Variable {} (Id={}) has invalid type\n".format(v["Name"],v["Id"]))
             return False
 
-        if len(self)>0 and v["Id"] in zip(*self)[lstcolnames.index("Id")]:
+        if len(self)>0 and v["Id"] in list(zip(*self))[lstcolnames.index("Id")]:
             self.log("Variable {} (Id={}) already in list\n".format(v["Name"],v["Id"]))
             return False
 
         list.append(self, [v[n] for n in lstcolnames])
 
+        self.change_callback()
+
         return True
 
+    def __delitem__(self, index):
+        list.__delitem__(self, index)
+        self.change_callback()
+
 class OPCUAClientModel(dict):
-    def __init__(self, log = lambda m:None):
+    def __init__(self, log, change_callback = lambda : None):
         super(OPCUAClientModel, self).__init__()
         for direction in directions:
-            self[direction] = OPCUAClientList(log)
+            self[direction] = OPCUAClientList(log, change_callback)
 
     def LoadCSV(self,path):
-        with open(path, 'rb') as csvfile:
+        with open(path, 'r') as csvfile:
             reader = csv.reader(csvfile, delimiter=',', quotechar='"')
-            buf = {direction:[] for direction, _model in self.iteritems()}
-            for direction, model in self.iteritems():
+            buf = {direction:[] for direction, _model in self.items()}
+            for direction, model in self.items():
                 self[direction][:] = []
             for row in reader:
                 direction = row[0]
-                self[direction].append(row[1:])
+                # avoids calling change callback whe loading CSV
+                list.append(self[direction],row[1:])
 
     def SaveCSV(self,path):
-        with open(path, 'wb') as csvfile:
-            for direction, data in self.iteritems():
+        with open(path, 'w') as csvfile:
+            for direction, data in self.items():
                 writer = csv.writer(csvfile, delimiter=',',
                                 quotechar='"', quoting=csv.QUOTE_MINIMAL)
                 for row in data:
                     writer.writerow([direction] + row)
 
-    def GenerateC(self, path, locstr, server_uri):
+    def GenerateC(self, path, locstr, config):
         template = """/* code generated by beremiz OPC-UA extension */
 
 #include <open62541/client_config_default.h>
 #include <open62541/client_highlevel.h>
 #include <open62541/plugin/log_stdout.h>
+#include <open62541/plugin/securitypolicy.h>
+#include <open62541/plugin/securitypolicy_default.h>
+
+#include <open62541/types.h>
+#include <open62541/types_generated_handling.h>
+
+#define _Log(level, ...)                                                                           \\
+    {{                                                                                             \\
+        char mstr[256];                                                                            \\
+        snprintf(mstr, 255, __VA_ARGS__);                                                          \\
+        LogMessage(level, mstr, strlen(mstr));                                                     \\
+    }}
+
+#define LogInfo(...) _Log(LOG_INFO, __VA_ARGS__);
+#define LogError(...) _Log(LOG_CRITICAL, __VA_ARGS__);
+#define LogWarning(...) _Log(LOG_WARNING, __VA_ARGS__);
+
+static UA_INLINE UA_ByteString
+loadFile(const char *const path) {{
+    UA_ByteString fileContents = UA_STRING_NULL;
+
+    FILE *fp = fopen(path, "rb");
+    if(!fp) {{
+        errno = 0;
+        LogError("OPC-UA could not open %s", path);
+        return fileContents;
+    }}
+
+    fseek(fp, 0, SEEK_END);
+    fileContents.length = (size_t)ftell(fp);
+    fileContents.data = (UA_Byte *)UA_malloc(fileContents.length * sizeof(UA_Byte));
+    if(fileContents.data) {{
+        fseek(fp, 0, SEEK_SET);
+        size_t read = fread(fileContents.data, sizeof(UA_Byte), fileContents.length, fp);
+        if(read != fileContents.length){{
+            UA_ByteString_clear(&fileContents);
+            LogError("OPC-UA could not read %s", path);
+        }}
+    }} else {{
+        fileContents.length = 0;
+        LogError("OPC-UA Not enough memoty to load %s", path);
+    }}
+    fclose(fp);
+
+    return fileContents;
+}}
 
 static UA_Client *client;
+static UA_ClientConfig *cc;
 
 #define DECL_VAR(ua_type, C_type, c_loc_name)                                                       \\
 static UA_Variant c_loc_name##_variant;                                                             \\
 static C_type c_loc_name##_buf = 0;                                                                 \\
 C_type *c_loc_name = &c_loc_name##_buf;
 
-%(decl)s
-
-void __cleanup_%(locstr)s(void)
-{
+{decl}
+
+void __cleanup_{locstr}(void)
+{{
     UA_Client_disconnect(client);
     UA_Client_delete(client);
-}
-
+}}
+
+#define INIT_NoAuth()                                                                              \\
+    LogInfo("OPC-UA Init no auth");                                                                \\
+    UA_ClientConfig_setDefault(cc);                                                                \\
+    retval = UA_Client_connect(client, uri);
+
+/* Note : Single policy is enforced here, by default open62541 client supports all policies */
+#define INIT_x509(Policy, UpperCaseMode, PrivateKey, Certificate)                                  \\
+    LogInfo("OPC-UA Init x509 %s,%s,%s,%s", #Policy, #UpperCaseMode, PrivateKey, Certificate);     \\
+                                                                                                   \\
+    UA_ByteString certificate = loadFile(Certificate);                                             \\
+    UA_ByteString privateKey  = loadFile(PrivateKey);                                              \\
+                                                                                                   \\
+    cc->securityMode = UA_MESSAGESECURITYMODE_##UpperCaseMode;                                     \\
+                                                                                                   \\
+    /* replacement for default behaviour */                                                        \\
+    /* UA_ClientConfig_setDefaultEncryption(cc, certificate, privateKey, NULL, 0, NULL, 0); */     \\
+    do{{                                                                                           \\
+        retval = UA_ClientConfig_setDefault(cc);                                                   \\
+        if(retval != UA_STATUSCODE_GOOD)                                                           \\
+            break;                                                                                 \\
+                                                                                                   \\
+        UA_SecurityPolicy *sp = (UA_SecurityPolicy*)                                               \\
+            UA_realloc(cc->securityPolicies, sizeof(UA_SecurityPolicy) * 2);                       \\
+        if(!sp){{                                                                                  \\
+            retval = UA_STATUSCODE_BADOUTOFMEMORY;                                                 \\
+            break;                                                                                 \\
+        }}                                                                                         \\
+        cc->securityPolicies = sp;                                                                 \\
+                                                                                                   \\
+        retval = UA_SecurityPolicy_##Policy(&cc->securityPolicies[cc->securityPoliciesSize],       \\
+                                                 certificate, privateKey, &cc->logger);            \\
+        if(retval != UA_STATUSCODE_GOOD) {{                                                        \\
+            UA_LOG_WARNING(&cc->logger, UA_LOGCATEGORY_USERLAND,                                   \\
+                           "Could not add SecurityPolicy Policy with error code %s",               \\
+                           UA_StatusCode_name(retval));                                            \\
+            UA_free(cc->securityPolicies);                                                         \\
+            cc->securityPolicies = NULL;                                                           \\
+            break;                                                                                 \\
+        }}                                                                                         \\
+                                                                                                   \\
+        ++cc->securityPoliciesSize;                                                                \\
+    }} while(0);                                                                                   \\
+                                                                                                   \\
+    retval = UA_Client_connect(client, uri);                                                       \\
+                                                                                                   \\
+    UA_ByteString_clear(&certificate);                                                             \\
+    UA_ByteString_clear(&privateKey);
+
+#define INIT_UserPassword(User, Password)                                                          \\
+    LogInfo("OPC-UA Init UserPassword %s,%s", User, Password);                                     \\
+    UA_ClientConfig_setDefault(cc);                                                                \\
+    retval = UA_Client_connectUsername(client, uri, User, Password);
 
 #define INIT_READ_VARIANT(ua_type, c_loc_name)                                                     \\
     UA_Variant_init(&c_loc_name##_variant);
 
-#define INIT_WRITE_VARIANT(ua_type, ua_type_enum, c_loc_name)       \\
+#define INIT_WRITE_VARIANT(ua_type, ua_type_enum, c_loc_name)                                      \\
     UA_Variant_setScalar(&c_loc_name##_variant, (ua_type*)c_loc_name, &UA_TYPES[ua_type_enum]);
 
-int __init_%(locstr)s(int argc,char **argv)
-{
+int __init_{locstr}(int argc,char **argv)
+{{
     UA_StatusCode retval;
     client = UA_Client_new();
-    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
-%(init)s
-
-    /* Connect to server */
-    retval = UA_Client_connect(client, "%(uri)s");
-    if(retval != UA_STATUSCODE_GOOD) {
+    cc = UA_Client_getConfig(client);
+    char *uri = "{uri}";
+{init}
+
+    if(retval != UA_STATUSCODE_GOOD) {{
+        LogError("OPC-UA Init Failed %d", retval);
         UA_Client_delete(client);
         return EXIT_FAILURE;
-    }
-}
+    }}
+    return 0;
+}}
 
 #define READ_VALUE(ua_type, ua_type_enum, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)        \\
     retval = UA_Client_readValueAttribute(                                                         \\
         client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant);                      \\
     if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(&c_loc_name##_variant) &&               \\
-       c_loc_name##_variant.type == &UA_TYPES[ua_type_enum]) {                                     \\
+       c_loc_name##_variant.type == &UA_TYPES[ua_type_enum]) {{                                    \\
             c_loc_name##_buf = *(ua_type*)c_loc_name##_variant.data;                               \\
             UA_Variant_clear(&c_loc_name##_variant);  /* Unalloc requiered on each read ! */       \\
-    }
-
-void __retrieve_%(locstr)s(void)
-{
+    }}
+
+void __retrieve_{locstr}(void)
+{{
     UA_StatusCode retval;
-%(retrieve)s
-}
-
-#define WRITE_VALUE(ua_type, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)       \\
+{retrieve}
+}}
+
+#define WRITE_VALUE(ua_type, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id)                     \\
     UA_Client_writeValueAttribute(                                                                 \\
         client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant);
 
-void __publish_%(locstr)s(void)
-{
-%(publish)s
-}
+void __publish_{locstr}(void)
+{{
+{publish}
+}}
 
 """
         
         formatdict = dict(
             locstr   = locstr,
-            uri      = server_uri,
+            uri      = config["URI"],
             decl     = "",
             cleanup  = "",
             init     = "",
             retrieve = "",
             publish  = "" 
         )
-        for direction, data in self.iteritems():
+
+        AuthType = config["AuthType"]
+        if AuthType == "x509":
+            config["UpperCaseMode"] = config["Mode"].upper()
+            formatdict["init"] += """
+    INIT_x509({Policy}, {UpperCaseMode}, "{PrivateKey}", "{Certificate}")""".format(**config)
+        elif AuthType == "UserPassword":
+            formatdict["init"] += """
+    INIT_UserPassword("{User}", "{Password}")""".format(**config)
+        else:
+            formatdict["init"] += """
+    INIT_NoAuth()"""
+
+        for direction, data in self.items():
             iec_direction_prefix = {"input": "__I", "output": "__Q"}[direction]
             for row in data:
                 name, ua_nsidx, ua_nodeid_type, _ua_node_id, ua_type, iec_number = row
@@ -570,18 +767,18 @@
 DECL_VAR({ua_type}, {C_type}, {c_loc_name})""".format(**locals())
 
                 if direction == "input":
-                    formatdict["init"] +="""
+                    formatdict["init"] += """
     INIT_READ_VARIANT({ua_type}, {c_loc_name})""".format(**locals())
                     formatdict["retrieve"] += """
     READ_VALUE({ua_type}, {ua_type_enum}, {c_loc_name}, {ua_nodeid_type}, {ua_nsidx}, {ua_node_id})""".format(**locals())
 
                 if direction == "output":
-                    formatdict["init"] +="""
+                    formatdict["init"] += """
     INIT_WRITE_VARIANT({ua_type}, {ua_type_enum}, {c_loc_name})""".format(**locals())
                     formatdict["publish"] += """
     WRITE_VALUE({ua_type}, {c_loc_name}, {ua_nodeid_type}, {ua_nsidx}, {ua_node_id})""".format(**locals())
 
-        Ccode = template%formatdict
+        Ccode = template.format(**formatdict)
         
         return Ccode
 
@@ -594,7 +791,20 @@
 
     frame = wx.Frame(None, -1, "OPCUA Client Test App", size=(800,600))
 
-    uri = sys.argv[1] if len(sys.argv)>1 else "opc.tcp://localhost:4840"
+    argc = len(sys.argv)
+
+    config={}
+    config["URI"] = sys.argv[1] if argc>1 else "opc.tcp://localhost:4840"
+
+    if argc > 2:
+        AuthType = sys.argv[2]
+        config["AuthType"] = AuthType
+        for (name, default), value in zip_longest(authParams[AuthType], sys.argv[3:]):
+            if value is None:
+                if default is None:
+                    raise Exception(name+" param expected")
+                value = default
+            config[name] = value
 
     test_panel = wx.Panel(frame)
     test_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
@@ -603,7 +813,7 @@
 
     modeldata = OPCUAClientModel(print)
 
-    opcuatestpanel = OPCUAClientPanel(test_panel, modeldata, print, lambda:uri)
+    opcuatestpanel = OPCUAClientPanel(test_panel, modeldata, print, lambda:config)
 
     def OnGenerate(evt):
         dlg = wx.FileDialog(
@@ -624,7 +834,11 @@
     -I ../../open62541/arch/ ../../open62541/build/bin/libopen62541.a
 */
 
-"""%(path, path[:-2]) + modeldata.GenerateC(path, "test", uri) + """
+"""%(path, path[:-2]) + modeldata.GenerateC(path, "test", config) + """
+
+int LogMessage(uint8_t level, char* buf, uint32_t size){
+    printf("log level:%d message:'%.*s'\\n", level, size, buf);
+};
 
 int main(int argc, char *argv[]) {
 
@@ -640,7 +854,7 @@
 }
 """
 
-            with open(path, 'wb') as Cfile:
+            with open(path, 'w') as Cfile:
                 Cfile.write(Ccode)
 
 
--- a/plcopen/BlockInstanceCollector.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/BlockInstanceCollector.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,9 +3,9 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 from collections import OrderedDict, namedtuple
-from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
+from . XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
 
 # -------------------------------------------------------------------------------
 #           Helpers object for generating pou block instances list
@@ -124,14 +124,14 @@
         self.SpecificValues = None
 
         self.CurrentInstance = _BlockInstanceInfos(
-            *(_translate_args([_StringValue, int] + [float] * 4, args) +
+            *(_translate_args([_StringValue, int] + [int] * 4, args) +
               [specific_values, [], []]))
 
         self.BlockInstances[self.CurrentInstance.id] = self.CurrentInstance
 
     def AddInstanceConnection(self, context, *args):
         connection_args = _translate_args(
-            [_StringValue] * 2 + [_BoolValue, _StringValue] + [float] * 2, args)
+            [_StringValue] * 2 + [_BoolValue, _StringValue] + [int] * 2, args)
 
         self.CurrentConnection = _InstanceConnectionInfos(
             *(connection_args[1:4] + [
@@ -152,7 +152,7 @@
 
     def AddLinkPoint(self, context, *args):
         self.CurrentLink.points.append(_Point(
-            *_translate_args([float] * 2, args)))
+            *_translate_args([int] * 2, args)))
 
     def AddAction(self, context, *args):
         if len(self.SpecificValues) == 0:
--- a/plcopen/InstanceTagnameCollector.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/InstanceTagnameCollector.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,9 +3,9 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from plcopen.XSLTModelQuery import XSLTModelQuery
-from plcopen.types_enums import *
+
+from . XSLTModelQuery import XSLTModelQuery
+from . types_enums import *
 
 
 class InstanceTagName(object):
--- a/plcopen/InstancesPathCollector.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/InstancesPathCollector.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,8 +3,8 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from plcopen.XSLTModelQuery import XSLTModelQuery
+
+from . XSLTModelQuery import XSLTModelQuery
 
 
 class InstancesPathCollector(XSLTModelQuery):
--- a/plcopen/POUVariablesCollector.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/POUVariablesCollector.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,9 +3,9 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
-from plcopen.types_enums import CLASS_TYPES, POU_TYPES, VAR_CLASS_INFOS
+
+from . XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
+from . types_enums import CLASS_TYPES, POU_TYPES, VAR_CLASS_INFOS
 
 
 def class_extraction(value):
--- a/plcopen/VariableInfoCollector.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/VariableInfoCollector.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,8 +3,8 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from plcopen.XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
+
+from . XSLTModelQuery import XSLTModelQuery, _StringValue, _BoolValue, _translate_args
 
 # -------------------------------------------------------------------------------
 #                 Helpers object for generating pou var list
--- a/plcopen/XSLTModelQuery.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/XSLTModelQuery.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,11 +3,11 @@
 # This file is part of Beremiz.
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 from lxml import etree
 import util.paths as paths
-from plcopen.structures import StdBlckLibs
+from . structures import StdBlckLibs
 from XSLTransform import XSLTransform
 
 ScriptDirectory = paths.AbsDir(__file__)
@@ -24,7 +24,7 @@
             ("GetProject", lambda *_ignored:
              [controller.GetProject(self.debug)]),
             ("GetStdLibs", lambda *_ignored:
-             [lib for lib in StdBlckLibs.values()]),
+             [lib for lib in list(StdBlckLibs.values())]),
             ("GetExtensions", lambda *_ignored:
              [ctn["types"] for ctn in controller.ConfNodeTypes])
         ]
--- a/plcopen/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -26,7 +26,7 @@
 
 # plcopen module dynamically creates its classes
 
-from __future__ import absolute_import
-from plcopen.plcopen import \
+
+from . plcopen import \
     PLCOpenParser, LoadProject, SaveProject, LoadPou, \
     LoadPouInstances, VarOrder, QualifierList, rect
--- a/plcopen/definitions.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/definitions.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from os.path import join
 import util.paths as paths
 from util.TranslationCatalogs import NoTranslate
--- a/plcopen/instance_tagname.xslt	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/instance_tagname.xslt	Wed Oct 04 16:24:00 2023 +0200
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:ns="beremiz" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:exsl="http://exslt.org/common" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0" exclude-result-prefixes="ns" extension-element-prefixes="ns">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns" exclude-result-prefixes="ns">
   <xsl:output method="xml"/>
   <xsl:param name="instance_path"/>
   <xsl:variable name="project" select="ns:GetProject()"/>
--- a/plcopen/instances_path.xslt	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/instances_path.xslt	Wed Oct 04 16:24:00 2023 +0200
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:exsl="http://exslt.org/common" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:ns="beremiz" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" extension-element-prefixes="ns" version="1.0" exclude-result-prefixes="ns">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns" exclude-result-prefixes="ns">
   <xsl:output method="xml"/>
   <xsl:param name="instance_type"/>
   <xsl:template match="text()"/>
--- a/plcopen/plcopen.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/plcopen.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,12 +24,9 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import division
 import re
 from collections import OrderedDict
 
-from six.moves import xrange
 from lxml import etree
 
 from xmlclass import *
@@ -214,7 +211,7 @@
 PLCOpen_v1_xml = PLCOpen_v1_xml.replace(
     "http://www.plcopen.org/xml/tc6.xsd",
     "http://www.plcopen.org/xml/tc6_0201")
-PLCOpen_v1_xsd = etree.XMLSchema(etree.fromstring(PLCOpen_v1_xml))
+PLCOpen_v1_xsd = etree.XMLSchema(etree.fromstring(PLCOpen_v1_xml.encode()))
 
 # XPath for file compatibility process
 ProjectResourcesXPath = PLCOpen_XPath("ppx:instances/ppx:configurations/ppx:configuration/ppx:resource")
@@ -304,7 +301,7 @@
 
 
 def LoadProject(filepath):
-    project_file = open(filepath)
+    project_file = open(filepath, encoding='utf-8')
     project_xml = project_file.read()
     project_file.close()
     return LoadProjectXML(project_xml)
@@ -335,11 +332,11 @@
         project,
         pretty_print=True,
         xml_declaration=True,
-        encoding='utf-8')
+        encoding='utf-8').decode()
 
     assert len(content) != 0
         
-    project_file = open(filepath, 'w')
+    project_file = open(filepath, 'w', encoding='utf-8')
     project_file.write(content)
     project_file.close()
 
@@ -440,7 +437,7 @@
 
     def setcontentHeader(self, contentheader):
         contentheader_obj = self.contentHeader
-        for attr, value in contentheader.iteritems():
+        for attr, value in contentheader.items():
             func = {"projectName": contentheader_obj.setname,
                     "projectVersion": contentheader_obj.setversion,
                     "authorName": contentheader_obj.setauthor,
@@ -495,7 +492,7 @@
             "ppx:types/ppx:pous/ppx:pou%s%s" %
             (("[@name!='%s']" % exclude) if exclude is not None else '',
              ("[%s]" % " or ".join(
-                 map(lambda x: "@pouType='%s'" % x, filter)))
+                 ["@pouType='%s'" % x for x in filter]))
              if len(filter) > 0 else ""),
             namespaces=PLCOpenParser.NSMAP)
     setattr(cls, "getpous", getpous)
@@ -650,7 +647,7 @@
     setattr(cls, "getpageSize", getpageSize)
 
     def setscaling(self, scaling):
-        for language, (x, y) in scaling.items():
+        for language, (x, y) in list(scaling.items()):
             self.coordinateInfo.setscaling(language, x, y)
     setattr(cls, "setscaling", setscaling)
 
@@ -749,7 +746,7 @@
 def _removeConfigurationResourceVariableByAddress(self, address):
     for varlist in self.getglobalVars():
         variables = varlist.getvariable()
-        for i in xrange(len(variables)-1, -1, -1):
+        for i in range(len(variables)-1, -1, -1):
             if variables[i].getaddress() == address:
                 variables.remove(variables[i])
 
@@ -757,7 +754,7 @@
 def _removeConfigurationResourceVariableByFilter(self, address_model):
     for varlist in self.getglobalVars():
         variables = varlist.getvariable()
-        for i in xrange(len(variables)-1, -1, -1):
+        for i in range(len(variables)-1, -1, -1):
             var_address = variables[i].getaddress()
             if var_address is not None:
                 result = address_model.match(var_address)
@@ -971,7 +968,7 @@
             # Array derived directly from an elementary type
             else:
                 basetype_name = base_type_name
-            return "ARRAY [%s] OF %s" % (",".join(map(lambda x: "%s..%s" % (x.getlower(), x.getupper()), vartype_content.getdimension())), basetype_name)
+            return "ARRAY [%s] OF %s" % (",".join(["%s..%s" % (x.getlower(), x.getupper()) for x in vartype_content.getdimension()]), basetype_name)
         # Variable type is an elementary type
         return vartype_content_name
     setattr(cls, "gettypeAsText", gettypeAsText)
@@ -1380,7 +1377,7 @@
         vars = []
         if self.interface is not None:
             reverse_types = {}
-            for name, value in VarTypes.items():
+            for name, value in list(VarTypes.items()):
                 reverse_types[value] = name
             for varlist in self.interface.getcontent():
                 vars.append((reverse_types[varlist.getLocalTag()], varlist))
--- a/plcopen/pou_block_instances.xslt	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/pou_block_instances.xslt	Wed Oct 04 16:24:00 2023 +0200
@@ -1,8 +1,8 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:ns="beremiz" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:exsl="http://exslt.org/common" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0" exclude-result-prefixes="ns" extension-element-prefixes="ns">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns" exclude-result-prefixes="ns">
   <xsl:output method="xml"/>
   <xsl:template match="text()"/>
-  <xsl:template match="ppx:pou[ppx:body]|ppx:transition[ppx:body]|ppx:action[ppx:body]">
+  <xsl:template priority="-1.0" match="ppx:pou[ppx:body]|ppx:transition[ppx:body]|ppx:action[ppx:body]">
     <xsl:apply-templates select="ppx:body/*[self::ppx:FBD or self::ppx:LD or self::ppx:SFC]/*"/>
   </xsl:template>
   <xsl:template name="add_instance">
--- a/plcopen/pou_block_instances.ysl2	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/pou_block_instances.ysl2	Wed Oct 04 16:24:00 2023 +0200
@@ -7,7 +7,7 @@
     
     template "text()";
     
-    template "ppx:pou[ppx:body]|ppx:transition[ppx:body]|ppx:action[ppx:body]" {
+    template "ppx:pou[ppx:body]|ppx:transition[ppx:body]|ppx:action[ppx:body]", priority="-1.0" {
         apply "ppx:body/*[self::ppx:FBD or self::ppx:LD or self::ppx:SFC]/*";
     }
     
--- a/plcopen/pou_variables.xslt	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/pou_variables.xslt	Wed Oct 04 16:24:00 2023 +0200
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:ns="beremiz" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:exsl="http://exslt.org/common" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0" exclude-result-prefixes="ns" extension-element-prefixes="ns">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns" exclude-result-prefixes="ns">
   <xsl:output method="xml"/>
   <xsl:template match="text()"/>
   <xsl:template mode="var_class" match="text()"/>
@@ -218,7 +218,7 @@
   <xsl:template mode="var_class" match="ppx:pou">
     <xsl:value-of select="@pouType"/>
   </xsl:template>
-  <xsl:template mode="var_class" match="*[self::ppx:type or self::ppx:baseType]/*">
+  <xsl:template mode="var_class" priority="-1.0" match="*[self::ppx:type or self::ppx:baseType]/*">
     <xsl:param name="default_class"/>
     <xsl:value-of select="$default_class"/>
   </xsl:template>
@@ -241,7 +241,7 @@
   <xsl:template mode="var_type" match="*[self::ppx:type or self::ppx:baseType]/ppx:wstring">
     <xsl:text>WSTRING</xsl:text>
   </xsl:template>
-  <xsl:template mode="var_type" match="*[self::ppx:type or self::ppx:baseType]/*">
+  <xsl:template mode="var_type" priority="-1.0" match="*[self::ppx:type or self::ppx:baseType]/*">
     <xsl:value-of select="local-name()"/>
   </xsl:template>
   <xsl:template mode="var_edit" match="*[self::ppx:type or self::ppx:baseType]/ppx:derived">
@@ -259,7 +259,7 @@
   <xsl:template mode="var_edit" match="*[self::ppx:type or self::ppx:baseType]/ppx:array">
     <xsl:apply-templates mode="var_edit" select="ppx:baseType"/>
   </xsl:template>
-  <xsl:template mode="var_edit" match="*[self::ppx:type or self::ppx:baseType]/*">
+  <xsl:template mode="var_edit" priority="-1.0" match="*[self::ppx:type or self::ppx:baseType]/*">
     <xsl:text>false</xsl:text>
   </xsl:template>
   <xsl:template mode="var_debug" match="*[self::ppx:type or self::ppx:baseType]/ppx:derived">
@@ -283,7 +283,7 @@
   <xsl:template mode="var_debug" match="*[self::ppx:type or self::ppx:baseType]/ppx:struct">
     <xsl:text>false</xsl:text>
   </xsl:template>
-  <xsl:template mode="var_debug" match="*[self::ppx:type or self::ppx:baseType]/*">
+  <xsl:template mode="var_debug" priority="-1.0" match="*[self::ppx:type or self::ppx:baseType]/*">
     <xsl:text>true</xsl:text>
   </xsl:template>
 </xsl:stylesheet>
--- a/plcopen/pou_variables.ysl2	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/pou_variables.ysl2	Wed Oct 04 16:24:00 2023 +0200
@@ -188,7 +188,7 @@
         value "@pouType";
     }
     
-    template "*[self::ppx:type or self::ppx:baseType]/*" mode="var_class" {
+    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_class", priority="-1.0" {
         param "default_class";
         value "$default_class";
     }
@@ -214,7 +214,7 @@
         > WSTRING
     }
   
-    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_type" {
+    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_type", priority="-1.0" {
         > «local-name()»
     }
     
@@ -231,7 +231,7 @@
         apply "ppx:baseType", mode="var_edit";
     }
     
-    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_edit" {
+    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_edit", priority="-1.0" {
         > false
     }
     
@@ -261,7 +261,7 @@
         > false
     }
     
-    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_debug" {
+    template "*[self::ppx:type or self::ppx:baseType]/*", mode="var_debug", priority="-1.0" {
         > true
     }
     
--- a/plcopen/structures.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/structures.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,13 +23,13 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import re
 from collections import OrderedDict
 from functools import reduce
 
-from plcopen.plcopen import LoadProject
-from plcopen.definitions import *
+from . plcopen import LoadProject
+from . definitions import *
 
 TypeHierarchy = dict(TypeHierarchy_list)
 
@@ -53,7 +53,7 @@
     """
     Returns list of all types that correspont to the ANY* meta type
     """
-    return [typename for typename, _parenttype in TypeHierarchy.items() if not typename.startswith("ANY") and IsOfType(typename, type)]
+    return [typename for typename, _parenttype in list(TypeHierarchy.items()) if not typename.startswith("ANY") and IsOfType(typename, type)]
 
 
 DataTypeRange = dict(DataTypeRange_list)
@@ -78,7 +78,7 @@
                for libname, tc6fname in StdTC6Libs}
 StdBlckLst = [{"name": libname, "list":
                [GetBlockInfos(pous) for pous in lib.getpous()]}
-              for libname, lib in StdBlckLibs.iteritems()]
+              for libname, lib in StdBlckLibs.items()]
 
 # -------------------------------------------------------------------------------
 #                             Test identifier
@@ -148,7 +148,7 @@
     len_of_not_predifined_variable = len([True for param_type in decl if param_type not in variables])
 
     for param_type in decl:
-        if param_type in variables.keys():
+        if param_type in list(variables.keys()):
             param_name = param_type
             param_type = variables[param_type]
         elif len_of_not_predifined_variable > 1:
@@ -202,7 +202,7 @@
                 Function_decl = dict([(champ, val) for champ, val in zip(fonctions, fields[1:]) if champ])
                 baseinputnumber = int(Function_decl.get("baseinputnumber", 1))
                 Function_decl["baseinputnumber"] = baseinputnumber
-                for param, value in Function_decl.iteritems():
+                for param, value in Function_decl.items():
                     if param in translate:
                         Function_decl[param] = translate[param](value)
                 Function_decl["type"] = "function"
@@ -250,13 +250,13 @@
                         store = True
                         for (InTypes, OutTypes) in ANY_TO_ANY_FILTERS.get(filter_name, []):
                             outs = reduce(lambda a, b: a or b,
-                                          map(lambda testtype: IsOfType(
+                                          [IsOfType(
                                               Function_decl["outputs"][0][1],
-                                              testtype), OutTypes))
+                                              testtype) for testtype in OutTypes])
                             inps = reduce(lambda a, b: a or b,
-                                          map(lambda testtype: IsOfType(
+                                          [IsOfType(
                                               Function_decl["inputs"][0][1],
-                                              testtype), InTypes))
+                                              testtype) for testtype in InTypes])
                             if inps and outs and Function_decl["outputs"][0][1] != Function_decl["inputs"][0][1]:
                                 store = True
                                 break
@@ -308,7 +308,7 @@
 TYPE_BLOCK_START_KEYWORDS = ["TYPE", "STRUCT"]
 TYPE_BLOCK_END_KEYWORDS = ["END_TYPE", "END_STRUCT"]
 TYPE_KEYWORDS = ["ARRAY", "OF", "T", "D", "TIME_OF_DAY", "DATE_AND_TIME"] + TYPE_BLOCK_START_KEYWORDS + TYPE_BLOCK_END_KEYWORDS
-TYPE_KEYWORDS.extend([keyword for keyword in TypeHierarchy.keys() if keyword not in TYPE_KEYWORDS])
+TYPE_KEYWORDS.extend([keyword for keyword in list(TypeHierarchy.keys()) if keyword not in TYPE_KEYWORDS])
 
 
 # Keywords for Variable Declaration
--- a/plcopen/types_enums.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/types_enums.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,7 +3,7 @@
 # This file is part of Beremiz
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 from util.TranslationCatalogs import NoTranslate
 _ = NoTranslate
 
@@ -16,7 +16,7 @@
     ITEM_CONFIGURATION,
     ITEM_RESOURCE,
     ITEM_DATATYPE
-] = range(8)
+] = list(range(8))
 
 ITEMS_UNEDITABLE = [
     ITEM_DATATYPES,
@@ -28,7 +28,7 @@
     ITEM_CONFIGURATIONS,
     ITEM_RESOURCES,
     ITEM_PROPERTIES
-] = range(8, 17)
+] = list(range(8, 17))
 
 ITEMS_VARIABLE = [
     ITEM_VAR_LOCAL,
@@ -38,7 +38,7 @@
     ITEM_VAR_INPUT,
     ITEM_VAR_OUTPUT,
     ITEM_VAR_INOUT
-] = range(17, 24)
+] = list(range(17, 24))
 
 ITEM_CONFNODE = 25
 
@@ -70,7 +70,7 @@
                    LOCATION_GROUP,
                    LOCATION_VAR_INPUT,
                    LOCATION_VAR_OUTPUT,
-                   LOCATION_VAR_MEMORY] = range(6)
+                   LOCATION_VAR_MEMORY] = list(range(6))
 
 UNEDITABLE_NAMES = [_("User-defined POUs"), _("Functions"), _("Function Blocks"),
                     _("Programs"), _("Data Types"), _("Transitions"), _("Actions"),
--- a/plcopen/variables_infos.xslt	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/variables_infos.xslt	Wed Oct 04 16:24:00 2023 +0200
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:ns="beremiz" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:exsl="http://exslt.org/common" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0" exclude-result-prefixes="ns" extension-element-prefixes="ns">
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:ppx="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns" exclude-result-prefixes="ns">
   <xsl:output method="xml"/>
   <xsl:param name="tree"/>
   <xsl:template match="text()"/>
@@ -155,7 +155,7 @@
     </xsl:variable>
     <xsl:value-of select="ns:SetType($name)"/>
   </xsl:template>
-  <xsl:template mode="var_type" match="*[self::ppx:type or self::ppx:baseType or self::ppx:returnType]/*">
+  <xsl:template mode="var_type" priority="-1.0" match="*[self::ppx:type or self::ppx:baseType or self::ppx:returnType]/*">
     <xsl:variable name="name">
       <xsl:value-of select="local-name()"/>
     </xsl:variable>
--- a/plcopen/variables_infos.ysl2	Fri Aug 18 11:38:20 2023 +0200
+++ b/plcopen/variables_infos.ysl2	Wed Oct 04 16:24:00 2023 +0200
@@ -152,7 +152,7 @@
         value "ns:SetType($name)";
     }
     
-    template "*[self::ppx:type or self::ppx:baseType or self::ppx:returnType]/*", mode="var_type" {
+    template "*[self::ppx:type or self::ppx:baseType or self::ppx:returnType]/*", mode="var_type", priority="-1.0" {
         variable "name" > «local-name()»
         value "ns:SetType($name)";
     }
--- a/py_ext/PythonEditor.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/py_ext/PythonEditor.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import keyword
 import wx.stc as stc
 
--- a/py_ext/PythonFileCTNMixin.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/py_ext/PythonFileCTNMixin.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,10 +24,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import os
 import re
-from builtins import str as text
 
 import util.paths as paths
 from xmlclass import GenerateParserFromXSD
@@ -77,7 +75,7 @@
                     self.CreateCodeFileBuffer(False)
                     self.OnCTNSave()
             except Exception as exc:
-                error = text(exc)
+                error = str(exc)
 
             if error is not None:
                 self.GetCTRoot().logger.write_error(
@@ -134,8 +132,7 @@
             return repr(content) if content else None
 
         pyextname = self.CTNName()
-        varinfos = map(
-            lambda variable: {
+        varinfos = [{
                 "name": variable.getname(),
                 "desc": repr(variable.getdesc()),
                 "onchangecode": _onchangecode(variable),
@@ -146,8 +143,7 @@
                 "IECtype": self.GetCTRoot().GetBaseType(variable.gettype()),
                 "initial": repr(variable.getinitial()),
                 "pyextname": pyextname
-            },
-            self.CodeFile.variables.variable)
+            } for variable in self.CodeFile.variables.variable]
 
         onchange_var_count = len([None for varinfo in varinfos if varinfo["onchange"]])
 
@@ -247,7 +243,7 @@
         # write generated content to python file
         runtimefile_path = os.path.join(buildpath,
                                         "runtime_%s.py" % location_str)
-        runtimefile = open(runtimefile_path, 'w')
+        runtimefile = open(runtimefile_path, 'wb')
         runtimefile.write(PyFileContent.encode('utf-8'))
         runtimefile.close()
 
--- a/py_ext/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/py_ext/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from .py_ext import *
 from .PythonEditor import PythonEditor
 from .PythonFileCTNMixin import PythonFileCTNMixin
--- a/py_ext/py_ext.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/py_ext/py_ext.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 from POULibrary import POULibrary
 from py_ext.PythonFileCTNMixin import PythonFileCTNMixin
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/requirements.txt	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,44 @@
+# generated by pip freeze
+aiofiles==23.1.0
+aiosqlite==0.19.0
+async-timeout==4.0.2
+asyncua @ git+https://github.com/FreeOpcUa/opcua-asyncio.git@98a64897a2d171653353de2f36d33085aef65e82
+attrs==23.1.0
+autobahn==23.1.2
+Automat==22.10.0
+Brotli==1.0.9
+cffi==1.15.1
+click==8.1.3
+constantly==15.1.0
+contourpy==1.0.7
+cryptography==40.0.2
+cycler==0.11.0
+fonttools==4.39.3
+gattrdict==2.0.1
+hyperlink==21.0.0
+idna==3.4
+ifaddr==0.2.0
+incremental==22.10.0
+kiwisolver==1.4.4
+lxml==4.9.2
+matplotlib==3.7.1
+msgpack==1.0.5
+Nevow @ git+https://git@github.com/beremiz/nevow-py3.git@cb62cc37824361725c0c0a599802b15210e6aaa3#egg=Nevow
+numpy==1.24.3
+packaging==23.1
+Pillow==9.5.0
+pycountry==22.3.5
+pycparser==2.21
+pyparsing==3.0.9
+Pyro5==5.14
+python-dateutil==2.8.2
+pytz==2023.3
+serpent==1.41
+six==1.16.0
+sortedcontainers==2.4.0
+Twisted==22.10.0
+txaio==23.1.1
+typing_extensions==4.5.0
+wxPython==4.2.0
+zeroconf==0.62.0
+zope.interface==6.0
--- a/runtime/NevowServer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/NevowServer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,13 +23,13 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import os
 import collections
 import shutil
 import platform as platform_module
-from zope.interface import implements
+from zope.interface import implementer
 from nevow import appserver, inevow, tags, loaders, athena, url, rend
 from nevow.page import renderer
 from nevow.static import File
@@ -44,97 +44,13 @@
 
 PAGE_TITLE = 'Beremiz Runtime Web Interface'
 
-xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
+xhtml_header = b'''<?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 '''
 
 WorkingDir = None
 
-
-class PLCHMI(athena.LiveElement):
-
-    initialised = False
-
-    def HMIinitialised(self, result):
-        self.initialised = True
-
-    def HMIinitialisation(self):
-        self.HMIinitialised(None)
-
-
-class DefaultPLCStartedHMI(PLCHMI):
-    docFactory = loaders.stan(
-        tags.div(render=tags.directive('liveElement'))[
-            tags.h1["PLC IS NOW STARTED"],
-        ])
-
-
-class PLCStoppedHMI(PLCHMI):
-    docFactory = loaders.stan(
-        tags.div(render=tags.directive('liveElement'))[
-            tags.h1["PLC IS STOPPED"],
-        ])
-
-
-class MainPage(athena.LiveElement):
-    jsClass = u"WebInterface.PLC"
-    docFactory = loaders.stan(
-        tags.invisible[
-            tags.div(render=tags.directive('liveElement'))[
-                tags.div(id='content')[
-                    tags.div(render=tags.directive('PLCElement'))]
-            ],
-            tags.a(href='settings')['Settings']])
-
-    def __init__(self, *a, **kw):
-        athena.LiveElement.__init__(self, *a, **kw)
-        self.pcl_state = False
-        self.HMI = None
-        self.resetPLCStartedHMI()
-
-    def setPLCState(self, state):
-        self.pcl_state = state
-        if self.HMI is not None:
-            self.callRemote('updateHMI')
-
-    def setPLCStartedHMI(self, hmi):
-        self.PLCStartedHMIClass = hmi
-
-    def resetPLCStartedHMI(self):
-        self.PLCStartedHMIClass = DefaultPLCStartedHMI
-
-    def getHMI(self):
-        return self.HMI
-
-    def HMIexec(self, function, *args, **kwargs):
-        if self.HMI is not None:
-            getattr(self.HMI, function, lambda: None)(*args, **kwargs)
-    athena.expose(HMIexec)
-
-    def resetHMI(self):
-        self.HMI = None
-
-    def PLCElement(self, ctx, data):
-        return self.getPLCElement()
-    renderer(PLCElement)
-
-    def getPLCElement(self):
-        self.detachFragmentChildren()
-        if self.pcl_state:
-            f = self.PLCStartedHMIClass()
-        else:
-            f = PLCStoppedHMI()
-        f.setFragmentParent(self)
-        self.HMI = f
-        return f
-    athena.expose(getPLCElement)
-
-    def detachFragmentChildren(self):
-        for child in self.liveFragmentChildren[:]:
-            child.detach()
-
-
 class ConfigurableBindings(configurable.Configurable):
 
     def __init__(self):
@@ -248,6 +164,7 @@
 
 extensions_settings_od = collections.OrderedDict()
 
+@implementer(ISettings)
 class SettingsPage(rend.Page):
     # We deserve a slash
     addSlash = True
@@ -255,8 +172,6 @@
     # This makes webform_css url answer some default CSS
     child_webform_css = webform.defaultCSS
     child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css')
-
-    implements(ISettings)
    
     def __getattr__(self, name):
         global extensions_settings_od
@@ -322,7 +237,7 @@
         if uploadedfile is not None:
             fobj = getattr(uploadedfile, "file", None)
         if fobj is not None:
-            with open(uploadedfile.filename, 'w') as destfd:
+            with open(uploadedfile.filename, 'wb') as destfd:
                 fobj.seek(0)
                 shutil.copyfileobj(fobj,destfd)
 
@@ -331,107 +246,10 @@
             return customSettingsURLs[segments[0]](ctx, segments)
         return super(SettingsPage, self).locateChild(ctx, segments)
 
-
-class WebInterface(athena.LivePage):
-
-    docFactory = loaders.stan([tags.raw(xhtml_header),
-                               tags.html(xmlns="http://www.w3.org/1999/xhtml")[
-                                   tags.head(render=tags.directive('liveglue'))[
-                                       tags.title[PAGE_TITLE],
-                                       tags.link(rel='stylesheet',
-                                                 type='text/css',
-                                                 href=url.here.child("webform_css"))
-                                   ],
-                                   tags.body[
-                                       tags.div[
-                                           tags.div(
-                                               render=tags.directive(
-                                                   "MainPage")),
-                                       ]]]])
-    MainPage = MainPage()
-    PLCHMI = PLCHMI
-
-    def child_settings(self, context):
-        return SettingsPage()
-
-    def __init__(self, plcState=False, *a, **kw):
-        super(WebInterface, self).__init__(*a, **kw)
-        self.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile(
-            __file__, 'webinterface.js')
-        self.plcState = plcState
-        self.MainPage.setPLCState(plcState)
-
-    def getHMI(self):
-        return self.MainPage.getHMI()
-
-    def LoadHMI(self, hmi, jsmodules):
-        for name, path in jsmodules.iteritems():
-            self.jsModules.mapping[name] = os.path.join(WorkingDir, path)
-        self.MainPage.setPLCStartedHMI(hmi)
-
-    def UnLoadHMI(self):
-        self.MainPage.resetPLCStartedHMI()
-
-    def PLCStarted(self):
-        self.plcState = True
-        self.MainPage.setPLCState(True)
-
-    def PLCStopped(self):
-        self.plcState = False
-        self.MainPage.setPLCState(False)
-
-    def renderHTTP(self, ctx):
-        """
-        Force content type to fit with SVG
-        """
-        req = ctx.locate(inevow.IRequest)
-        req.setHeader('Content-type', 'application/xhtml+xml')
-        return super(WebInterface, self).renderHTTP(ctx)
-
-    def render_MainPage(self, ctx, data):
-        f = self.MainPage
-        f.setFragmentParent(self)
-        return ctx.tag[f]
-
-    def child_(self, ctx):
-        self.MainPage.detachFragmentChildren()
-        return WebInterface(plcState=self.plcState)
-
-    def beforeRender(self, ctx):
-        d = self.notifyOnDisconnect()
-        d.addErrback(self.disconnected)
-
-    def disconnected(self, reason):
-        self.MainPage.resetHMI()
-        # print reason
-        # print "We will be called back when the client disconnects"
-
-
 def RegisterWebsite(iface, port):
-    website = WebInterface()
+    website = SettingsPage()
     site = appserver.NevowSite(website)
 
     reactor.listenTCP(port, site, interface=iface)
     print(_('HTTP interface port :'), port)
     return website
-
-
-class statuslistener(object):
-
-    def __init__(self, site):
-        self.oldstate = None
-        self.site = site
-
-    def listen(self, state):
-        if state != self.oldstate:
-            action = {'Started': self.site.PLCStarted,
-                      'Stopped': self.site.PLCStopped}.get(state, None)
-            if action is not None:
-                action()
-            self.oldstate = state
-
-
-def website_statuslistener_factory(site):
-    return statuslistener(site).listen
-
-
--- a/runtime/PLCObject.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/PLCObject.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,7 +22,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 
-from __future__ import absolute_import
 from threading import Thread, Lock, Event, Condition
 import ctypes
 import os
@@ -33,8 +32,6 @@
 import hashlib
 from tempfile import mkstemp
 from functools import wraps, partial
-from six.moves import xrange
-from past.builtins import execfile
 import _ctypes
 
 from runtime.typemapping import TypeTranslator
@@ -59,14 +56,15 @@
 
 
 lib_ext = {
-    "linux2": ".so",
+    "linux": ".so",
     "win32":  ".dll",
 }.get(sys.platform, "")
 
 
 def PLCprint(message):
-    sys.stdout.write("PLCobject : "+message+"\n")
-    sys.stdout.flush()
+    if sys.stdout:
+        sys.stdout.write("PLCobject : "+message+"\n")
+        sys.stdout.flush()
 
 
 def RunInMain(func):
@@ -83,8 +81,7 @@
         if os.path.exists(self.tmpdir):
             shutil.rmtree(self.tmpdir)
         os.mkdir(self.tmpdir)
-        # FIXME : is argv of any use nowadays ?
-        self.argv = [WorkingDir] + argv  # force argv[0] to be "path" to exec...
+        self.argv = []
         self.statuschange = statuschange
         self.evaluator = evaluator
         self.pyruntimevars = pyruntimevars
@@ -135,7 +132,8 @@
             msg, = args
         PLCprint(msg)
         if self._LogMessage is not None:
-            return self._LogMessage(level, msg, len(msg))
+            bmsg = msg.encode()
+            return self._LogMessage(level, bmsg, len(bmsg))
         return None
 
     @RunInMain
@@ -163,8 +161,8 @@
                                      ctypes.byref(tv_sec),
                                      ctypes.byref(tv_nsec))
             if sz and sz <= maxsz:
-                self._log_read_buffer[sz] = '\x00'
-                return self._log_read_buffer.value, tick.value, tv_sec.value, tv_nsec.value
+                return (self._log_read_buffer[:sz].decode(), tick.value,
+                        tv_sec.value, tv_nsec.value)
         elif self._loading_error is not None and level == 0:
             return self._loading_error, 0, 0, 0
         return None
@@ -188,7 +186,7 @@
 
             self.PLC_ID = ctypes.c_char_p.in_dll(self.PLClibraryHandle, "PLC_ID")
             if len(md5) == 32:
-                self.PLC_ID.value = md5
+                self.PLC_ID.value = md5.encode()
 
             self._startPLC = self.PLClibraryHandle.startPLC
             self._startPLC.restype = ctypes.c_int
@@ -393,7 +391,7 @@
             for filename in filenames:
                 name, ext = os.path.splitext(filename)
                 if name.upper().startswith("RUNTIME") and ext.upper() == ".PY":
-                    execfile(os.path.join(self.workingdir, filename), self.python_runtime_vars)
+                    exec(compile(open(os.path.join(self.workingdir, filename), "rb").read(), os.path.join(self.workingdir, filename), 'exec'), self.python_runtime_vars)
                     for methodname in MethodNames:
                         method = self.python_runtime_vars.get("_%s_%s" % (name, methodname), None)
                         if method is not None:
@@ -425,10 +423,11 @@
         res, cmd, blkid = "None", "None", ctypes.c_void_p()
         compile_cache = {}
         while True:
-            cmd = self._PythonIterator(res, blkid)
+            cmd = self._PythonIterator(res.encode(), blkid)
             FBID = blkid.value
             if cmd is None:
                 break
+            cmd = cmd.decode()
             try:
                 self.python_runtime_vars["FBID"] = FBID
                 ccmd, AST = compile_cache.get(FBID, (None, None))
@@ -561,7 +560,7 @@
 
     @RunInMain
     def _GetPLCstatus(self):
-        return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount))
+        return self.PLCStatus, list(map(self.GetLogCount, range(LogLevelsCount)))
 
     @RunInMain
     def GetPLCID(self):
@@ -598,7 +597,7 @@
 
     @RunInMain
     def PurgeBlobs(self):
-        for fd, _path, _md5sum in self.blobs.values():
+        for fd, _path, _md5sum in list(self.blobs.values()):
             os.close(fd)
         self._init_blobs()
 
@@ -606,7 +605,8 @@
         blob = self.blobs.pop(blobID, None)
 
         if blob is None:
-            raise Exception(_("Missing data to create file: {}").format(newpath))
+            raise Exception(
+                _(f"Missing data to create file: {newpath}").decode())
 
         self._BlobAsFile(blob, newpath)
 
@@ -623,7 +623,7 @@
 
     def RepairPLC(self):
         self.PurgePLC()
-        MainWorker.quit()
+        MainWorker.finish()
 
     @RunInMain
     def PurgePLC(self):
--- a/runtime/PyroServer.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/PyroServer.py	Wed Oct 04 16:24:00 2023 +0200
@@ -9,16 +9,49 @@
 
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import sys
 import os
 
-import Pyro
-import Pyro.core as pyro
+import Pyro5
+import Pyro5.server
+
 import runtime
 from runtime.ServicePublisher import ServicePublisher
 
+Pyro5.config.SERIALIZER = "msgpack"
+
+def make_pyro_exposed_stub(method_name):
+    stub = lambda self, *args, **kwargs: \
+        getattr(self.plc_object_instance, method_name)(*args, **kwargs)
+    stub.__name__ = method_name
+    Pyro5.server.expose(stub)
+    return stub
+    
+
+class PLCObjectPyroAdapter(type("PLCObjectPyroStubs", (), {
+    name: make_pyro_exposed_stub(name) for name in [
+        "AppendChunkToBlob",
+        "GetLogMessage",
+        "GetPLCID",
+        "GetPLCstatus",
+        "GetTraceVariables",
+        "MatchMD5", 
+        "NewPLC",
+        "PurgeBlobs",
+        "RemoteExec",
+        "RepairPLC",
+        "ResetLogCount",
+        "SeedBlob",
+        "SetTraceVariablesList",
+        "StartPLC",
+        "StopPLC"
+    ]
+})):
+    def __init__(self, plc_object_instance):
+        self.plc_object_instance = plc_object_instance
+    
 
 class PyroServer(object):
     def __init__(self, servicename, ip_addr, port):
@@ -40,40 +73,22 @@
         if self._to_be_published():
             print(_("Publishing service on local network"))
 
-        sys.stdout.flush()
+        if sys.stdout:
+            sys.stdout.flush()
 
     def PyroLoop(self, when_ready):
         if self._to_be_published():
             self.Publish()
 
         while self.continueloop:
-            Pyro.config.PYRO_MULTITHREADED = 0
-            pyro.initServer()
-            self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
+            self.daemon = Pyro5.server.Daemon(host=self.ip_addr, port=self.port)
 
-            # pyro never frees memory after connection close if no timeout set
-            # taking too small timeout value may cause
-            # unwanted diconnection when IDE is kept busy for long periods
-            self.daemon.setTimeout(60)
-
-            pyro_obj = Pyro.core.ObjBase()
-            pyro_obj.delegateTo(runtime.GetPLCObjectSingleton())
-
-            self.daemon.connect(pyro_obj, "PLCObject")
+            self.daemon.register(PLCObjectPyroAdapter(runtime.GetPLCObjectSingleton()), "PLCObject")
 
             when_ready()
 
-            # "pipe to self" trick to to accelerate runtime shutdown 
-            # instead of waiting for arbitrary pyro timeout.
-            others = []
-            if not sys.platform.startswith('win'):
-                self.piper, self.pipew = os.pipe()
-                others.append(self.piper)
+            self.daemon.requestLoop()
 
-            self.daemon.requestLoop(others=others, callback=lambda x: None)
-            self.piper, self.pipew = None, None
-            if hasattr(self, 'sock'):
-                self.daemon.sock.close()
         self.Unpublish()
 
     def Restart(self):
@@ -81,8 +96,7 @@
 
     def Quit(self):
         self.continueloop = False
-        self.daemon.shutdown(True)
-        self.daemon.closedown()
+        self.daemon.shutdown()
         if not sys.platform.startswith('win'):
             if self.pipew is not None:
                 os.write(self.pipew, "goodbye")
--- a/runtime/ServicePublisher.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/ServicePublisher.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,8 +22,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import socket
 import threading
 import zeroconf
--- a/runtime/Stunnel.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/Stunnel.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,7 +1,7 @@
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import os
-from binascii import b2a_hqx
+from binascii import b2a_base64
 try:
     from runtime.spawn_subprocess import call
 except ImportError:
@@ -25,9 +25,10 @@
 
 def PSKgen(ID, PSKpath):
 
-    # b2a_hqx output len is 4/3 input len
+    # secret string length is 256
+    # b2a_base64 output len is 4/3 input len
     secret = os.urandom(192)  # int(256/1.3333)
-    secretstring = b2a_hqx(secret)
+    secretstring = b2a_base64(secret)
 
     PSKstring = ID+":"+secretstring
     with open(PSKpath, 'w') as f:
--- a/runtime/WampClient.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/WampClient.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,13 +22,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import time
 import json
 import os
 import re
-from six import text_type as text
 from autobahn.twisted import wamp
 from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
 from autobahn.wamp import types, auth
@@ -110,12 +107,12 @@
     def onConnect(self):
         if "secret" in self.config.extra:
             user = self.config.extra["ID"]
-            self.join(u"Automation", [u"wampcra"], user)
+            self.join("Automation", ["wampcra"], user)
         else:
-            self.join(u"Automation")
+            self.join("Automation")
 
     def onChallenge(self, challenge):
-        if challenge.method == u"wampcra":
+        if challenge.method == "wampcra":
             if "secret" in self.config.extra:
                 secret = self.config.extra["secret"].encode('utf8')
                 signature = auth.compute_wcs(
@@ -139,10 +136,10 @@
                 registerOptions = None
                 print(_("TypeError register option: {}".format(e)))
 
-            self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions)
+            self.register(GetCallee(name), '.'.join((ID, name)), registerOptions)
 
         for name in SubscribedEvents:
-            self.subscribe(GetCallee(name), text(name))
+            self.subscribe(GetCallee(name), str(name))
 
         for func in DoOnJoin:
             func(self)
@@ -158,7 +155,7 @@
 
     def publishWithOwnID(self, eventID, value):
         ID = self.config.extra["ID"]
-        self.publish(text(ID+'.'+eventID), value)
+        self.publish(str(ID+'.'+eventID), value)
 
 
 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
@@ -185,7 +182,7 @@
             _transportFactory = None
 
     def setClientFactoryOptions(self, options):
-        for key, value in options.items():
+        for key, value in list(options.items()):
             if key in ["maxDelay", "initialDelay", "maxRetries", "factor", "jitter"]:
                 setattr(self, key, value)
 
@@ -214,7 +211,7 @@
             _("WAMP configuration error:"))
 
 def UpdateWithDefault(d1, d2):
-    for k, v in d2.items():
+    for k, v in list(d2.items()):
         d1.setdefault(k, v)
 
 def GetConfiguration():
@@ -370,12 +367,12 @@
 
 def PublishEvent(eventID, value):
     if getWampStatus() == "Attached":
-        _WampSession.publish(text(eventID), value)
+        _WampSession.publish(str(eventID), value)
 
 
 def PublishEventWithOwnID(eventID, value):
     if getWampStatus() == "Attached":
-        _WampSession.publishWithOwnID(text(eventID), value)
+        _WampSession.publishWithOwnID(str(eventID), value)
 
 
 # WEB CONFIGURATION INTERFACE
@@ -446,7 +443,7 @@
 
 def getDownloadUrl(ctx, argument):
     if lastKnownConfig is not None:
-        return url.URL.fromContext(ctx).\
+        return url.URL.fromConstr(ctx).\
             child(WAMP_SECRET_URL).\
             child(lastKnownConfig["ID"] + ".secret")
 
--- a/runtime/Worker.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/Worker.py	Wed Oct 04 16:24:00 2023 +0200
@@ -7,11 +7,8 @@
 #
 # See COPYING.Runtime file for copyrights details.
 
-from __future__ import absolute_import
-import sys
-from threading import Lock, Condition
-import six
-from six.moves import _thread
+
+from threading import Lock, Condition, Thread, get_ident
 
 
 class job(object):
@@ -32,9 +29,9 @@
             call, args, kwargs = self.job
             self.result = call(*args, **kwargs)
             self.success = True
-        except Exception:
+        except Exception as e:
             self.success = False
-            self.exc_info = sys.exc_info()
+            self.exc_info = e
 
 
 class worker(object):
@@ -51,40 +48,99 @@
         self.free = Condition(self.mutex)
         self.job = None
         self.enabled = False
+        self.stopper = None
+        self.own_thread = 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)
+        raise job.exc_info
 
     def runloop(self, *args, **kwargs):
         """
         meant to be called by worker thread (blocking)
         """
-        self._threadID = _thread.get_ident()
+        self._threadID = get_ident()
         self.mutex.acquire()
         self.enabled = True
         if args or kwargs:
-            _job = job(*args, **kwargs)
-            _job.do()
-            # _job.success can't be None after do()
-            if not _job.success:
-                self.reraise(_job)
+            self.job = job(*args, **kwargs)
+            self.job.do()
+            # fail if first job fails
+            if not self.job.success:
+                self.reraise(self.job)
+            self.job = None
+
+        self.free.notify()
 
         while not self._finish:
-            self.todo.wait()
-            if self.job is not None:
-                self.job.do()
-                self.done.notify()
-            else:
-                break
+            self.todo.wait_for(lambda: self.job is not None)
+            self.job.do()
+            self.done.notify()
+            self.job = None
+            self.free.notify()
+            
+        self.mutex.release()
 
+    def interleave(self, waker, stopper, *args, **kwargs):
+        """
+        as for twisted reactor's interleave, it passes all jobs to waker func
+        additionaly, it creates a new thread to wait for new job.
+        """
+        self.feed = Condition(self.mutex)
+        self._threadID = get_ident()
+        self.stopper = stopper
+
+        def do_pending_job():
+            self.mutex.acquire()
+            self.job.do()
+            self.done.notify_all()
+            self.mutex.release()
+
+        def wakerfeedingloop():
+            self.mutex.acquire()
+            self.enabled = True
+
+            # Handle first job
+            if args or kwargs:
+                self.job = job(*args, **kwargs)
+                waker(do_pending_job)
+                self.done.wait_for(lambda: self.job.success is not None)
+                # fail if first job fails
+                if not self.job.success:
+                    self.reraise(self.job)
+                self.job = None
+
+            self.free.notify()
+
+            while not self._finish:
+                self.todo.wait_for(lambda: self.job is not None)
+                if self._finish:
+                    break
+                waker(do_pending_job)
+                self.done.wait_for(lambda: self.job.success is not None)
+                self.job = None
+                self.free.notify()
+
+            self.mutex.release()
+
+        self.own_thread = Thread(target = wakerfeedingloop)
+        self.own_thread.start()
+
+    def stop(self):
+        """
+        !interleave
+        """
+        self.mutex.acquire()
+        self._finish = True
+        self.enabled = False
+        self.job = None
+        self.todo.notify()
+        self.done.notify_all()
         self.mutex.release()
+        self.own_thread.join()
 
     def call(self, *args, **kwargs):
         """
@@ -96,7 +152,7 @@
 
         _job = job(*args, **kwargs)
 
-        if self._threadID == _thread.get_ident():
+        if self._threadID == get_ident():
             # if caller is worker thread execute immediately
             _job.do()
         else:
@@ -106,13 +162,11 @@
                 self.mutex.release()
                 raise EOFError("Worker is disabled")
 
-            while self.job is not None:
-                self.free.wait()
+            self.free.wait_for(lambda: self.job is None)
 
             self.job = _job
             self.todo.notify()
-            self.done.wait()
-            self.job = None
+            self.done.wait_for(lambda: _job.success is not None)
             self.free.notify()
             self.mutex.release()
 
@@ -136,3 +190,9 @@
         self.todo.notify()
         self.done.notify()
         self.mutex.release()
+
+    def finish(self):
+        if self.own_thread is None:
+            self.quit()
+        if self.stopper is not None:
+            self.stopper()
--- a/runtime/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -1,8 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-from __future__ import absolute_import
-from __future__ import print_function
+
+
 import traceback
 import sys
 
--- a/runtime/monotonic_time.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/monotonic_time.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,8 @@
 SOFTWARE.
 '''
 
-from __future__ import print_function
-from __future__ import unicode_literals
+
+
 
 __author__ = 'Gavin Beatty <gavinbeatty@gmail.com>'
 __version__ = '2.1.0.dev0'
--- a/runtime/spawn_subprocess.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/spawn_subprocess.py	Wed Oct 04 16:24:00 2023 +0200
@@ -3,15 +3,16 @@
 
 # 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 os, sys
 import signal
 import shlex
 import posix_spawn
 
 PIPE = "42"
 
+fsencoding = sys.getfilesystemencoding()
 
 class Popen(object):
     def __init__(self, args, stdin=None, stdout=None):
@@ -34,6 +35,7 @@
             file_actions.add_dup2(p2cread, 0)
             # close other end
             file_actions.add_close(p2cwrite)
+        args = [s.encode(fsencoding) for s in args if type(s)==str]
         self.pid = posix_spawn.posix_spawnp(args[0], args, file_actions=file_actions)
         if stdout is not None:
             self.stdout = os.fdopen(c2pread)
--- a/runtime/typemapping.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/typemapping.py	Wed Oct 04 16:24:00 2023 +0200
@@ -4,15 +4,9 @@
 # See COPYING.Runtime file for copyrights details.
 #
 
-from __future__ import absolute_import
-import ctypes
 from ctypes import *
 from datetime import timedelta as td
 
-ctypes.pythonapi.PyString_AsString.argtypes = (ctypes.c_void_p,)
-ctypes.pythonapi.PyString_AsString.restype = ctypes.POINTER(ctypes.c_char)
-
-
 class IEC_STRING(Structure):
     """
     Must be changed according to changes in iec_types.h
@@ -40,7 +34,7 @@
 
 
 SameEndianessTypeTranslator = {
-    "BOOL":       _t(c_uint8, lambda x: x.value != 0),
+    "BOOL":       _t(c_uint8, lambda x: bool(x.value)),
     "STEP":       _t(c_uint8),
     "TRANSITION": _t(c_uint8),
     "ACTION":     _t(c_uint8),
@@ -49,7 +43,7 @@
     "BYTE":       _t(c_uint8),
     "STRING":     (IEC_STRING,
                    lambda x: x.body[:x.len],
-                   lambda t, x: t(len(x), x)),
+                   lambda t, x: t(len(x), x.encode() if type(x)==str else x)),
     "INT":        _t(c_int16),
     "UINT":       _t(c_uint16),
     "WORD":       _t(c_uint16),
@@ -74,17 +68,17 @@
 TypeTranslator = SameEndianessTypeTranslator
 
 # Construct debugger natively supported types
-DebugTypesSize = dict([(key, sizeof(t)) for key, (t, p, u) in SameEndianessTypeTranslator.iteritems() if t is not None])
+DebugTypesSize = dict([(key, sizeof(t)) for key, (t, p, u) in SameEndianessTypeTranslator.items() if t is not None])
 
 
 def UnpackDebugBuffer(buff, indexes):
     res = []
     buffoffset = 0
     buffsize = len(buff)
-    buffptr = cast(ctypes.pythonapi.PyString_AsString(id(buff)), c_void_p).value
+    buffptr = cast(cast(buff, c_char_p), c_void_p).value
     for iectype in indexes:
-        c_type, unpack_func, _pack_func = \
-            TypeTranslator.get(iectype, (None, None, None))
+        c_type, unpack_func, _pack_func = TypeTranslator.get(iectype,
+                                                             (None, None, None))
 
         cursor = c_void_p(buffptr + buffoffset)
         if iectype == "STRING":
@@ -98,8 +92,8 @@
             size = sizeof(c_type)
 
         if c_type is not None and (buffoffset + size) <= buffsize:
-            value = unpack_func(cast(cursor,
-                                     POINTER(c_type)).contents)
+            ptr = cast(cursor, POINTER(c_type))
+            value = unpack_func(ptr.contents)
             buffoffset += size
             res.append(value)
         else:
--- a/runtime/xenomai.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/runtime/xenomai.py	Wed Oct 04 16:24:00 2023 +0200
@@ -4,7 +4,7 @@
 # See COPYING.Runtime file for copyrights details.
 #
 
-from __future__ import absolute_import
+
 from ctypes import CDLL, RTLD_GLOBAL, pointer, c_int, POINTER, c_char, create_string_buffer
 
 
--- a/setup.py	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-"""
-Install Beremiz
-"""
-from setuptools import setup
-
-setup(
-    name='beremiz',
-    version='0.1', 
-    install_requires=["Twisted == 20.3.0", "attrs == 19.2.0", "Automat == 0.3.0",
-                      "zope.interface == 4.4.2", "Nevow == 0.14.5", "PyHamcrest == 2.0.2",
-                      "Pygments == 2.9.0", "Pyro == 3.16", "constantly == 15.1.0",
-                      "future == 0.18.2", "hyperlink == 21.0.0", "incremental == 21.3.0",
-                      "pathlib == 1.0.1", "prompt-toolkit == 3.0.19", "zeroconf-py2compat == 0.19.10", 
-                      "idna == 2.10"]
-)
--- a/svghmi/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -6,5 +6,5 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 from svghmi.svghmi import *
--- a/svghmi/hmi_tree.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/hmi_tree.py	Wed Oct 04 16:24:00 2023 +0200
@@ -6,8 +6,8 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
-from itertools import izip, imap
+
+
 from pprint import pformat
 import weakref
 import hashlib
@@ -30,7 +30,7 @@
     "HMI_REAL":{}
 }
 
-HMI_TYPES = HMI_TYPES_DESC.keys()
+HMI_TYPES = list(HMI_TYPES_DESC.keys())
 
 class HMITreeNode(object):
     def __init__(self, path, name, nodetype, iectype = None, vartype = None, cpath = None, hmiclass = None):
@@ -64,7 +64,7 @@
         for child in self.children:
             if child.path is not None:
                 in_common = 0
-                for child_path_item, node_path_item in izip(child.path, node.path):
+                for child_path_item, node_path_item in zip(child.path, node.path):
                     if child_path_item == node_path_item:
                         in_common +=1
                     else:
@@ -113,7 +113,7 @@
         res = etree.Element(self.nodetype, **attribs)
 
         if hasattr(self, "children"):
-            for child_etree in imap(lambda c:c.etree(), self.children):
+            for child_etree in map(lambda c:c.etree(), self.children):
                 res.append(child_etree)
 
         return res
@@ -158,10 +158,10 @@
         s = hashlib.new('md5')
         self._hash(s)
         # limit size to HMI_HASH_SIZE as in svghmi.c
-        return map(ord,s.digest())[:8]
+        return s.digest()[:8]
 
     def _hash(self, s):
-        s.update(str((self.name,self.nodetype)))
+        s.update(self.name.encode() + self.nodetype.encode())
         if hasattr(self, "children"):
             for c in self.children:
                 c._hash(s)
--- a/svghmi/i18n.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/i18n.py	Wed Oct 04 16:24:00 2023 +0200
@@ -6,7 +6,7 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 from lxml import etree
 import os
 import sys
@@ -20,6 +20,7 @@
 # https://pypi.org/project/pycountry/18.12.8/
 # python2 -m pip install pycountry==18.12.8 --user
 import pycountry
+from dialogs import MessageBoxOnce
 
 cmd_parser = re.compile(r'(?:"([^"]+)"\s*|([^\s]+)\s*)?')
 
@@ -39,10 +40,21 @@
             poedit_path = None
 
     else:
-        try:
-            poedit_path = subprocess.check_output("command -v poedit", shell=True).strip()
-        except subprocess.CalledProcessError:
-            poedit_path = None
+        if "SNAP" in os.environ:
+            MessageBoxOnce("Launching POEdit with xdg-open",
+                    "Confined app can't launch POEdit directly.\n"+
+                        "Instead, PO/POT file is passed to xdg-open.\n"+
+                        "Please select POEdit when proposed.\n\n"+
+                    "Notes: \n"+
+                    " - POEdit must be installed on you system.\n"+
+                    " - If no choice is proposed, use file manager to change POT/PO file properties.\n",
+                    "SVGHMII18SnapWarning")
+            poedit_path = "xdg-open"
+        else:
+            try:
+                poedit_path = subprocess.check_output("command -v poedit", shell=True).strip()
+            except subprocess.CalledProcessError:
+                poedit_path = None
 
     if poedit_path is None:
         wx.MessageBox("POEdit is not found or installed !")
@@ -119,7 +131,7 @@
         langs.append((langname,langcode))
 
         broken = False
-        for msgid, msg in translation.iteritems():
+        for msgid, msg in translation.items():
             broken = True
             errcallback(_('{}: Unused translation "{}":"{}"\n').format(langcode,msgid,msg))
         if broken or langcode in broken_lang:
@@ -234,13 +246,13 @@
 
     def write(self, fp):
         timestamp = time.strftime('%Y-%m-%d %H:%M+%Z')
-        print >> fp, pot_header % {'time': timestamp}
+        print(pot_header % {'time': timestamp}, file=fp)
         reverse = {}
-        for k, v in self.__messages.items():
+        for k, v in list(self.__messages.items()):
             keys = list(v)
             keys.sort()
             reverse.setdefault(tuple(keys), []).append((k, v))
-        rkeys = reverse.keys()
+        rkeys = list(reverse.keys())
         rkeys.sort()
         for rkey in rkeys:
             rentries = reverse[rkey]
@@ -255,12 +267,12 @@
                     if len(locline) + len(s) <= 78:
                         locline = locline + s
                     else:
-                        print >> fp, locline
+                        print(locline, file=fp)
                         locline = locpfx + s
                 if len(locline) > len(locpfx):
-                    print >> fp, locline
-                print >> fp, 'msgid', normalize(k)
-                print >> fp, 'msgstr ""\n'
+                    print(locline, file=fp)
+                print('msgid', normalize(k), file=fp)
+                print('msgstr ""\n', file=fp)
 
 
 class POReader:
@@ -309,8 +321,8 @@
             # This is a message with plural forms
             elif l.startswith('msgid_plural'):
                 if section != ID:
-                    print >> sys.stderr, 'msgid_plural not preceded by msgid on %s:%d' %\
-                        (infile, lno)
+                    print('msgid_plural not preceded by msgid on %s:%d' %\
+                        (infile, lno), file=sys.stderr)
                     sys.exit(1)
                 l = l[12:]
                 msgid += '\0' # separator of singular and plural
@@ -320,16 +332,16 @@
                 section = STR
                 if l.startswith('msgstr['):
                     if not is_plural:
-                        print >> sys.stderr, 'plural without msgid_plural on %s:%d' %\
-                            (infile, lno)
+                        print('plural without msgid_plural on %s:%d' %\
+                            (infile, lno), file=sys.stderr)
                         sys.exit(1)
                     l = l.split(']', 1)[1]
                     if msgstr:
                         msgstr += '\0' # Separator of the various plural forms
                 else:
                     if is_plural:
-                        print >> sys.stderr, 'indexed msgstr required for plural on  %s:%d' %\
-                            (infile, lno)
+                        print('indexed msgstr required for plural on  %s:%d' %\
+                            (infile, lno), file=sys.stderr)
                         sys.exit(1)
                     l = l[6:]
             # Skip empty lines
@@ -342,9 +354,9 @@
             elif section == STR:
                 msgstr += l
             else:
-                print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
-                      'before:'
-                print >> sys.stderr, l
+                print('Syntax error on %s:%d' % (infile, lno), \
+                      'before:', file=sys.stderr)
+                print(l, file=sys.stderr)
                 sys.exit(1)
         # Add last entry
         if section == STR:
--- a/svghmi/svghmi.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/svghmi.py	Wed Oct 04 16:24:00 2023 +0200
@@ -6,8 +6,9 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
+import sys
 import shutil
 import hashlib
 import shlex
@@ -302,10 +303,14 @@
 
         return ret
 
-if wx.Platform == '__WXMSW__':
+if sys.platform.startswith('win'):
     default_cmds={
         "launch":"cmd.exe /c 'start msedge {url}'",
         "watchdog":"cmd.exe /k 'echo watchdog for {url} !'"}
+elif "SNAP" in os.environ:
+    default_cmds={
+        "launch":"xdg-open {url}",
+        "watchdog":"echo Watchdog for {name} !"}
 else:
     default_cmds={
         "launch":"chromium {url}",
@@ -422,13 +427,12 @@
         InkscapeGeomColumns = ["Id", "x", "y", "w", "h"]
 
         inkpath = get_inkscape_path()
-
         if inkpath is None:
             self.FatalError("SVGHMI: inkscape is not installed.")
 
         svgpath = self._getSVGpath()
         status, result, _err_result = ProcessLogger(self.GetCTRoot().logger,
-                                                     '"' + inkpath + '" -S "' + svgpath + '"',
+                                                     [inkpath, '-S', svgpath],
                                                      no_stdout=True,
                                                      no_stderr=True).spin()
         if status != 0:
@@ -438,7 +442,7 @@
         for line in result.split():
             strippedline = line.strip()
             attrs = dict(
-                zip(InkscapeGeomColumns, line.strip().split(',')))
+                list(zip(InkscapeGeomColumns, line.strip().split(','))))
 
             res.append(etree.Element("bbox", **attrs))
 
@@ -606,7 +610,7 @@
                 # print(transform.xslt.error_log)
                 # print(etree.tostring(result.xslt_profile,pretty_print=True))
 
-                with open(hash_path, 'wb') as digest_file:
+                with open(hash_path, 'w') as digest_file:
                     digest_file.write(digest)
             else:
                 self.GetCTRoot().logger.write("    No changes - XSLT transformation skipped\n")
@@ -672,14 +676,14 @@
         factory = HMIWebSocketServerFactory()
         factory.setProtocolOptions(maxConnections={maxConnections})
 
-        svghmi_root.putChild("ws", WebSocketResource(factory))
+        svghmi_root.putChild(b"ws", WebSocketResource(factory))
 
         svghmi_listener = reactor.listenTCP({port}, Site(svghmi_root), interface='{interface}')
         path_list = []
         svghmi_servers["{interface}:{port}"] = (svghmi_root, svghmi_listener, path_list)
 
     svghmi_root.putChild(
-        '{path}',
+        b'{path}',
         NoCacheFile('{xhtml}',
             defaultType='application/xhtml+xml'))
 
@@ -736,7 +740,7 @@
         return res
 
     def _ImportSVG(self):
-        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "",  _("SVG files (*.svg)|*.svg|All files|*.*"), wx.OPEN)
+        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a SVG file"), os.getcwd(), "",  _("SVG files (*.svg)|*.svg|All files|*.*"), wx.FD_OPEN)
         if dialog.ShowModal() == wx.ID_OK:
             svgpath = dialog.GetPath()
             if os.path.isfile(svgpath):
@@ -780,7 +784,7 @@
     def _EditPO(self):
         """ Select a specific translation and edit it with POEdit """
         project_path = self.CTNPath()
-        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a PO file"), project_path, "",  _("PO files (*.po)|*.po"), wx.OPEN)
+        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, _("Choose a PO file"), project_path, "",  _("PO files (*.po)|*.po"), wx.FD_OPEN)
         if dialog.ShowModal() == wx.ID_OK:
             POFile = dialog.GetPath()
             if os.path.isfile(POFile):
@@ -806,7 +810,7 @@
             _("Choose a font"),
             os.path.expanduser("~"),
             "",
-            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.OPEN)
+            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.FD_OPEN)
 
         if dialog.ShowModal() == wx.ID_OK:
             fontfile = dialog.GetPath()
@@ -843,7 +847,7 @@
             _("Choose a font to remove"),
             fontdir,
             "",
-            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.OPEN)
+            _("Font files (*.ttf;*.otf;*.woff;*.woff2)|*.ttf;*.otf;*.woff;*.woff2"), wx.FD_OPEN)
         if dialog.ShowModal() == wx.ID_OK:
             fontfile = dialog.GetPath()
             if os.path.isfile(fontfile):
--- a/svghmi/svghmi_server.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/svghmi_server.py	Wed Oct 04 16:24:00 2023 +0200
@@ -5,7 +5,7 @@
 # Copyright (C) 2019: Edouard TISSERANT
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import errno
 from threading import RLock, Timer
 import os, time
@@ -273,7 +273,7 @@
                 break
 
 def AddPathToSVGHMIServers(path, factory, *args, **kwargs):
-    for k,v in svghmi_servers.iteritems():
+    for k,v in svghmi_servers.items():
         svghmi_root, svghmi_listener, path_list = v
         svghmi_root.putChild(path, factory(*args, **kwargs))
 
--- a/svghmi/ui.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/svghmi/ui.py	Wed Oct 04 16:24:00 2023 +0200
@@ -6,7 +6,7 @@
 #
 # See COPYING file for copyrights details.
 
-from __future__ import absolute_import
+
 import os
 import hashlib
 import weakref
@@ -14,7 +14,7 @@
 import tempfile
 from threading import Thread, Lock
 from functools import reduce
-from itertools import izip
+
 from operator import or_
 from tempfile import NamedTemporaryFile
 
@@ -31,6 +31,19 @@
 
 from util.ProcessLogger import ProcessLogger
 
+# When running as a confined Snap, /tmp isn't accessible from the outside
+# and Widget DnD to Inkscape can't work, since it can't find generated svg 
+# This forces tmp directory in $SNAP_USER_DATA, accessible from other apps
+if "SNAP" in os.environ:
+     NamedTemporaryFile_orig = NamedTemporaryFile
+     tmpdir = os.path.join(os.environ["SNAP_USER_DATA"], ".tmp")
+     if not os.path.exists(tmpdir):
+         os.mkdir(tmpdir)
+     def NamedTemporaryFile(*args,**kwargs):
+        kwargs["dir"] = tmpdir
+        return NamedTemporaryFile_orig(*args, **kwargs)
+
+
 ScriptDirectory = paths.AbsDir(__file__)
 
 HMITreeDndMagicWord = "text/beremiz-hmitree"
@@ -58,13 +71,13 @@
                 display_name = ('{} (class={})'.format(c.name, c.hmiclass)) \
                                if c.hmiclass is not None else c.name
                 tc_child = self.AppendItem(current_tc_root, display_name)
-                self.SetPyData(tc_child, c)
+                self.SetItemData(tc_child, c)
 
                 self._recurseTree(c,tc_child)
             else:
                 display_name = '{} {}'.format(c.nodetype[4:], c.name)
                 tc_child = self.AppendItem(current_tc_root, display_name)
-                self.SetPyData(tc_child, c)
+                self.SetItemData(tc_child, c)
 
     def OnTreeNodeSelection(self, event):
         items = self.GetSelections()
@@ -106,7 +119,7 @@
         root_display_name = _("Please build to see HMI Tree") \
             if hmi_tree_root is None else "HMI"
         self.root = self.AddRoot(root_display_name)
-        self.SetPyData(self.root, hmi_tree_root)
+        self.SetItemData(self.root, hmi_tree_root)
 
         if hmi_tree_root is not None:
             self._recurseTree(hmi_tree_root, self.root)
@@ -146,11 +159,11 @@
                     for d in dirlist:
                         current_tc_root = self.AppendItem(current_tc_root, d)
                         res.append(current_tc_root)
-                        self.SetPyData(current_tc_root, None)
+                        self.SetItemData(current_tc_root, None)
                     dirlist = []
                     res.pop()
                 tc_child = self.AppendItem(current_tc_root, f)
-                self.SetPyData(tc_child, p)
+                self.SetItemData(tc_child, p)
         return res
 
     def MakeTree(self, lib_dir = None):
@@ -163,7 +176,7 @@
         root_display_name = _("Please select widget library directory") \
             if lib_dir is None else os.path.basename(lib_dir)
         self.root = self.AddRoot(root_display_name)
-        self.SetPyData(self.root, None)
+        self.SetItemData(self.root, None)
 
         if lib_dir is not None and os.path.exists(lib_dir):
             self._recurseTree(lib_dir, self.root, [])
@@ -236,9 +249,7 @@
         accepts = self.argdesc.get("accepts").split(',')
         self.setValidity(
             reduce(or_,
-                   map(lambda typename: 
-                           models[typename].match(txt) is not None,
-                       accepts), 
+                   [models[typename].match(txt) is not None for typename in accepts], 
                    False)
             if accepts and txt else None)
         self.ParentObj.RegenSVGLater()
@@ -270,9 +281,7 @@
         event.Skip()
     
 def KeepDoubleNewLines(txt):
-    return "\n\n".join(map(
-        lambda s:re.sub(r'\s+',' ',s),
-        txt.split("\n\n")))
+    return "\n\n".join([re.sub(r'\s+',' ',s) for s in txt.split("\n\n")])
 
 _conf_key = "SVGHMIWidgetLib"
 _preview_height = 200
@@ -326,7 +335,7 @@
         self.main_sizer.AddGrowableCol(0)
         self.main_sizer.AddGrowableRow(2)
 
-        self.staticmsg = wx.StaticText(self, label = _("Drag selected Widget from here to Inkscape"))
+        self.staticmsg = wx.StaticText(self.main_panel, label = _("Drag selected Widget from here to Inkscape"))
         self.preview = wx.Panel(self.main_panel, size=(-1, _preview_height + _preview_margin*2))
         self.signature_sizer = wx.BoxSizer(wx.VERTICAL)
         self.args_box = wx.StaticBox(self.main_panel, -1,
@@ -427,7 +436,7 @@
             # Get Preview panel size
             sz = self.preview.GetClientSize()
             w = self.bmp.GetWidth()
-            dc.DrawBitmap(self.bmp, (sz.width - w)/2, _preview_margin)
+            dc.DrawBitmap(self.bmp, (sz.width - w)//2, _preview_margin)
 
 
 
@@ -464,10 +473,9 @@
 
         # TODO: spawn a thread, to decouple thumbnail gen
         status, result, _err_result = ProcessLogger(
-            self.Controler.GetCTRoot().logger,
-            '"' + inkpath + '" "' + svgpath + '" ' +
-            export_opt + ' "' + thumbpath +
-            '" -D -h ' + str(_preview_height)).spin()
+            #self.Controler.GetCTRoot().logger,
+            None,
+            [ inkpath, svgpath, export_opt, thumbpath, "-D", "-h", str(_preview_height)]).spin()
         if status != 0:
             self.msg = _("Inkscape couldn't generate thumbnail.")
             return False
@@ -639,7 +647,7 @@
             # TODO: check that only last arg has multiple ordinality
             args += [args[-1]]*(len(prefillargs)-len(args))
         self.args_box.Show(len(args)!=0)
-        for arg, prefillarg in izip(args,prefillargs):
+        for arg, prefillarg in zip(args,prefillargs):
             self.AddArgToSignature(arg, prefillarg)
 
         # TODO support predefined path count (as for XYGraph)
--- a/targets/Generic/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Generic/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from ..toolchain_makefile import toolchain_makefile
 
 
--- a/targets/Linux/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Linux/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from ..toolchain_gcc import toolchain_gcc
 
 
@@ -32,7 +32,7 @@
     extension = ".so"
 
     def getBuilderCFLAGS(self):
-        additional_cflags = ["-fPIC"]
+        additional_cflags = ["-fPIC", "-Wno-implicit-function-declaration", "-Wno-int-conversion"]
         build_for_realtime = self.CTRInstance.GetTarget().getcontent().getRealTime()
         if build_for_realtime:
             additional_cflags.append("-DREALTIME_LINUX")
--- a/targets/Linux/plc_Linux_main.c	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Linux/plc_Linux_main.c	Wed Oct 04 16:24:00 2023 +0200
@@ -117,6 +117,11 @@
         struct timespec plc_start_time;
 #endif
 
+// BEREMIZ_TEST_CYCLES is defined in tests that need to emulate time:
+// - all BEREMIZ_TEST_CYCLES cycles are executed in a row with no pause
+// - __CURRENT_TIME is incremented each cycle according to emulated cycle period
+
+#ifndef BEREMIZ_TEST_CYCLES
         // Sleep until next PLC run
         res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle_time, NULL);
         if(res==EINTR){
@@ -126,6 +131,7 @@
             _LogError("PLC thread timer returned error %d \n", res);
             return;
         }
+#endif // BEREMIZ_TEST_CYCLES
 
 #ifdef REALTIME_LINUX
         // timer overrun detection
@@ -137,12 +143,30 @@
         }
 #endif
 
+#ifdef BEREMIZ_TEST_CYCLES
+#define xstr(s) str(s)
+#define str(arg) #arg
+        // fake current time
+        __CURRENT_TIME.tv_sec = next_cycle_time.tv_sec;
+        __CURRENT_TIME.tv_nsec = next_cycle_time.tv_nsec;
+        // exit loop when enough cycles
+        if(__tick >= BEREMIZ_TEST_CYCLES) {
+            _LogWarning("TEST PLC thread ended after "xstr(BEREMIZ_TEST_CYCLES)" cycles.\n");
+            // After pre-defined test cycles count, PLC thread exits.
+            // Remaining PLC runtime is expected to be cleaned-up/killed by test script
+            return;
+        }
+#else
         PLC_GetTime(&__CURRENT_TIME);
+#endif
         __run();
 
+#ifndef BEREMIZ_TEST_CYCLES
         // ensure next PLC cycle occurence is in the future
         clock_gettime(CLOCK_MONOTONIC, &plc_end_time);
-        while(timespec_gt(plc_end_time, next_cycle_time)){
+        while(timespec_gt(plc_end_time, next_cycle_time))
+#endif
+        {
             periods += 1;
             inc_timespec(&next_cycle_time, period_ns);
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/OSX/XSD	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,6 @@
+
+                  <xsd:element name="OSX">
+                    <xsd:complexType>
+                      %(toolchain_gcc)s
+                    </xsd:complexType>
+                  </xsd:element>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/OSX/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,41 @@
+#!/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) 2007: Edouard TISSERANT and Laurent BESSARD
+#
+# 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.
+
+
+from ..toolchain_gcc import toolchain_gcc
+import platform
+
+
+class OSX_target(toolchain_gcc):
+    dlopen_prefix = "./"
+    extension = ".dynlib"
+
+    def getBuilderCFLAGS(self):
+        return toolchain_gcc.getBuilderCFLAGS(self) + \
+            ["-fPIC", "-Wno-deprecated-declarations",
+             "-Wno-implicit-function-declaration", "-Wno-int-conversion",
+             "-Wno-parentheses-equality", "-Wno-varargs", "-arch", platform.machine()]
+
+    def getBuilderLDFLAGS(self):
+        return toolchain_gcc.getBuilderLDFLAGS(self) + ["-shared", "-arch", platform.machine()]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/OSX/plc_OSX_main.c	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,296 @@
+/**
+ * Mac OSX specific code
+ **/
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <locale.h>
+#include <semaphore.h>
+#include <dispatch/dispatch.h>
+
+static dispatch_semaphore_t Run_PLC;
+
+long AtomicCompareExchange(long *atomicvar, long compared, long exchange)
+{
+    return __sync_val_compare_and_swap(atomicvar, compared, exchange);
+}
+
+long long AtomicCompareExchange64(long long *atomicvar, long long compared,
+                                  long long exchange)
+{
+    return __sync_val_compare_and_swap(atomicvar, compared, exchange);
+}
+
+void PLC_GetTime(IEC_TIME * CURRENT_TIME)
+{
+    struct timespec tmp;
+    clock_gettime(CLOCK_REALTIME, &tmp);
+    CURRENT_TIME->tv_sec = tmp.tv_sec;
+    CURRENT_TIME->tv_nsec = tmp.tv_nsec;
+}
+
+dispatch_queue_t queue;
+dispatch_source_t PLC_timer;
+
+static inline void PLC_timer_cancel(void *arg)
+{
+    dispatch_release(PLC_timer);
+    dispatch_release(queue);
+    exit(0);
+}
+
+static inline void PLC_timer_notify(void *arg)
+{
+    PLC_GetTime(&__CURRENT_TIME);
+    dispatch_semaphore_signal(Run_PLC);
+}
+
+void PLC_SetTimer(unsigned long long next, unsigned long long period)
+{
+    if (next == period && next == 0) {
+        dispatch_suspend(PLC_timer);
+    } else {
+        dispatch_time_t start;
+        start = dispatch_walltime(NULL, next);
+        dispatch_source_set_timer(PLC_timer, start, period, 0);
+        dispatch_resume(PLC_timer);
+    }
+}
+
+void catch_signal(int sig)
+{
+    signal(SIGINT, catch_signal);
+    printf("Got Signal %d\n", sig);
+    dispatch_source_cancel(PLC_timer);
+    exit(0);
+}
+
+static unsigned long __debug_tick;
+
+pthread_t PLC_thread;
+static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int PLC_shutdown = 0;
+
+int ForceSaveRetainReq(void)
+{
+    return PLC_shutdown;
+}
+
+void PLC_thread_proc(void *arg)
+{
+    while (!PLC_shutdown) {
+        dispatch_semaphore_wait(Run_PLC, DISPATCH_TIME_FOREVER);
+        __run();
+    }
+    pthread_exit(0);
+}
+
+#define maxval(a,b) ((a>b)?a:b)
+int startPLC(int argc, char **argv)
+{
+    setlocale(LC_NUMERIC, "C");
+
+    PLC_shutdown = 0;
+
+    Run_PLC = dispatch_semaphore_create(0);
+
+    pthread_create(&PLC_thread, NULL, (void *)&PLC_thread_proc, NULL);
+
+    pthread_mutex_init(&debug_wait_mutex, NULL);
+    pthread_mutex_init(&debug_mutex, NULL);
+    pthread_mutex_init(&python_wait_mutex, NULL);
+    pthread_mutex_init(&python_mutex, NULL);
+
+    pthread_mutex_lock(&debug_wait_mutex);
+    pthread_mutex_lock(&python_wait_mutex);
+
+    queue = dispatch_queue_create("timerQueue", 0);
+    PLC_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
+
+    dispatch_set_context(PLC_timer, &PLC_timer);
+    dispatch_source_set_event_handler_f(PLC_timer, PLC_timer_notify);
+    dispatch_source_set_cancel_handler_f(PLC_timer, PLC_timer_cancel);
+
+    if (__init(argc, argv) == 0) {
+        PLC_SetTimer(common_ticktime__, common_ticktime__);
+
+        /* install signal handler for manual break */
+        signal(SIGINT, catch_signal);
+    } else {
+        return 1;
+    }
+    return 0;
+}
+
+int TryEnterDebugSection(void)
+{
+    if (pthread_mutex_trylock(&debug_mutex) == 0) {
+        /* Only enter if debug active */
+        if (__DEBUG) {
+            return 1;
+        }
+        pthread_mutex_unlock(&debug_mutex);
+    }
+    return 0;
+}
+
+void LeaveDebugSection(void)
+{
+    pthread_mutex_unlock(&debug_mutex);
+}
+
+int stopPLC()
+{
+    /* Stop the PLC */
+    PLC_shutdown = 1;
+    dispatch_semaphore_signal(Run_PLC);
+    PLC_SetTimer(0, 0);
+    pthread_join(PLC_thread, NULL);
+    dispatch_release(Run_PLC);
+    Run_PLC = NULL;
+    dispatch_source_cancel(PLC_timer);
+    __cleanup();
+    pthread_mutex_destroy(&debug_wait_mutex);
+    pthread_mutex_destroy(&debug_mutex);
+    pthread_mutex_destroy(&python_wait_mutex);
+    pthread_mutex_destroy(&python_mutex);
+    return 0;
+}
+
+extern unsigned long __tick;
+
+int WaitDebugData(unsigned long *tick)
+{
+    int res;
+    if (PLC_shutdown)
+        return 1;
+    /* Wait signal from PLC thread */
+    res = pthread_mutex_lock(&debug_wait_mutex);
+    *tick = __debug_tick;
+    return res;
+}
+
+/* Called by PLC thread when debug_publish finished
+ * This is supposed to unlock debugger thread in WaitDebugData*/
+void InitiateDebugTransfer()
+{
+    /* remember tick */
+    __debug_tick = __tick;
+    /* signal debugger thread it can read data */
+    pthread_mutex_unlock(&debug_wait_mutex);
+}
+
+int suspendDebug(int disable)
+{
+    /* Prevent PLC to enter debug code */
+    pthread_mutex_lock(&debug_mutex);
+    /*__DEBUG is protected by this mutex */
+    __DEBUG = !disable;
+    if (disable)
+        pthread_mutex_unlock(&debug_mutex);
+    return 0;
+}
+
+void resumeDebug(void)
+{
+    __DEBUG = 1;
+    /* Let PLC enter debug code */
+    pthread_mutex_unlock(&debug_mutex);
+}
+
+/* from plc_python.c */
+int WaitPythonCommands(void)
+{
+    /* Wait signal from PLC thread */
+    return pthread_mutex_lock(&python_wait_mutex);
+}
+
+/* Called by PLC thread on each new python command*/
+void UnBlockPythonCommands(void)
+{
+    /* signal python thread it can read data */
+    pthread_mutex_unlock(&python_wait_mutex);
+}
+
+int TryLockPython(void)
+{
+    return pthread_mutex_trylock(&python_mutex) == 0;
+}
+
+void UnLockPython(void)
+{
+    pthread_mutex_unlock(&python_mutex);
+}
+
+void LockPython(void)
+{
+    pthread_mutex_lock(&python_mutex);
+}
+
+struct RT_to_nRT_signal_s {
+    pthread_cond_t WakeCond;
+    pthread_mutex_t WakeCondLock;
+};
+
+typedef struct RT_to_nRT_signal_s RT_to_nRT_signal_t;
+
+#define _LogAndReturnNull(text) \
+    {\
+    	char mstr[256] = text " for ";\
+        strncat(mstr, name, 255);\
+        LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
+        return NULL;\
+    }
+
+void *create_RT_to_nRT_signal(char *name)
+{
+    RT_to_nRT_signal_t *sig =
+        (RT_to_nRT_signal_t *) malloc(sizeof(RT_to_nRT_signal_t));
+
+    if (!sig)
+        _LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
+
+    pthread_cond_init(&sig->WakeCond, NULL);
+    pthread_mutex_init(&sig->WakeCondLock, NULL);
+
+    return (void *)sig;
+}
+
+void delete_RT_to_nRT_signal(void *handle)
+{
+    RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t *) handle;
+
+    pthread_cond_destroy(&sig->WakeCond);
+    pthread_mutex_destroy(&sig->WakeCondLock);
+
+    free(sig);
+}
+
+int wait_RT_to_nRT_signal(void *handle)
+{
+    int ret;
+    RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t *) handle;
+    pthread_mutex_lock(&sig->WakeCondLock);
+    ret = pthread_cond_wait(&sig->WakeCond, &sig->WakeCondLock);
+    pthread_mutex_unlock(&sig->WakeCondLock);
+    return ret;
+}
+
+int unblock_RT_to_nRT_signal(void *handle)
+{
+    RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t *) handle;
+    return pthread_cond_signal(&sig->WakeCond);
+}
+
+void nRT_reschedule(void)
+{
+    sched_yield();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/targets/OSX/plc_OSX_main_retain.c	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,352 @@
+/*
+  This file is part of Beremiz, a Integrated Development Environment for
+  programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+
+  See COPYING.runtime
+
+  Copyright (C) 2018: Sergey Surkov <surkov.sv@summatechnology.ru>
+  Copyright (C) 2018: Andrey Skvortsov <andrej.skvortzov@gmail.com>
+
+*/
+
+#ifndef HAVE_RETAIN
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include "iec_types.h"
+
+int GetRetainSize(void);
+
+/* Retain buffer.  */
+FILE *retain_buffer;
+const char rb_file[] = "retain_buffer_file";
+const char rb_file_bckp[] = "retain_buffer_file.bak";
+
+/* Retain header struct.  */
+struct retain_info_t {
+    uint32_t retain_size;
+    uint32_t hash_size;
+    uint8_t *hash;
+    uint32_t header_offset;
+    uint32_t header_crc;
+};
+
+/* Init retain info structure.  */
+struct retain_info_t retain_info;
+
+/* CRC lookup table and initial state.  */
+static const uint32_t crc32_table[256] = {
+    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
+        0xE963A535, 0x9E6495A3,
+    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
+        0xE7B82D07, 0x90BF1D91,
+    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB,
+        0xF4D4B551, 0x83D385C7,
+    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
+        0xFA0F3D63, 0x8D080DF5,
+    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447,
+        0xD20D85FD, 0xA50AB56B,
+    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75,
+        0xDCD60DCF, 0xABD13D59,
+    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
+        0xCFBA9599, 0xB8BDA50F,
+    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11,
+        0xC1611DAB, 0xB6662D3D,
+    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
+        0x9FBFE4A5, 0xE8B8D433,
+    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
+        0x91646C97, 0xE6635C01,
+    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B,
+        0x8208F4C1, 0xF50FC457,
+    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49,
+        0x8CD37CF3, 0xFBD44C65,
+    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
+        0xA4D1C46D, 0xD3D6F4FB,
+    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
+        0xAA0A4C5F, 0xDD0D7CC9,
+    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3,
+        0xB966D409, 0xCE61E49F,
+    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
+        0xB7BD5C3B, 0xC0BA6CAD,
+    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF,
+        0x04DB2615, 0x73DC1683,
+    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D,
+        0x0A00AE27, 0x7D079EB1,
+    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
+        0x196C3671, 0x6E6B06E7,
+    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9,
+        0x17B7BE43, 0x60B08ED5,
+    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767,
+        0x3FB506DD, 0x48B2364B,
+    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
+        0x316E8EEF, 0x4669BE79,
+    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
+        0x220216B9, 0x5505262F,
+    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
+        0x2CD99E8B, 0x5BDEAE1D,
+    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
+        0x72076785, 0x05005713,
+    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D,
+        0x7CDCEFB7, 0x0BDBDF21,
+    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B,
+        0x6FB077E1, 0x18B74777,
+    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
+        0x616BFFD3, 0x166CCF45,
+    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
+        0x4969474D, 0x3E6E77DB,
+    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
+        0x47B2CF7F, 0x30B5FFE9,
+    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
+        0x54DE5729, 0x23D967BF,
+    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1,
+        0x5A05DF1B, 0x2D02EF8D,
+};
+
+uint32_t retain_crc;
+
+/* Calculate CRC32 for len bytes from pointer buf with init starting value.  */
+uint32_t GenerateCRC32Sum(const void *buf, unsigned int len, uint32_t init)
+{
+    uint32_t crc = ~init;
+    unsigned char *current = (unsigned char *)buf;
+    while (len--)
+        crc = crc32_table[(crc ^ *current++) & 0xFF] ^ (crc >> 8);
+    return ~crc;
+}
+
+/* Calc CRC32 for retain file byte by byte.  */
+int CheckFileCRC(FILE * file_buffer)
+{
+    /* Set the magic constant for one-pass CRC calc according to ZIP CRC32.  */
+    const uint32_t magic_number = 0x2144df1c;
+
+    /* CRC initial state.  */
+    uint32_t calc_crc32 = 0;
+    char data_block = 0;
+
+    while (!feof(file_buffer)) {
+        if (fread(&data_block, sizeof(data_block), 1, file_buffer))
+            calc_crc32 =
+                GenerateCRC32Sum(&data_block, sizeof(char), calc_crc32);
+    }
+
+    /* Compare crc result with a magic number.  */
+    return (calc_crc32 == magic_number) ? 1 : 0;
+}
+
+/* Compare current hash with hash from file byte by byte.  */
+int CheckFilehash(void)
+{
+    int k, ret;
+    int offset = sizeof(retain_info.retain_size);
+
+    rewind(retain_buffer);
+    fseek(retain_buffer, offset, SEEK_SET);
+
+    uint32_t size;
+    ret = fread(&size, sizeof(size), 1, retain_buffer);
+    if (size != retain_info.hash_size)
+        return 0;
+
+    for (k = 0; k < retain_info.hash_size; k++) {
+        uint8_t file_digit;
+        ret = fread(&file_digit, sizeof(char), 1, retain_buffer);
+        if (file_digit != *(retain_info.hash + k))
+            return 0;
+    }
+
+    return 1;
+}
+
+void InitRetain(void)
+{
+    int i;
+
+    /* Get retain size in bytes */
+    retain_info.retain_size = GetRetainSize();
+
+    /* Hash stored in retain file as array of char in hex digits
+       (that's why we divide strlen in two).  */
+    retain_info.hash_size = PLC_ID ? strlen(PLC_ID) / 2 : 0;
+    //retain_info.hash_size = 0;
+    retain_info.hash = malloc(retain_info.hash_size);
+
+    /* Transform hash string into byte sequence.  */
+    for (i = 0; i < retain_info.hash_size; i++) {
+        int byte = 0;
+        sscanf((PLC_ID + i * 2), "%02X", &byte);
+        retain_info.hash[i] = byte;
+    }
+
+    /* Calc header offset.  */
+    retain_info.header_offset = sizeof(retain_info.retain_size) +
+        sizeof(retain_info.hash_size) + retain_info.hash_size;
+
+    /*  Set header CRC initial state.  */
+    retain_info.header_crc = 0;
+
+    /* Calc crc for header.  */
+    retain_info.header_crc = GenerateCRC32Sum(&retain_info.retain_size,
+                                              sizeof(retain_info.retain_size),
+                                              retain_info.header_crc);
+
+    retain_info.header_crc = GenerateCRC32Sum(&retain_info.hash_size,
+                                              sizeof(retain_info.hash_size),
+                                              retain_info.header_crc);
+
+    retain_info.header_crc = GenerateCRC32Sum(retain_info.hash,
+                                              retain_info.hash_size,
+                                              retain_info.header_crc);
+}
+
+void CleanupRetain(void)
+{
+    /* Free hash memory.  */
+    free(retain_info.hash);
+}
+
+int CheckRetainFile(const char *file)
+{
+    retain_buffer = fopen(file, "rb");
+    if (retain_buffer) {
+        /* Check CRC32 and hash.  */
+        if (CheckFileCRC(retain_buffer))
+            if (CheckFilehash())
+                return 1;
+        fclose(retain_buffer);
+        retain_buffer = NULL;
+    }
+    return 0;
+}
+
+int CheckRetainBuffer(void)
+{
+    retain_buffer = NULL;
+    if (!retain_info.retain_size)
+        return 1;
+
+    /* Check latest retain file.  */
+    if (CheckRetainFile(rb_file))
+        return 1;
+
+    /* Check if we have backup.  */
+    if (CheckRetainFile(rb_file_bckp))
+        return 1;
+
+    /* We don't have any valid retain buffer - nothing to remind.  */
+    return 0;
+}
+
+#ifndef FILE_RETAIN_SAVE_PERIOD_S
+#define FILE_RETAIN_SAVE_PERIOD_S 1.0
+#endif
+
+static double CalcDiffSeconds(IEC_TIME * t1, IEC_TIME * t2)
+{
+    IEC_TIME dt = {
+        t1->tv_sec - t2->tv_sec,
+        t1->tv_nsec - t2->tv_nsec
+    };
+
+    if ((dt.tv_nsec < -1000000000) || ((dt.tv_sec > 0) && (dt.tv_nsec < 0))) {
+        dt.tv_sec--;
+        dt.tv_nsec += 1000000000;
+    }
+    if ((dt.tv_nsec > +1000000000) || ((dt.tv_sec < 0) && (dt.tv_nsec > 0))) {
+        dt.tv_sec++;
+        dt.tv_nsec -= 1000000000;
+    }
+    return dt.tv_sec + 1e-9 * dt.tv_nsec;
+}
+
+int RetainSaveNeeded(void)
+{
+    int ret = 0;
+    static IEC_TIME last_save;
+    IEC_TIME now;
+    double diff_s;
+
+    /* no retain */
+    if (!retain_info.retain_size)
+        return 0;
+
+    /* periodic retain flush to avoid high I/O load */
+    PLC_GetTime(&now);
+
+    diff_s = CalcDiffSeconds(&now, &last_save);
+
+    if ((diff_s > FILE_RETAIN_SAVE_PERIOD_S) || ForceSaveRetainReq()) {
+        ret = 1;
+        last_save = now;
+    }
+    return ret;
+}
+
+void ValidateRetainBuffer(void)
+{
+    if (!retain_buffer)
+        return;
+
+    /* Add retain data CRC to the end of buffer file.  */
+    fseek(retain_buffer, 0, SEEK_END);
+    fwrite(&retain_crc, sizeof(uint32_t), 1, retain_buffer);
+
+    /* Sync file buffer and close file.  */
+#ifdef __WIN32
+    fflush(retain_buffer);
+#else
+    fsync(fileno(retain_buffer));
+#endif
+
+    fclose(retain_buffer);
+    retain_buffer = NULL;
+}
+
+void InValidateRetainBuffer(void)
+{
+    if (!RetainSaveNeeded())
+        return;
+
+    /* Rename old retain file into *.bak if it exists.  */
+    rename(rb_file, rb_file_bckp);
+
+    /* Set file CRC initial value.  */
+    retain_crc = retain_info.header_crc;
+
+    /* Create new retain file.  */
+    retain_buffer = fopen(rb_file, "wb+");
+    if (!retain_buffer) {
+        fprintf(stderr, "Failed to create retain file : %s\n", rb_file);
+        return;
+    }
+
+    /* Write header to the new file.  */
+    fwrite(&retain_info.retain_size,
+           sizeof(retain_info.retain_size), 1, retain_buffer);
+    fwrite(&retain_info.hash_size,
+           sizeof(retain_info.hash_size), 1, retain_buffer);
+    fwrite(retain_info.hash,
+           sizeof(char), retain_info.hash_size, retain_buffer);
+}
+
+void Retain(unsigned int offset, unsigned int count, void *p)
+{
+    if (!retain_buffer)
+        return;
+
+    /* Generate CRC 32 for each data block.  */
+    retain_crc = GenerateCRC32Sum(p, count, retain_crc);
+
+    /* Save current var in file.  */
+    fseek(retain_buffer, retain_info.header_offset + offset, SEEK_SET);
+    fwrite(p, count, 1, retain_buffer);
+}
+
+void Remind(unsigned int offset, unsigned int count, void *p)
+{
+    int ret;
+    /* Remind variable from file.  */
+    fseek(retain_buffer, retain_info.header_offset + offset, SEEK_SET);
+    ret = fread((void *)p, count, 1, retain_buffer);
+}
+#endif                          // !HAVE_RETAIN
--- a/targets/Win32/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Win32/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from ..toolchain_gcc import toolchain_gcc
 
 
@@ -31,5 +31,9 @@
     dlopen_prefix = ""
     extension = ".dll"
 
+    def getBuilderCFLAGS(self):
+        return toolchain_gcc.getBuilderCFLAGS(self) + \
+            ["-Wno-implicit-function-declaration", "-Wno-int-conversion"]
+
     def getBuilderLDFLAGS(self):
         return toolchain_gcc.getBuilderLDFLAGS(self) + ["-shared", "-lwinmm"]
--- a/targets/Win32/plc_Win32_main.c	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Win32/plc_Win32_main.c	Wed Oct 04 16:24:00 2023 +0200
@@ -35,12 +35,6 @@
 	(*CURRENT_TIME).tv_nsec = timetmp.millitm * 1000000;
 }
 
-void PLC_timer_notify()
-{
-    PLC_GetTime(&__CURRENT_TIME);
-    __run();
-}
-
 HANDLE PLC_timer = NULL;
 void PLC_SetTimer(unsigned long long next, unsigned long long period)
 {
@@ -65,9 +59,12 @@
 {
     PLC_shutdown = 0;
     while(!PLC_shutdown) {
-        if (WaitForSingleObject(PLC_timer, INFINITE) != WAIT_OBJECT_0)
+        if (WaitForSingleObject(PLC_timer, INFINITE) != WAIT_OBJECT_0){
             PLC_shutdown = 1;
-        PLC_timer_notify();
+            break;
+        }
+        PLC_GetTime(&__CURRENT_TIME);
+        __run();
     }
 }
 
@@ -172,9 +169,16 @@
 
 int stopPLC()
 {
+ 	
+    PLC_shutdown = 1;
+    // force last wakeup of PLC thread
+    SetWaitableTimer(PLC_timer, 0, 0, NULL, NULL, 0);
+    // wait end of PLC thread
+    WaitForSingleObject(PLC_thread, INFINITE);
+
+    __cleanup();
+
     CloseHandle(PLC_timer);
-    WaitForSingleObject(PLC_thread, INFINITE);
-    __cleanup();
     CloseHandle(debug_wait_sem);
     CloseHandle(debug_sem);
     CloseHandle(python_wait_sem);
--- a/targets/XSD_toolchain_makefile	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/XSD_toolchain_makefile	Wed Oct 04 16:24:00 2023 +0200
@@ -1,3 +1,3 @@
 
-          <xsd:attribute name="Command" type="xsd:string" use="optional" default="make -C %(buildpath)s all BEREMIZSRC=%(src)s BEREMIZCFLAGS=%(cflags)s MD5=%(md5)s USE_BEREMIZ=1 FROM_BEREMIZ=1"/>
+          <xsd:attribute name="Command" type="xsd:string" use="optional" default="make -C %(buildpath)s -f ../project_files/Makefile all BEREMIZSRC=%(src)s BEREMIZCFLAGS=%(cflags)s MD5=%(md5)s USE_BEREMIZ=1 FROM_BEREMIZ=1"/>
           
--- a/targets/Xenomai/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/Xenomai/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 from ..toolchain_gcc import toolchain_gcc
 
 
--- a/targets/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -35,15 +35,15 @@
 """
 
 
-from __future__ import absolute_import
 from os import listdir, path
 import util.paths as paths
+import importlib
 
 _base_path = paths.AbsDir(__file__)
 
 
 def _GetLocalTargetClassFactory(name):
-    return lambda: getattr(__import__(name, globals(), locals()), name+"_target")
+    return lambda: getattr(importlib.import_module(f"targets.{name}"), f"{name}_target")
 
 
 targets = dict([(name, {"xsd":   path.join(_base_path, name, "XSD"),
@@ -69,12 +69,12 @@
     targetchoices = ""
 
     # Get all xsd toolchains
-    for toolchainname, xsdfilename in toolchains.iteritems():
+    for toolchainname, xsdfilename in toolchains.items():
         if path.isfile(xsdfilename):
             DictXSD_toolchain["toolchain_"+toolchainname] = open(xsdfilename).read()
 
     # Get all xsd targets
-    for target_name, nfo in targets.iteritems():
+    for target_name, nfo in targets.items():
         xsd_string = open(nfo["xsd"]).read()
         targetchoices += xsd_string % dict(DictXSD_toolchain,
                                            target_name=target_name)
--- a/targets/toolchain_gcc.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/toolchain_gcc.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 import re
 import operator
@@ -51,14 +51,24 @@
         """
         Returns list of builder specific CFLAGS
         """
-        return [self.CTRInstance.GetTarget().getcontent().getCFLAGS()]
+        cflags = [self.CTRInstance.GetTarget().getcontent().getCFLAGS()]
+        if "CFLAGS" in os.environ:
+            cflags.append(os.environ["CFLAGS"])
+        if "SYSROOT" in os.environ:
+            cflags.append("--sysroot="+os.environ["SYSROOT"])
+        return cflags
 
     def getBuilderLDFLAGS(self):
         """
         Returns list of builder specific LDFLAGS
         """
-        return self.CTRInstance.LDFLAGS + \
+        ldflags = self.CTRInstance.LDFLAGS + \
             [self.CTRInstance.GetTarget().getcontent().getLDFLAGS()]
+        if "LDLAGS" in os.environ:
+            ldflags.append(os.environ["LDLAGS"])
+        if "SYSROOT" in os.environ:
+            ldflags.append("--sysroot="+os.environ["SYSROOT"])
+        return ldflags
 
     def getCompiler(self):
         """
@@ -118,7 +128,7 @@
         self.append_cfile_deps(src, deps)
         # recurse through deps
         # TODO detect cicular deps.
-        return reduce(operator.concat, map(self.concat_deps, deps), src)
+        return reduce(operator.concat, list(map(self.concat_deps, deps)), src)
 
     def check_and_update_hash_and_deps(self, bn):
         # Get latest computed hash and deps
@@ -126,7 +136,7 @@
         # read source
         src = open(os.path.join(self.buildpath, bn)).read()
         # compute new hash
-        newhash = hashlib.md5(src).hexdigest()
+        newhash = hashlib.md5(src.encode()).hexdigest()
         # compare
         match = (oldhash == newhash)
         if not match:
@@ -138,7 +148,7 @@
             self.srcmd5[bn] = (newhash, deps)
         # recurse through deps
         # TODO detect cicular deps.
-        return reduce(operator.and_, map(self.check_and_update_hash_and_deps, deps), match)
+        return reduce(operator.and_, list(map(self.check_and_update_hash_and_deps, deps)), match)
 
     def calc_source_md5(self):
         wholesrcdata = ""
--- a/targets/toolchain_makefile.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/targets/toolchain_makefile.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 import re
 import operator
@@ -83,7 +83,7 @@
                     deps.append(depfn)
         # recurse through deps
         # TODO detect cicular deps.
-        return reduce(operator.concat, map(self.concat_deps, deps), src)
+        return reduce(operator.concat, list(map(self.concat_deps, deps)), src)
 
     def build(self):
         srcfiles = []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/Makefile	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,229 @@
+#! gmake
+
+# beremiz/tests/Makefile :
+#
+#   Makefile to prepare and run Beremiz tests.
+#
+#   For developper to:
+#       - quickly run a test (TDD) on current code 
+#       - write new tests, debug existing tests 
+#
+#     Use cases :
+#
+#       run given tests
+#           $ make run_python_exemple.sikuli
+#
+#       run tests from particular test classes
+#           $ make ide_tests
+#
+#       run one particular test in a Xnest window
+#           $ make xnest_run_python_exemple.sikuli
+#
+#       run Xnest window with just xterm
+#           $ make xnest_xterm
+#
+#       run Xnest window with sikuli IDE and xterm
+#           $ make xnest_sikuli
+#
+#       build minimal beremiz and matiec to run tests
+#           $ make built_apps
+#
+#   For CI/CD scripts to catch and report all failures. Use cases :
+#
+#       run all tests
+#           $ make
+#
+#   
+#   Test results, and other test byproducts are in $(test_dir), 
+#   $(test_dir) defaults to $(HOME)/test and can be overloaded:
+#       $ make test_dir=${HOME}/other_test_dir
+#
+#   Makefile attemps to use xvfb-run to run each test individually with its own
+#   X server instance. This behavior can be overloaded
+#       $ DISPLAY=:42 make xserver_command='echo "Using $DISPLAY X Server !";'
+#
+#   Matiec and Beremiz code are expected to be clean, ready to build
+#   Any change in Matiec directory triggers rebuild of matiec.
+#   Any change in Matiec and Beremiz directory triggers copy of source code
+#   to $(test_dir)/build.
+#
+#   BEREMIZPYTHONPATH is expected to be absolute path to python interpreter
+#
+#   Please note:
+#       In order to run asside a freshly build Matiec, tested beremiz instance
+#       needs to run on code from $(test_dir)/build/beremiz, a fresh copy
+#       of the Beremiz directory $(src)/beremiz, where we run tests from.
+#   
+
+all: source_check cli_tests ide_tests runtime_tests 
+
+# Variable $(src) is directory such that executed 
+# $(src)/Makefile is this file.
+src := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
+# $(workspace) is directory containing this project
+workspace ?= $(abspath $(src)/../..)
+
+test_dir ?= $(HOME)/test
+build_dir = $(test_dir)/build
+
+#
+# SOURCE and BUILD
+#
+
+BUILT_PROJECTS=beremiz matiec open62541
+
+tar_opts=--absolute-names --exclude=.hg --exclude=.git --exclude=.*.pyc --exclude=.*.swp
+
+# sha1 checksum of source is used to force copy/compile on each change
+
+define make_checksum_assign
+$(1)_checksum = $(shell tar $(tar_opts) -c $(workspace)/$(1) | sha1sum | cut -d ' ' -f 1)
+endef
+$(foreach project,$(BUILT_PROJECTS),$(eval $(call make_checksum_assign,$(project))))
+
+$(build_dir):
+	mkdir -p $(build_dir)
+
+define make_src_rule
+$(build_dir)/$(1)/$($(1)_checksum).sha1: | $(build_dir) $(workspace)/$(1)
+	rm -rf $(build_dir)/$(1)
+	tar -C $(workspace) $(tar_opts) -c $(1) | tar -C $(build_dir) -x
+	touch $$@
+endef
+$(foreach project,$(BUILT_PROJECTS),$(eval $(call make_src_rule,$(project))))
+
+$(build_dir)/matiec/iec2c: | $(build_dir)/matiec/$(matiec_checksum).sha1
+	cd $(build_dir)/matiec && \
+    autoreconf -i && \
+    ./configure && \
+    make
+
+$(build_dir)/open62541/build/bin/libopen62541.a: | $(build_dir)/open62541/$(open62541_checksum).sha1
+	cd $(build_dir)/open62541 && \
+    rm -rf build && mkdir build && cd build && \
+	cmake -D UA_ENABLE_ENCRYPTION=OPENSSL .. && \
+	make
+
+built_apps: $(build_dir)/matiec/iec2c $(build_dir)/beremiz/$(beremiz_checksum).sha1 $(build_dir)/open62541/build/bin/libopen62541.a
+	touch $@
+
+define log_command
+	$(call $(1),$(2)) | tee test_stdout.txt; exit $$$${PIPESTATUS[0]}
+endef
+
+define prep_test
+	rm -rf $(test_dir)/$(1)_results
+	mkdir $(test_dir)/$(1)_results
+	cd $(test_dir)/$(1)_results
+endef
+
+#
+# IDE TESTS
+#
+
+ide_test_dir = $(src)/ide_tests
+sikuli_ide_tests = $(subst $(ide_test_dir)/,,$(wildcard $(ide_test_dir)/*.sikuli))
+pytest_ide_tests = $(subst $(ide_test_dir)/,,$(wildcard $(ide_test_dir)/*.pytest))
+
+fluxbox_command ?= echo "session.screen0.toolbar.placement: TopCenter" > fluxbox_init; (fluxbox -rc fluxbox_init >/dev/null 2>&1 &)
+
+define sikuli_idetest_command
+	$(fluxbox_command); BEREMIZPATH=$(build_dir)/beremiz sikulix -r $(src)/ide_tests/$(1)
+endef
+
+
+DELAY=400
+KILL_DELAY=430
+PYTEST=$(dir $(BEREMIZPYTHONPATH))pytest
+define pytest_idetest_command
+	$(fluxbox_command); PYTHONPATH=$(ide_test_dir) timeout -k $(KILL_DELAY) $(DELAY) $(PYTEST) --maxfail=1 --timeout=100  $(src)/ide_tests/$(1)
+endef
+
+# Xnest based interactive sessions for tests edit and debug. 
+# Would be nice with something equivalent to xvfb-run, waiting for USR1.
+# Arbitrary "sleep 1" is probably enough for interactive use
+define xnest_run
+	Xnest :42 -geometry 1920x1080+0+0 & export xnestpid=$$!; sleep 1; DISPLAY=:42 $(1); export res=$$?; kill $${xnestpid} 2>/dev/null; exit $${res}
+endef
+
+xserver_command ?= xvfb-run -s '-screen 0 1920x1080x24'
+
+define make_idetest_rule
+$(test_dir)/$(1)_results/.passed: built_apps
+	$(call prep_test,$(1)); $(xserver_command) bash -c '$(call log_command,$(2),$(1))'
+	touch $$@
+
+# Manually invoked rule {testname}.sikuli
+$(1): $(test_dir)/$(1)_results/.passed
+
+# Manually invoked rule xnest_{testname}.sikuli
+# runs test in xnest so that one can see what happens
+xnest_$(1): built_apps
+	$(call prep_test,$(1)); $$(call xnest_run, bash -c '$(call log_command,$(2),$(1))')
+
+ide_tests_targets += $(test_dir)/$(1)_results/.passed
+endef
+$(foreach idetest,$(sikuli_ide_tests),$(eval $(call make_idetest_rule,$(idetest),sikuli_idetest_command)))
+$(foreach idetest,$(pytest_ide_tests),$(eval $(call make_idetest_rule,$(idetest),pytest_idetest_command)))
+
+ide_tests : $(ide_tests_targets)
+	echo "$(ide_tests_targets) : Passed"
+
+xnest_xterm: built_apps
+	$(call xnest_run, bash -c '$(fluxbox_command);xterm')
+
+xnest_sikuli: built_apps
+	$(call xnest_run, bash -c '$(fluxbox_command);(BEREMIZPATH=$(build_dir)/beremiz xterm -e sikulix &);xterm')
+
+xvfb_sikuli: built_apps
+	echo "******************************************"
+	echo "On host, run 'xvncviewer 127.0.0.1:5900' to see sikuli X session"
+	echo "Docker container must be created with TESTDEBUG=YES. For example :"
+	echo "./clean_docker_container.sh && ./build_docker_image.sh && TESTDEBUG=YES ./create_docker_container.sh && ./do_test_in_docker.sh xvfb_sikuli"
+	echo "******************************************"
+	$(xserver_command) bash -c '(fluxbox &);(x11vnc &);(BEREMIZPATH=$(build_dir)/beremiz xterm -e sikulix &);xterm'
+
+#
+# CLI TESTS
+#
+
+cli_test_dir = $(src)/cli_tests
+cli_tests = $(subst $(cli_test_dir)/,,$(wildcard $(cli_test_dir)/*.bash))
+
+define clitest_command
+	BEREMIZPATH=$(build_dir)/beremiz source $(src)/cli_tests/$(1)
+endef
+
+define make_clitest_rule
+$(test_dir)/$(1)_results/.passed: built_apps
+	$(call prep_test,$(1)); bash -c '$(call log_command,$(2),$(1))'
+	touch $$@
+
+# Manually invoked rule
+$(1): $(test_dir)/$(1)_results/.passed
+
+cli_tests_targets += $(test_dir)/$(1)_results/.passed
+endef
+$(foreach clitest,$(cli_tests),$(eval $(call make_clitest_rule,$(clitest),clitest_command)))
+
+cli_tests: $(cli_tests_targets)
+	echo "$(cli_tests_targets) : Passed"
+
+clean_results:
+	rm -rf $(test_dir)/*_results
+
+clean: clean_results
+	rm -rf $(build_dir)
+
+
+# TODOs 
+
+source_check:
+	echo TODO $@
+
+runtime_tests:
+	echo TODO $@
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/iec61131_lang_test.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Run IEC61131 language test through command line, and check for success in output
+
+coproc setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k --project-home $BEREMIZPATH/tests/projects/iec61131_lang_test build transfer run;
+
+while read -t 5 -u ${COPROC[0]} line; do 
+    echo "$line"
+    if [[ "$line" == *ALL\ TESTS\ OK* ]]; then
+        pkill -9 -s $COPROC_PID 
+        exit 0
+    fi
+done
+
+exit 42
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/opcua_test.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+rm -f ./SRVOK ./PLCOK
+
+# Run server
+$BEREMIZPYTHONPATH - > >(
+    echo "Start SRV loop"
+    while read line; do 
+        # Wait for server to print modified value
+        echo "SRV>> $line"
+        if [[ "$line" == 3.4 ]]; then
+            echo "PLC could write value"
+            touch ./SRVOK
+        fi
+    done
+    echo "End SRV loop"
+) << EOF &
+
+import sys
+import os
+import time
+import asyncio
+
+from asyncua import Server
+
+async def main():
+    server = Server()
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
+
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
+
+    objects = server.get_objects_node()
+
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
+
+    await server.start()
+    try:
+        while True:
+            await asyncio.sleep(1)
+            print(await testvar.get_value())
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
+EOF
+SERVER_PID=$!
+
+# Start PLC with opcua test
+setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k \
+     --project-home $BEREMIZPATH/tests/projects/opcua_client build transfer run > >(
+echo "Start PLC loop"
+while read line; do 
+    # Wait for PLC runtime to output expected value on stdout
+    echo "PLC>> $line"
+    if [[ "$line" == 1.2 ]]; then
+        echo "PLC could read value"
+        touch ./PLCOK
+    fi
+done
+echo "End PLC loop"
+) &
+PLC_PID=$!
+
+echo all subprocess started, start polling results
+res=110  # default to ETIMEDOUT
+c=30
+while ((c--)); do
+    if [[ -a ./SRVOK && -a ./PLCOK ]]; then
+        echo got results.
+        res=0  # OK success
+        break
+    else
+        echo waiting.... $c
+        sleep 1
+    fi
+done
+
+# Kill PLC and subprocess
+echo will kill PLC:$PLC_PID and SERVER:$SERVER_PID
+pkill -s $PLC_PID 
+kill $SERVER_PID
+
+exit $res
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/opcua_test_encrypted.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,114 @@
+#!/bin/bash
+
+rm -f ./SRVOK ./PLCOK
+
+
+yes "" | openssl req -x509 -newkey rsa:2048 -keyout my_private_key.pem -out my_cert.pem \
+        -days 355 -nodes -addext "subjectAltName = URI:urn:example.org:FreeOpcUa:python-opcua"
+openssl x509 -outform der -in my_cert.pem -out my_cert.der
+
+# Run server
+$BEREMIZPYTHONPATH - > >(
+    echo "Start SRV loop"
+    while read line; do 
+        # Wait for server to print modified value
+        echo "SRV>> $line"
+        if [[ "$line" == 3.4 ]]; then
+            echo "PLC could write value"
+            touch ./SRVOK
+        fi
+    done
+    echo "End SRV loop"
+) << EOF &
+
+import sys
+import os
+import time
+import asyncio
+
+from asyncua import ua, Server
+from asyncua.server.users import User, UserRole
+
+# Asyncua can't work without (over)simple shared cerificates/privkey.
+# No user is involved in that case, but asyncua needs it.
+# Over permessive User Manager hereafter helps cuting that corner.
+class AllAdminUserManager:
+    def get_user(self, iserver, username=None, password=None, certificate=None):
+        return User(role=UserRole.Admin)
+
+async def main():
+    server = Server(user_manager=AllAdminUserManager())
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
+
+    server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
+    await server.load_certificate("my_cert.der")
+    await server.load_private_key("my_private_key.pem")
+
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
+
+    objects = server.get_objects_node()
+
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
+
+    await server.start()
+
+    try:
+        while True:
+            await asyncio.sleep(1)
+            print(await testvar.get_value())
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
+EOF
+SERVER_PID=$!
+
+PROJECT_FILES_DIR=$BEREMIZPATH/tests/projects/opcua_client_encrypted/project_files
+mkdir $PROJECT_FILES_DIR
+cp my_cert.der my_private_key.pem $PROJECT_FILES_DIR
+
+# Start PLC with opcua test
+setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k \
+     --project-home $BEREMIZPATH/tests/projects/opcua_client_encrypted build transfer run > >(
+echo "Start PLC loop"
+while read line; do 
+    # Wait for PLC runtime to output expected value on stdout
+    echo "PLC>> $line"
+    if [[ "$line" == 1.2 ]]; then
+        echo "PLC could read value"
+        touch ./PLCOK
+    fi
+done
+echo "End PLC loop"
+) &
+PLC_PID=$!
+
+echo all subprocess started, start polling results
+res=110  # default to ETIMEDOUT
+c=45
+while ((c--)); do
+    if [[ -a ./SRVOK && -a ./PLCOK ]]; then
+        echo got results.
+        res=0  # OK success
+        break
+    else
+        echo waiting.... $c
+        sleep 1
+    fi
+done
+
+# Kill PLC and subprocess
+echo will kill PLC:$PLC_PID and SERVER:$SERVER_PID
+pkill -s $PLC_PID 
+kill $SERVER_PID
+
+exit $res
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cli_tests/run_python_example.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Run python example throug command line, and check usual output
+
+coproc setsid $BEREMIZPYTHONPATH $BEREMIZPATH/Beremiz_cli.py -k --project-home $BEREMIZPATH/exemples/python build transfer run;
+
+while read -u ${COPROC[0]} line; do 
+    echo "$line"
+    if [[ "$line" == *Grumpf* ]]; then
+        pkill -9 -s $COPROC_PID 
+        exit 0
+    fi
+done
+
+exit 42
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/conftest.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,78 @@
+#!/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) 2017: Andrey Skvortsov
+#
+# 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.
+
+
+
+import os
+import sys
+
+# import pytest
+# import xvfbwrapper
+
+
+def init_environment():
+    """Append module root directory to sys.path"""
+    try:
+        import Beremiz as _Beremiz
+    except ImportError:
+        sys.path.append(
+            os.path.abspath(
+                os.path.join(
+                    os.path.dirname(__file__), '..', '..')
+            )
+        )
+
+
+init_environment()
+
+#
+# Something seems to be broken in Beremiz application,
+# because after tests in test_application.py during Xvfb shutdown
+# pytest returns error message:
+# pytest: Fatal IO error 11 (Die Ressource ist zur Zeit nicht verfügbar) on X server :2821.
+#
+# As a result of that pytest returns code 1 as some tests were failed,
+# but they aren't.
+#
+# To avoid this Xvfb is launched and killed not by pytest.
+# $ Xvfb :42 -screen 0 1280x1024x24 &
+# $ export DISPLAY=:42
+# $ pytest --timeout=10 ./tests/tools
+# $ pkill -9 Xvfb
+#
+# TODO: find root of this problem.
+
+
+# vdisplay = None
+#
+# @pytest.fixture(scope="session", autouse=True)
+# def start_xvfb_server(request):
+#     global vdisplay
+#     vdisplay = xvfbwrapper.Xvfb(width=1280, height=720)
+#     vdisplay.start()
+#     request.addfinalizer(stop_xvfb_server)
+#
+# def stop_xvfb_server():
+#     if vdisplay is not None:
+#         vdisplay.stop()
Binary file tests/ide_tests/debug_project.sikuli/1646066996789.png has changed
Binary file tests/ide_tests/debug_project.sikuli/1646066996790.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/debug_project.sikuli/debug_project.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,79 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test
+
+def test(app):
+
+    app.k.Clean()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitPatternInStdout("Successfully built.", 10)
+    
+    app.k.Connect()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.click("main")
+
+    app.WaitIdleUI()
+    
+    app.click("1646066996789.png")
+
+    app.WaitIdleUI()
+
+    app.click("example")
+
+    app.WaitIdleUI()
+
+    app.type(Key.DOWN * 10, Key.CTRL)
+
+    app.WaitIdleUI()
+
+    app.k.Run()
+
+    # wait up to 10 seconds for 10 Grumpfs
+    app.waitPatternInStdout("Grumpf", 10, 10)
+
+    app.rightClick("1646066996790.png")
+
+    # app.click("Force value")
+
+    app.type(Key.DOWN)
+
+    app.type(Key.ENTER)
+
+    # app.click("1646062660790.png")
+
+    # app.type("a", Key.CTRL)
+
+    # app.type(Key.BACKSPACE)
+    app.type(Key.HOME)
+
+    app.type("a", Key.CTRL)
+
+    app.type(Key.DELETE)
+
+    app.type("'sys.stdout.write(\"DEBUG TEST OK\\n\")'")
+
+    app.type(Key.ENTER)
+    
+    # wait 10 seconds for 10 patterns
+    return app.waitPatternInStdout("DEBUG TEST OK", 10)
+
+run_test(test, exemple="python")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/edit_project.sikuli/edit_project.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,64 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test
+
+def test(app):
+
+    app.doubleClick("main")
+
+    app.WaitIdleUI()
+
+    app.click("example")
+
+    app.WaitIdleUI()
+
+    app.type(Key.DOWN * 10, Key.CTRL)
+
+    # Zoom in to allow OCR
+    app.type("+")
+
+    app.WaitIdleUI()
+
+    app.doubleClick("Hello")
+
+    app.WaitIdleUI()
+
+    app.type(Key.TAB*3)  # select text content
+
+    app.type("'sys.stdout.write(\"EDIT TEST OK\\n\")'")
+
+    app.type(Key.ENTER)
+
+    app.WaitIdleUI()
+
+    app.k.Clean()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Build()
+
+    app.waitPatternInStdout("Successfully built.", 10)
+
+    app.k.Connect()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Transfer()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Run()
+
+    # wait 10 seconds for 10 patterns
+    return app.waitPatternInStdout("EDIT TEST OK", 10)
+
+run_test(test, exemple="python")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/new_project.sikuli/new_project.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,136 @@
+""" This test opens, builds and runs a new project.
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import *
+
+def test(app):
+    
+    new_project_path = os.path.join(os.path.abspath(os.path.curdir), "new_test_project")
+    
+    # New project path must exist (usually created in directory selection dialog)
+    os.mkdir(new_project_path)
+    
+    app.WaitIdleUI()
+    
+    # Create new project (opens new project directory selection dialog)
+    app.k.New()
+    
+    app.WaitIdleUI()
+    
+    # Move to "Home" section of file selecor, otherwise address is 
+    # "file ignored" at first run
+    app.type("f", Key.CTRL)
+    app.WaitIdleUI()
+    app.type(Key.ESC)
+    app.WaitIdleUI()
+    app.type(Key.TAB)
+    app.WaitIdleUI()
+    
+    # Enter directory by name
+    app.k.Address()
+    app.WaitIdleUI()
+    
+    # Fill address bar
+    app.type(new_project_path + Key.ENTER)
+    
+    app.WaitIdleUI()
+    
+    # When prompted for creating first program select type ST
+    app.type(Key.TAB*4)  # go to lang dropdown
+    app.type(Key.DOWN*2) # change selected language
+    app.type(Key.ENTER)  # validate
+    
+    app.WaitIdleUI()
+    
+    # Name created program
+    app.type("Test program")
+    
+    app.WaitIdleUI()
+    
+    # Focus on Variable grid
+    app.type(Key.TAB*4)
+    
+    # Add 2 variables
+    app.type(Key.ADD*2)
+    
+    # Focus on ST text
+    app.WaitIdleUI()
+    
+    app.type(Key.TAB*8)
+    
+    app.type("""\
+    LocalVar0 := LocalVar1;
+    {printf("Test OK\\n");fflush(stdout);}
+    """)
+    
+    app.k.Save()
+    
+    # Close ST POU
+    app.type("w", Key.CTRL)
+    
+    app.WaitIdleUI()
+    
+    # Focus project tree and select root item
+    app.type(Key.TAB)
+    
+    app.type(Key.LEFT)
+    
+    app.type(Key.UP)
+    
+    # Edit root item
+    app.type(Key.ENTER)
+    
+    app.WaitIdleUI()
+    
+    # Switch to config tab
+    app.type(Key.RIGHT*2)
+    
+    # Focus on URI
+    app.type(Key.TAB)
+    
+    # Set URI
+    app.type("LOCAL://")
+    
+    # FIXME: Select other field to ensure URI is validated
+    app.type(Key.TAB)
+    
+    app.k.Save()
+    
+    # Close project config editor
+    app.type("w", Key.CTRL)
+    
+    app.WaitIdleUI()
+    
+    # Focus seems undefined at that time (FIXME)
+    # Force focussing on "something" so that next shortcut is taken
+    app.type(Key.TAB)
+    
+    app.waitIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitPatternInStdout("Successfully built.", 10)
+    
+    app.k.Connect()
+    
+    app.waitIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitIdleStdout()
+    
+    app.k.Run()
+    
+    # wait 10 seconds
+    return app.waitPatternInStdout("Test OK", 10)
+
+
+run_test(test)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/opcua_browse.sikuli/opcua_browse.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,65 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test, AuxiliaryProcess
+
+def test(app):
+
+    server = AuxiliaryProcess(app, ["/bin/bash",os.path.join(getBundlePath(),"opcua_service.bash")])
+
+    app.doubleClick(["opcua_0", "opcua"])
+
+    app.WaitIdleUI()
+
+    app.click("Server")
+
+    app.WaitIdleUI()
+
+    app.doubleClick("Objects")
+
+    app.WaitIdleUI()
+
+    app.doubleClick("TestObject")
+
+    app.dragNdrop(["TestIn", "Testln","Testin"], "output variables")
+
+    app.wait(1)
+
+    app.dragNdrop("TestOut", "input variables")
+
+    app.wait(3)
+
+    app.k.Clean()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Build()
+
+    app.waitPatternInStdout("Successfully built.", 10)
+
+    app.k.Connect()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Transfer()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Run()
+
+    # wait 10 seconds for 10 patterns
+    res = app.waitPatternInStdout("6.8", 10)
+
+    server.close()
+
+    return res
+
+run_test(test, testproject="opcua_browse")
Binary file tests/ide_tests/opcua_browse.sikuli/opcua_browse_server.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/opcua_browse.sikuli/opcua_service.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+echo "Instant OPC-UA server for test"
+
+# Run server
+exec $BEREMIZPYTHONPATH - << EOF
+
+import sys
+import os
+import time
+import asyncio
+
+from asyncua import Server
+
+async def main():
+    server = Server()
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
+
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
+
+    objects = server.get_objects_node()
+
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
+
+    await server.start()
+    try:
+        while True:
+            await asyncio.sleep(1)
+            inval = await testvar.get_value()
+            print(inval)
+            await testvarout.set_value(inval*2)
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
+EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_browse_encrypted.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,67 @@
+""" This test opens, modifies, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import run_test, AuxiliaryProcess
+
+def test(app):
+
+    server = AuxiliaryProcess(app, ["/bin/bash",os.path.join(getBundlePath(),"opcua_service.bash")])
+
+    server.waitPatternInStdout("CERTS READY", 5)
+
+    app.doubleClick(["opcua_0", "opcua"])
+
+    app.WaitIdleUI()
+
+    app.click("Server")
+
+    app.WaitIdleUI()
+
+    app.doubleClick("Objects")
+
+    app.WaitIdleUI()
+
+    app.doubleClick("TestObject")
+
+    app.dragNdrop(["TestIn", "Testln", "Testin"], "output variables")
+
+    app.wait(1)
+
+    app.dragNdrop("TestOut", "input variables")
+
+    app.wait(3)
+
+    app.k.Clean()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Build()
+
+    app.waitPatternInStdout("Successfully built.", 10)
+
+    app.k.Connect()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Transfer()
+
+    app.waitForChangeAndIdleStdout()
+
+    app.k.Run()
+
+    # wait 10 seconds for 10 patterns
+    res = app.waitPatternInStdout("6.8", 10)
+
+    server.close()
+
+    return res
+
+run_test(test, testproject="opcua_browse_encrypted")
Binary file tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_browse_server.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_service.bash	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+set -x -e
+
+echo "Instant encrypted OPC-UA server for test"
+
+rm -f my_cert.pem my_cert.der my_private_key.pem
+
+yes "" | openssl req -x509 -newkey rsa:2048 -keyout my_private_key.pem -out my_cert.pem \
+        -days 355 -nodes -addext "subjectAltName = URI:urn:example.org:FreeOpcUa:python-opcua"
+openssl x509 -outform der -in my_cert.pem -out my_cert.der
+
+PROJECT_FILES_DIR=$BEREMIZPATH/tests/projects/opcua_browse_encrypted/project_files
+mkdir $PROJECT_FILES_DIR -p
+cp my_cert.der my_private_key.pem $PROJECT_FILES_DIR
+
+echo "CERTS READY"
+
+# Run server
+exec $BEREMIZPYTHONPATH - << EOF
+
+import sys
+import os
+import time
+import asyncio
+
+from asyncua import ua, Server
+from asyncua.server.users import User, UserRole
+
+# Asyncua can't work without (over)simple shared cerificates/privkey.
+# No user is involved in that case, but asyncua needs it.
+# Over permessive User Manager hereafter helps cuting that corner.
+class AllAdminUserManager:
+    def get_user(self, iserver, username=None, password=None, certificate=None):
+        return User(role=UserRole.Admin)
+
+async def main():
+    server = Server(user_manager=AllAdminUserManager())
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
+
+    server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
+    await server.load_certificate("my_cert.der")
+    await server.load_private_key("my_private_key.pem")
+
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
+
+    objects = server.get_objects_node()
+
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
+
+    await server.start()
+
+    try:
+        while True:
+            await asyncio.sleep(1)
+            inval = await testvar.get_value()
+            print(inval)
+            await testvarout.set_value(inval*2)
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
+EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/run_python_exemple.sikuli/run_python_exemple.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,39 @@
+""" This test opens, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import *
+
+def test(app):
+    # Start the app
+    
+    app.k.Clean()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitPatternInStdout("Successfully built.", 10)
+    
+    app.k.Connect()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Run()
+      
+    # wait 10 seconds for 10 Grumpfs
+    return app.waitPatternInStdout("Grumpf", 10, 10)
+    
+run_test(test, exemple="python")
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/sikuliberemiz.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,448 @@
+"Commons definitions for sikuli based beremiz IDE GUI tests"
+
+import os
+import sys
+import subprocess
+import traceback
+import signal
+import re
+from threading import Thread, Event, Lock
+from time import time as timesec
+from xml.sax.saxutils import escape as escape_xml
+
+import sikuli
+
+beremiz_path = os.environ["BEREMIZPATH"]
+python_bin = os.environ.get("BEREMIZPYTHONPATH", "/usr/bin/python")
+opj = os.path.join
+
+tessdata_path = os.environ["TESSDATAPATH"]
+
+ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
+def escape_ansi(line):
+    return ansi_escape.sub('', line)
+
+def escape(txt):
+    return escape_xml(escape_ansi(txt))
+
+class KBDShortcut:
+    """Send shortut to app by calling corresponding methods.
+
+    example:
+        k = KBDShortcut()
+        k.Clean()
+    """
+
+    fkeys = {"Stop":     sikuli.Key.F4,
+             "Run":      sikuli.Key.F5,
+             "Transfer": sikuli.Key.F6,
+             "Connect":  sikuli.Key.F7,
+             "Clean":    sikuli.Key.F9,
+             "Build":    sikuli.Key.F11,
+             "Save":     ("s",sikuli.Key.CTRL),
+             "New":      ("n",sikuli.Key.CTRL),
+             "Address":  ("l",sikuli.Key.CTRL)}  # to reach address bar in GTK's file selector
+
+    def __init__(self, app):
+        self.app = app
+    
+    def __getattr__(self, name):
+        fkey = self.fkeys[name]
+        if type(fkey) != tuple:
+            fkey = (fkey,)
+
+        def PressShortCut():
+            self.app.sikuliapp.focus()
+            sikuli.type(*fkey)
+            self.app.ReportText("Sending " + name + " shortcut")
+
+        return PressShortCut
+
+
+class IDEIdleObserver:
+    "Detects when IDE is idle. This is particularly handy when staring an operation and witing for the en of it."
+
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
+        self.r = sikuli.Region(self.sikuliapp.window())
+        self.targetOffset = self.r.getTopLeft()
+
+        self.idechanged = False
+        
+        # 200 was selected because default 50 was still catching cursor blinking in console
+        # FIXME : remove blinking cursor in console
+        self.r.onChange(200,self._OnIDEWindowChange)
+        self.r.observeInBackground()
+
+    def __del__(self):
+        self.r.stopObserver()
+
+    def _OnIDEWindowChange(self, event):
+        self.idechanged = True
+
+    def WaitIdleUI(self, period=1, timeout=15):
+        """
+        Wait for IDE to stop changing
+        Parameters: 
+            period (int): how many seconds with no change to consider idle
+            timeout (int): how long to wait for idle, in seconds
+        """
+        c = max(timeout/period,1)
+        while c > 0:
+            self.idechanged = False
+            sikuli.wait(period)
+            if not self.idechanged:
+                break
+            c = c - 1
+
+        self.ReportScreenShot("UI is idle" if c != 0 else "UI is not idle")
+
+        if c == 0:
+            raise Exception("Window did not idle before timeout")
+
+ 
+class stdoutIdleObserver:
+    "Detects when IDE's stdout is idle. Can be more reliable than pixel based version (false changes ?)"
+
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
+        self.stdoutchanged = False
+
+        self.event = Event()
+
+        self.pattern = None
+        self.success_event = Event()
+
+        if self.proc is not None:
+            self.thread = Thread(target = self._waitStdoutProc).start()
+
+    def __del__(self):
+        pass  # self.thread.join() ?
+
+    def _waitStdoutProc(self):
+        while True:
+            a = self.proc.stdout.readline()
+            if len(a) == 0 or a is None: 
+                break
+            self.ReportOutput(a)
+            self.event.set()
+            if self.pattern is not None and a.find(self.pattern) >= 0:
+                sys.stdout.write("found pattern in '" + a +"'")
+                self.success_event.set()
+
+    def waitForChangeAndIdleStdout(self, period=2, timeout=15):
+        """
+        Wait for IDE'stdout to start changing
+        Parameters: 
+            timeout (int): how long to wait for change, in seconds
+        """
+        start_time = timesec()
+
+        wait_result = self.event.wait(timeout)
+
+        self.ReportScreenShot("stdout changed" if wait_result else "stdout didn't change")
+
+        if wait_result:
+            self.event.clear()
+        else:
+            raise Exception("Stdout didn't become active before timeout")
+
+        self.waitIdleStdout(period, timeout - (timesec() - start_time))
+
+    def waitIdleStdout(self, period=2, timeout=15):
+        """
+        Wait for IDE'stdout to stop changing
+        Parameters: 
+            period (int): how many seconds with no change to consider idle
+            timeout (int): how long to wait for idle, in seconds
+        """
+        end_time = timesec() + timeout
+        self.event.clear()
+        while timesec() < end_time:
+            if self.event.wait(period):
+                # no timeout -> got event -> not idle -> loop again
+                self.event.clear()
+            else:
+                # timeout -> no event -> idle -> exit
+                self.ReportScreenShot("stdout is idle")
+                return True
+
+        self.ReportScreenShot("stdout did not idle")
+
+        raise Exception("Stdout did not idle before timeout")
+
+    def waitPatternInStdout(self, pattern, timeout, count=1):
+        found = 0
+        self.pattern = pattern
+        end_time = timesec() + timeout
+        while True:
+            remain = end_time - timesec()
+            if remain <= 0 :
+                res = False
+                break
+
+            res = self.success_event.wait(remain)
+            if res:
+                self.success_event.clear()
+                found = found + 1
+                if found >= count:
+                    break
+        self.pattern = None
+        self.ReportScreenShot("found pattern" if res else "pattern not found")
+        return res
+
+class BeremizApp(IDEIdleObserver, stdoutIdleObserver):
+    def __init__(self, projectpath=None, exemple=None, testproject=None):
+        """
+        Starts Beremiz IDE, waits for main window to appear, maximize it.
+
+            Parameters: 
+                projectpath (str): path to project to open
+                exemple (str): path relative to exemples directory
+
+            Returns:
+                Sikuli App class instance
+        """
+        self.ocropts = sikuli.OCR.globalOptions()
+        self.ocropts.dataPath(tessdata_path)
+        
+        # 0 use legacy Tesseract (not so good, but repeatable)
+        # 1 use RNN Tesseract (better but non-repeatable)
+        # 2 use both
+        self.ocropts.oem(0)
+        self.ocropts.psm(12)
+        self.ocropts.smallFont()
+
+        self.imgnum = 0
+        self.starttime = timesec()
+        self.screen = sikuli.Screen()
+
+        self.report = open("report.xhtml", "w")
+        self.report.write("""\
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf-8"/>
+    <meta name="color-scheme" content="light dark"/>
+    <title>Test report</title>
+  </head>
+  <body>
+""")
+
+        command = ["setsid", python_bin, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"]
+
+        if exemple is not None:
+            command.append(opj(beremiz_path,"exemples",exemple))
+        elif projectpath is not None:
+            command.append(projectpath)
+        elif testproject is not None:
+            command.append(opj(beremiz_path,"tests","projects",testproject))
+
+
+        # App class is broken in Sikuli 2.0.5: can't start process with arguments.
+        # 
+        # Workaround : - use subprocess module to spawn IDE process,
+        #              - use wmctrl to find IDE window details and maximize it
+        #              - pass exact window title to App class constructor
+
+        self.ReportText("Launching " + repr(command))
+
+        self.proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0)
+
+        # Window are macthed against process' PID
+        ppid = self.proc.pid
+
+        # Timeout 5s
+        c = 50
+        while c > 0:
+            # equiv to "wmctrl -l -p | grep $pid"
+            try:
+                wlist = filter(lambda l:(len(l)>2 and l[2]==str(ppid)), map(lambda s:s.split(None,4), subprocess.check_output(["wmctrl", "-l", "-p"]).splitlines()))
+            except subprocess.CalledProcessError:
+                wlist = []
+
+            # window with no title only has 4 fields do describe it
+            # beremiz splashcreen has no title
+            # wait until main window is visible
+            if len(wlist) == 1 and len(wlist[0]) == 5:
+                windowID,_zero,wpid,_XID,wtitle = wlist[0] 
+                break
+
+            sikuli.wait(0.1)
+            c = c - 1
+
+        if c == 0:
+            raise Exception("Couldn't find Beremiz window")
+
+        # Maximize window x and y
+        subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"])
+
+        # switchApp creates an App object by finding window by title, is not supposed to spawn a process
+        self.sikuliapp = sikuli.switchApp(wtitle)
+        self.k = KBDShortcut(self)
+
+        IDEIdleObserver.__init__(self)
+        stdoutIdleObserver.__init__(self)
+
+        # stubs for common sikuli calls to allow adding hooks later
+        for name, takes_matches in [
+            ("click", True),
+            ("doubleClick", True),
+            ("type", False),
+            ("rightClick", True),
+            ("wait", False)]:
+            def makeMyMeth(n,m):
+                def myMeth(*args, **kwargs):
+                    self.ReportScreenShot("Begin: " + n + "(" + repr(args) + "," + repr(kwargs) + ")")
+                    if m:
+                        args = map(self.handle_PFRML_arg, args)
+                        kwargs = dict(map(lambda k,v:(k,self.handle_PFRML_arg(v)), kwargs.items()))
+                    try:
+                        getattr(sikuli, n)(*args, **kwargs)
+                    finally:
+                        self.ReportScreenShot("end: " + n + "(" + repr(args) + "," + repr(kwargs) + ")")
+                return myMeth
+            setattr(self, name, makeMyMeth(name,takes_matches))
+
+    def handle_PFRML_arg(self, arg):
+        if type(arg)==list:
+            return self.findBest(*arg)
+        if type(arg)==str and not arg.endswith(".png"):
+            return self.findBest(arg)
+        return arg
+
+    def findBest(self, *args):
+        #match = self.r.findBest(*args)
+        match = None 
+        matches = sikuli.OCR.readWords(self.r) + sikuli.OCR.readLines(self.r)
+        for m in matches:
+            mText = m.getText().encode('ascii', 'ignore')
+            for arg in args:
+                if arg in mText or arg in mText.translate(None, "\"`'|-. "):
+                    if match is None:
+                        match = m
+                    if mText == arg:
+                        match = m
+                        break
+        if match is None:
+            self.ReportText("Not found: " + repr(args) + " OCR content: ")
+            for m in matches:
+                self.ReportText(repr(m) + ": " + m.getText().encode('ascii', 'ignore'))
+            raise Exception("Not Found: " + repr(args))
+        
+        # translate match to screen ref
+        #match.setTargetOffset(self.targetOffset)
+        match.setTopLeft(match.getTopLeft().offset(self.targetOffset))
+
+        self.ReportTextImage("Found for " + repr(args) + ": " +
+            " ".join([repr(match), repr(match.getTarget()), repr(match.getTargetOffset())]),
+            self.screen.capture(match))
+        return match.getTarget()
+
+    def dragNdrop(self, src, dst):
+        self.ReportScreenShot("Drag: (" + repr(src) + ")")
+        sikuli.drag(self.handle_PFRML_arg(src))
+        sikuli.mouseMove(5,0)
+        sikuli.dropAt(self.handle_PFRML_arg(dst))
+        self.ReportScreenShot("Drop: (" + repr(dst) + ")")
+
+    def close(self):
+
+        self.ReportScreenShot("Close app")
+        os.kill(self.proc.pid, signal.SIGKILL)
+        #self.sikuliapp.close()
+        #self.sikuliapp = None
+
+        self.report.write("""
+  </body>
+</html>""")
+        self.report.close()
+
+    def __del__(self):
+        if self.sikuliapp is not None:
+            self.sikuliapp.close()
+        IDEIdleObserver.__del__(self)
+        stdoutIdleObserver.__del__(self)
+
+    def ReportScreenShot(self, msg):
+        cap = self.screen.capture(self.r)
+        self.ReportTextImage(msg, cap)
+
+    def ReportTextImage(self, msg, img):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        fname = "capture"+str(self.imgnum)+".png"
+        sys.stdout.write(elapsed + " [" + fname + "] " + msg + "\n")
+        img.save(".", fname)
+        self.imgnum = self.imgnum + 1
+        self.report.write( "<p>" + escape(elapsed + msg) + "<br/><img src=\""+ fname + "\"/>" + "</p>")
+
+    def ReportText(self, text):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        #res = u"<p><![CDATA[" + elapsed + text + "]]></p>"
+        sys.stdout.write(elapsed + text + "\n")
+        res = u"<p>" + escape(elapsed + text) + "</p>"
+        self.report.write(res)
+
+    def ReportOutput(self, text):
+        elapsed = "%.3fs: "%(timesec() - self.starttime)
+        sys.stdout.write(elapsed + text)
+        self.report.write("<pre>" + escape(elapsed + text) + "</pre>")
+
+
+class AuxiliaryProcess(stdoutIdleObserver):
+    def __init__(self, beremiz_app, command):
+        self.app = beremiz_app
+        self.app.ReportText("Launching process " + repr(command))
+        self.proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0)
+        self.app.ReportText("Launched process " + repr(command) + " PID: " + str(self.proc.pid))
+        stdoutIdleObserver.__init__(self)
+
+    def close(self):
+        if self.proc is not None:
+            proc = self.proc
+            self.proc = None
+            # self.proc.stdout.close()
+            self.app.ReportText("Kill process PID: " + str(proc.pid))
+            try:
+                os.kill(proc.pid, signal.SIGTERM)
+            except OSError:
+                pass
+            proc.wait()
+            # self.thread.join()
+
+    def ReportOutput(self, text):
+        self.app.ReportOutput("Aux: "+text)
+
+    def ReportScreenShot(self, msg):
+        self.app.ReportOutput("Aux: "+msg)
+
+    def __del__(self):
+        self.close()
+
+def run_test(func, *args, **kwargs):
+    app = BeremizApp(*args, **kwargs)
+    try:
+        success = func(app)
+    except:
+        # sadly, sys.excepthook is broken in sikuli/jython 
+        # purpose of this run_test function is to work around it.
+        # and catch exception cleanly anyhow
+        e_type, e_value, e_traceback = sys.exc_info()
+        err_msg = "\n".join(traceback.format_exception(e_type, e_value, e_traceback))
+        app.ReportOutput(err_msg)
+        success = False
+
+    app.close()
+
+    if success:
+        sikuli.exit(0)
+    else:
+        sikuli.exit(1)
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/svghmi_basic.sikuli/svghmi_basic.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,45 @@
+""" This test opens, builds and runs exemple project named "python".
+Test succeeds if runtime's stdout behaves as expected
+"""
+
+import os
+import time
+
+# allow module import from current test directory's parent
+addImportPath(os.path.dirname(getBundlePath()))
+
+# common test definitions module
+from sikuliberemiz import *
+
+def test(app):
+    # Start the app
+    
+    app.k.Clean()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Build()
+    
+    app.waitPatternInStdout("Successfully built.", 10)
+    
+    app.k.Connect()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Transfer()
+    
+    app.waitForChangeAndIdleStdout()
+    
+    app.k.Run()
+
+    app.waitForChangeAndIdleStdout()
+    # app.WaitIdleUI()
+
+    app.ocropts.fontSize(20)
+    #app.ocropts.textHeight(25)
+    app.click("OFF")
+
+    # wait 10 seconds for 10 Grumpfs
+    return app.waitPatternInStdout("ALL GREEN LIGHTS", 10)
+    
+run_test(test, testproject="svghmi_basic")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ide_tests/wx_widgets.pytest/test_CustomIntCtrl.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,140 @@
+#!/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) 2017: Andrey Skvortsov
+#
+# 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.
+
+
+
+
+import unittest
+import time
+
+import wx
+import conftest
+import controls.CustomIntCtrl
+
+
+class TestCustomIntCtrl(unittest.TestCase):
+    def setUp(self):
+        self.app = wx.App()
+        self.frame = wx.Frame(None)
+
+    def tearDown(self):
+        self.frame.Destroy()
+        wx.CallAfter(wx.Exit)
+        self.app.MainLoop()
+
+    def testMaxLimit(self):
+        """Test working upper bound"""
+        self.AddControls()
+        self.int_ctrl.SetValue(self.max_val + 100)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+        self.assertEqual(self.int_ctrl.GetValue(), self.max_val)
+
+    def testMinLimit(self):
+        """Test working lower bound"""
+        self.AddControls()
+        self.int_ctrl.SetValue(self.min_val - 100)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), self.min_val)
+
+    def testCorrectValue(self):
+        """Test case if no limiting is necessary"""
+        self.AddControls()
+        val = (self.max_val + self.min_val) // 2
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), val)
+
+    def testEventBinding(self):
+        """Test event sending after edit and bound checks are done"""
+        self.AddControls()
+        self.event_happend = False
+
+        def EventHandler(event):
+            self.event_happend = True
+            event.Skip()
+
+        self.int_ctrl.Bind(controls.CustomIntCtrl.EVT_CUSTOM_INT, EventHandler)
+
+        val = (self.max_val + self.min_val) // 2
+
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+        self.txt_ctrl.SetFocus()
+
+        self.ProcessEvents()
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(self.int_ctrl.GetValue(), val)
+        self.assertTrue(self.event_happend)
+
+    def testLongNumbers(self):
+        """Test support of long integer"""
+        self.AddControls()
+        val = 40000000000
+        self.int_ctrl.SetMax(val)
+        self.int_ctrl.SetValue(val)
+        self.ProcessEvents()
+
+        self.txt_ctrl.SetFocus()
+        self.ProcessEvents()
+
+        self.assertEqual(val, val)
+
+    def ProcessEvents(self):
+        for dummy in range(0, 10):
+            wx.Yield()
+            time.sleep(0.01)
+
+    def AddControls(self):
+        vs = wx.BoxSizer(wx.VERTICAL)
+        self.int_ctrl = controls.CustomIntCtrl(self.frame)
+        self.txt_ctrl = wx.TextCtrl(self.frame)
+        vs.Add(self.int_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
+        vs.Add(self.txt_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
+        self.frame.SetSizer(vs)
+        vs.Fit(self.frame)
+        self.frame.Show()
+        self.frame.Raise()
+
+        self.min_val = 50
+        self.max_val = 100
+        self.int_ctrl.SetBounds(self.min_val, self.max_val)
+        self.ProcessEvents()
+
+
+if __name__ == '__main__':
+    conftest.init_environment()
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/iec61131_lang_test/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType>
+    <Linux CFLAGS="-DBEREMIZ_TEST_CYCLES=1000"/>
+  </TargetType>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/iec61131_lang_test/gen_conversion.py	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,37 @@
+
+# Naive code generator for type convesion function XX_TO_YY testing
+
+
+types = [
+    ("BOOL", "TRUE"),
+    ("SINT", "42"),
+    ("USINT", "42"),
+    ("BYTE", "42"),
+    ("STRING", "'42'"),
+    ("INT", "42"),
+    ("UINT", "42"),
+    ("WORD", "42"),
+    ("DINT", "42"),
+    ("UDINT", "42"),
+    ("DWORD", "42"),
+    ("LINT", "42"),
+    ("ULINT", "42"),
+    ("LWORD", "42"),
+    ("REAL", "42.0"),
+    ("LREAL", "42.0"),
+    #("TIME", "42"),
+    #("TOD", "42"),
+    #("DATE", "42"),
+    #("DT", "42"),
+]
+
+for tsrc, src_literal in types:
+    for tdest, dest_literal in types:
+        if tsrc == tdest: continue
+        s = f"""
+RESULT := '{tsrc}_TO_{tdest}'; 
+IF {tsrc}_TO_{tdest}({tsrc}#{src_literal}) <> {tdest}#{dest_literal} THEN RETURN; END_IF;
+"""
+        print(s)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/iec61131_lang_test/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2183 @@
+<?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="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2023-03-11T14:33:27"/>
+  <contentHeader name="Unnamed" modificationDateTime="2023-03-27T09:12:21">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="8" y="8"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="CONVERSION_TEST" pouType="functionBlock">
+        <interface>
+          <outputVars>
+            <variable name="RESULT">
+              <type>
+                <string/>
+              </type>
+            </variable>
+          </outputVars>
+        </interface>
+        <body>
+          <ST>
+            <xhtml:p><![CDATA[
+(* Code generated by gen_vonversion.py, 
+   fixed by hand for corner cases *)
+
+(* 240 conversions from literals tested *)
+
+(* Tested types
+   BOOL, SINT, USINT, BYTE, STRING, INT, UINT,
+   WORD, DINT, UDINT, DWORD, LINT, ULINT, LWORD, 
+   REAL, LREAL *)
+
+(* TODO: test TIME, TOD, DATE and DT *)
+(* TODO: similar test with values that tiggers overflow *)
+
+RESULT := 'BOOL_TO_SINT'; 
+IF BOOL_TO_SINT(BOOL#TRUE) <> SINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_USINT'; 
+IF BOOL_TO_USINT(BOOL#TRUE) <> USINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_BYTE'; 
+IF BOOL_TO_BYTE(BOOL#TRUE) <> BYTE#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_STRING'; 
+IF BOOL_TO_STRING(BOOL#TRUE) <> 'TRUE' THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_INT'; 
+IF BOOL_TO_INT(BOOL#TRUE) <> INT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_UINT'; 
+IF BOOL_TO_UINT(BOOL#TRUE) <> UINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_WORD'; 
+IF BOOL_TO_WORD(BOOL#TRUE) <> WORD#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_DINT'; 
+IF BOOL_TO_DINT(BOOL#TRUE) <> DINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_UDINT'; 
+IF BOOL_TO_UDINT(BOOL#TRUE) <> UDINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_DWORD'; 
+IF BOOL_TO_DWORD(BOOL#TRUE) <> DWORD#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_LINT'; 
+IF BOOL_TO_LINT(BOOL#TRUE) <> LINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_ULINT'; 
+IF BOOL_TO_ULINT(BOOL#TRUE) <> ULINT#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_LWORD'; 
+IF BOOL_TO_LWORD(BOOL#TRUE) <> LWORD#1 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_REAL'; 
+IF BOOL_TO_REAL(BOOL#TRUE) <> REAL#1.0 THEN RETURN; END_IF;
+
+
+RESULT := 'BOOL_TO_LREAL'; 
+IF BOOL_TO_LREAL(BOOL#TRUE) <> LREAL#1.0 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_BOOL'; 
+IF SINT_TO_BOOL(SINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_USINT'; 
+IF SINT_TO_USINT(SINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_BYTE'; 
+IF SINT_TO_BYTE(SINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_STRING'; 
+IF SINT_TO_STRING(SINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_INT'; 
+IF SINT_TO_INT(SINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_UINT'; 
+IF SINT_TO_UINT(SINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_WORD'; 
+IF SINT_TO_WORD(SINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_DINT'; 
+IF SINT_TO_DINT(SINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_UDINT'; 
+IF SINT_TO_UDINT(SINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_DWORD'; 
+IF SINT_TO_DWORD(SINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_LINT'; 
+IF SINT_TO_LINT(SINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_ULINT'; 
+IF SINT_TO_ULINT(SINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_LWORD'; 
+IF SINT_TO_LWORD(SINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_REAL'; 
+IF SINT_TO_REAL(SINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'SINT_TO_LREAL'; 
+IF SINT_TO_LREAL(SINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_BOOL'; 
+IF USINT_TO_BOOL(USINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_SINT'; 
+IF USINT_TO_SINT(USINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_BYTE'; 
+IF USINT_TO_BYTE(USINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_STRING'; 
+IF USINT_TO_STRING(USINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_INT'; 
+IF USINT_TO_INT(USINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_UINT'; 
+IF USINT_TO_UINT(USINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_WORD'; 
+IF USINT_TO_WORD(USINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_DINT'; 
+IF USINT_TO_DINT(USINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_UDINT'; 
+IF USINT_TO_UDINT(USINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_DWORD'; 
+IF USINT_TO_DWORD(USINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_LINT'; 
+IF USINT_TO_LINT(USINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_ULINT'; 
+IF USINT_TO_ULINT(USINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_LWORD'; 
+IF USINT_TO_LWORD(USINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_REAL'; 
+IF USINT_TO_REAL(USINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'USINT_TO_LREAL'; 
+IF USINT_TO_LREAL(USINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_BOOL'; 
+IF BYTE_TO_BOOL(BYTE#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_SINT'; 
+IF BYTE_TO_SINT(BYTE#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_USINT'; 
+IF BYTE_TO_USINT(BYTE#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_STRING'; 
+IF BYTE_TO_STRING(BYTE#42) <> '16#2a' THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_INT'; 
+IF BYTE_TO_INT(BYTE#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_UINT'; 
+IF BYTE_TO_UINT(BYTE#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_WORD'; 
+IF BYTE_TO_WORD(BYTE#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_DINT'; 
+IF BYTE_TO_DINT(BYTE#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_UDINT'; 
+IF BYTE_TO_UDINT(BYTE#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_DWORD'; 
+IF BYTE_TO_DWORD(BYTE#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_LINT'; 
+IF BYTE_TO_LINT(BYTE#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_ULINT'; 
+IF BYTE_TO_ULINT(BYTE#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_LWORD'; 
+IF BYTE_TO_LWORD(BYTE#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_REAL'; 
+IF BYTE_TO_REAL(BYTE#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'BYTE_TO_LREAL'; 
+IF BYTE_TO_LREAL(BYTE#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_BOOL'; 
+IF STRING_TO_BOOL('42') <> BOOL#FALSE THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_SINT'; 
+IF STRING_TO_SINT('42') <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_USINT'; 
+IF STRING_TO_USINT('42') <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_BYTE'; 
+IF STRING_TO_BYTE('42') <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_INT'; 
+IF STRING_TO_INT('42') <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_UINT'; 
+IF STRING_TO_UINT('42') <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_WORD'; 
+IF STRING_TO_WORD('42') <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_DINT'; 
+IF STRING_TO_DINT('42') <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_UDINT'; 
+IF STRING_TO_UDINT('42') <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_DWORD'; 
+IF STRING_TO_DWORD('42') <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_LINT'; 
+IF STRING_TO_LINT('42') <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_ULINT'; 
+IF STRING_TO_ULINT('42') <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_LWORD'; 
+IF STRING_TO_LWORD('42') <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_REAL'; 
+IF STRING_TO_REAL('42') <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'STRING_TO_LREAL'; 
+IF STRING_TO_LREAL('42') <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_BOOL'; 
+IF INT_TO_BOOL(INT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_SINT'; 
+IF INT_TO_SINT(INT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_USINT'; 
+IF INT_TO_USINT(INT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_BYTE'; 
+IF INT_TO_BYTE(INT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_STRING'; 
+IF INT_TO_STRING(INT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_UINT'; 
+IF INT_TO_UINT(INT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_WORD'; 
+IF INT_TO_WORD(INT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_DINT'; 
+IF INT_TO_DINT(INT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_UDINT'; 
+IF INT_TO_UDINT(INT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_DWORD'; 
+IF INT_TO_DWORD(INT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_LINT'; 
+IF INT_TO_LINT(INT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_ULINT'; 
+IF INT_TO_ULINT(INT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_LWORD'; 
+IF INT_TO_LWORD(INT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_REAL'; 
+IF INT_TO_REAL(INT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'INT_TO_LREAL'; 
+IF INT_TO_LREAL(INT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_BOOL'; 
+IF UINT_TO_BOOL(UINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_SINT'; 
+IF UINT_TO_SINT(UINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_USINT'; 
+IF UINT_TO_USINT(UINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_BYTE'; 
+IF UINT_TO_BYTE(UINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_STRING'; 
+IF UINT_TO_STRING(UINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_INT'; 
+IF UINT_TO_INT(UINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_WORD'; 
+IF UINT_TO_WORD(UINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_DINT'; 
+IF UINT_TO_DINT(UINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_UDINT'; 
+IF UINT_TO_UDINT(UINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_DWORD'; 
+IF UINT_TO_DWORD(UINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_LINT'; 
+IF UINT_TO_LINT(UINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_ULINT'; 
+IF UINT_TO_ULINT(UINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_LWORD'; 
+IF UINT_TO_LWORD(UINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_REAL'; 
+IF UINT_TO_REAL(UINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'UINT_TO_LREAL'; 
+IF UINT_TO_LREAL(UINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_BOOL'; 
+IF WORD_TO_BOOL(WORD#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_SINT'; 
+IF WORD_TO_SINT(WORD#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_USINT'; 
+IF WORD_TO_USINT(WORD#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_BYTE'; 
+IF WORD_TO_BYTE(WORD#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_STRING'; 
+IF WORD_TO_STRING(WORD#42) <> '16#2a' THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_INT'; 
+IF WORD_TO_INT(WORD#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_UINT'; 
+IF WORD_TO_UINT(WORD#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_DINT'; 
+IF WORD_TO_DINT(WORD#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_UDINT'; 
+IF WORD_TO_UDINT(WORD#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_DWORD'; 
+IF WORD_TO_DWORD(WORD#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_LINT'; 
+IF WORD_TO_LINT(WORD#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_ULINT'; 
+IF WORD_TO_ULINT(WORD#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_LWORD'; 
+IF WORD_TO_LWORD(WORD#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_REAL'; 
+IF WORD_TO_REAL(WORD#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'WORD_TO_LREAL'; 
+IF WORD_TO_LREAL(WORD#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_BOOL'; 
+IF DINT_TO_BOOL(DINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_SINT'; 
+IF DINT_TO_SINT(DINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_USINT'; 
+IF DINT_TO_USINT(DINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_BYTE'; 
+IF DINT_TO_BYTE(DINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_STRING'; 
+IF DINT_TO_STRING(DINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_INT'; 
+IF DINT_TO_INT(DINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_UINT'; 
+IF DINT_TO_UINT(DINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_WORD'; 
+IF DINT_TO_WORD(DINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_UDINT'; 
+IF DINT_TO_UDINT(DINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_DWORD'; 
+IF DINT_TO_DWORD(DINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_LINT'; 
+IF DINT_TO_LINT(DINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_ULINT'; 
+IF DINT_TO_ULINT(DINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_LWORD'; 
+IF DINT_TO_LWORD(DINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_REAL'; 
+IF DINT_TO_REAL(DINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'DINT_TO_LREAL'; 
+IF DINT_TO_LREAL(DINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_BOOL'; 
+IF UDINT_TO_BOOL(UDINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_SINT'; 
+IF UDINT_TO_SINT(UDINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_USINT'; 
+IF UDINT_TO_USINT(UDINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_BYTE'; 
+IF UDINT_TO_BYTE(UDINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_STRING'; 
+IF UDINT_TO_STRING(UDINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_INT'; 
+IF UDINT_TO_INT(UDINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_UINT'; 
+IF UDINT_TO_UINT(UDINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_WORD'; 
+IF UDINT_TO_WORD(UDINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_DINT'; 
+IF UDINT_TO_DINT(UDINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_DWORD'; 
+IF UDINT_TO_DWORD(UDINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_LINT'; 
+IF UDINT_TO_LINT(UDINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_ULINT'; 
+IF UDINT_TO_ULINT(UDINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_LWORD'; 
+IF UDINT_TO_LWORD(UDINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_REAL'; 
+IF UDINT_TO_REAL(UDINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'UDINT_TO_LREAL'; 
+IF UDINT_TO_LREAL(UDINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_BOOL'; 
+IF DWORD_TO_BOOL(DWORD#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_SINT'; 
+IF DWORD_TO_SINT(DWORD#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_USINT'; 
+IF DWORD_TO_USINT(DWORD#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_BYTE'; 
+IF DWORD_TO_BYTE(DWORD#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_STRING'; 
+IF DWORD_TO_STRING(DWORD#42) <> '16#2a' THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_INT'; 
+IF DWORD_TO_INT(DWORD#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_UINT'; 
+IF DWORD_TO_UINT(DWORD#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_WORD'; 
+IF DWORD_TO_WORD(DWORD#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_DINT'; 
+IF DWORD_TO_DINT(DWORD#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_UDINT'; 
+IF DWORD_TO_UDINT(DWORD#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_LINT'; 
+IF DWORD_TO_LINT(DWORD#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_ULINT'; 
+IF DWORD_TO_ULINT(DWORD#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_LWORD'; 
+IF DWORD_TO_LWORD(DWORD#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_REAL'; 
+IF DWORD_TO_REAL(DWORD#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'DWORD_TO_LREAL'; 
+IF DWORD_TO_LREAL(DWORD#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_BOOL'; 
+IF LINT_TO_BOOL(LINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_SINT'; 
+IF LINT_TO_SINT(LINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_USINT'; 
+IF LINT_TO_USINT(LINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_BYTE'; 
+IF LINT_TO_BYTE(LINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_STRING'; 
+IF LINT_TO_STRING(LINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_INT'; 
+IF LINT_TO_INT(LINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_UINT'; 
+IF LINT_TO_UINT(LINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_WORD'; 
+IF LINT_TO_WORD(LINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_DINT'; 
+IF LINT_TO_DINT(LINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_UDINT'; 
+IF LINT_TO_UDINT(LINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_DWORD'; 
+IF LINT_TO_DWORD(LINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_ULINT'; 
+IF LINT_TO_ULINT(LINT#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_LWORD'; 
+IF LINT_TO_LWORD(LINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_REAL'; 
+IF LINT_TO_REAL(LINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'LINT_TO_LREAL'; 
+IF LINT_TO_LREAL(LINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_BOOL'; 
+IF ULINT_TO_BOOL(ULINT#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_SINT'; 
+IF ULINT_TO_SINT(ULINT#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_USINT'; 
+IF ULINT_TO_USINT(ULINT#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_BYTE'; 
+IF ULINT_TO_BYTE(ULINT#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_STRING'; 
+IF ULINT_TO_STRING(ULINT#42) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_INT'; 
+IF ULINT_TO_INT(ULINT#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_UINT'; 
+IF ULINT_TO_UINT(ULINT#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_WORD'; 
+IF ULINT_TO_WORD(ULINT#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_DINT'; 
+IF ULINT_TO_DINT(ULINT#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_UDINT'; 
+IF ULINT_TO_UDINT(ULINT#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_DWORD'; 
+IF ULINT_TO_DWORD(ULINT#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_LINT'; 
+IF ULINT_TO_LINT(ULINT#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_LWORD'; 
+IF ULINT_TO_LWORD(ULINT#42) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_REAL'; 
+IF ULINT_TO_REAL(ULINT#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'ULINT_TO_LREAL'; 
+IF ULINT_TO_LREAL(ULINT#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_BOOL'; 
+IF LWORD_TO_BOOL(LWORD#42) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_SINT'; 
+IF LWORD_TO_SINT(LWORD#42) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_USINT'; 
+IF LWORD_TO_USINT(LWORD#42) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_BYTE'; 
+IF LWORD_TO_BYTE(LWORD#42) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_STRING'; 
+IF LWORD_TO_STRING(LWORD#42) <> '16#2a' THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_INT'; 
+IF LWORD_TO_INT(LWORD#42) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_UINT'; 
+IF LWORD_TO_UINT(LWORD#42) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_WORD'; 
+IF LWORD_TO_WORD(LWORD#42) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_DINT'; 
+IF LWORD_TO_DINT(LWORD#42) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_UDINT'; 
+IF LWORD_TO_UDINT(LWORD#42) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_DWORD'; 
+IF LWORD_TO_DWORD(LWORD#42) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_LINT'; 
+IF LWORD_TO_LINT(LWORD#42) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_ULINT'; 
+IF LWORD_TO_ULINT(LWORD#42) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_REAL'; 
+IF LWORD_TO_REAL(LWORD#42) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'LWORD_TO_LREAL'; 
+IF LWORD_TO_LREAL(LWORD#42) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_BOOL'; 
+IF REAL_TO_BOOL(REAL#42.0) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_SINT'; 
+IF REAL_TO_SINT(REAL#42.0) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_USINT'; 
+IF REAL_TO_USINT(REAL#42.0) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_BYTE'; 
+IF REAL_TO_BYTE(REAL#42.0) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_STRING'; 
+IF REAL_TO_STRING(REAL#42.0) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_INT'; 
+IF REAL_TO_INT(REAL#42.0) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_UINT'; 
+IF REAL_TO_UINT(REAL#42.0) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_WORD'; 
+IF REAL_TO_WORD(REAL#42.0) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_DINT'; 
+IF REAL_TO_DINT(REAL#42.0) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_UDINT'; 
+IF REAL_TO_UDINT(REAL#42.0) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_DWORD'; 
+IF REAL_TO_DWORD(REAL#42.0) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_LINT'; 
+IF REAL_TO_LINT(REAL#42.0) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_ULINT'; 
+IF REAL_TO_ULINT(REAL#42.0) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_LWORD'; 
+IF REAL_TO_LWORD(REAL#42.0) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'REAL_TO_LREAL'; 
+IF REAL_TO_LREAL(REAL#42.0) <> LREAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_BOOL'; 
+IF LREAL_TO_BOOL(LREAL#42.0) <> BOOL#TRUE THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_SINT'; 
+IF LREAL_TO_SINT(LREAL#42.0) <> SINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_USINT'; 
+IF LREAL_TO_USINT(LREAL#42.0) <> USINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_BYTE'; 
+IF LREAL_TO_BYTE(LREAL#42.0) <> BYTE#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_STRING'; 
+IF LREAL_TO_STRING(LREAL#42.0) <> '42' THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_INT'; 
+IF LREAL_TO_INT(LREAL#42.0) <> INT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_UINT'; 
+IF LREAL_TO_UINT(LREAL#42.0) <> UINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_WORD'; 
+IF LREAL_TO_WORD(LREAL#42.0) <> WORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_DINT'; 
+IF LREAL_TO_DINT(LREAL#42.0) <> DINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_UDINT'; 
+IF LREAL_TO_UDINT(LREAL#42.0) <> UDINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_DWORD'; 
+IF LREAL_TO_DWORD(LREAL#42.0) <> DWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_LINT'; 
+IF LREAL_TO_LINT(LREAL#42.0) <> LINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_ULINT'; 
+IF LREAL_TO_ULINT(LREAL#42.0) <> ULINT#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_LWORD'; 
+IF LREAL_TO_LWORD(LREAL#42.0) <> LWORD#42 THEN RETURN; END_IF;
+
+
+RESULT := 'LREAL_TO_REAL'; 
+IF LREAL_TO_REAL(LREAL#42.0) <> REAL#42.0 THEN RETURN; END_IF;
+
+
+RESULT := 'OK'; 
+
+]]></xhtml:p>
+          </ST>
+        </body>
+      </pou>
+      <pou name="main_test" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="RUN">
+              <type>
+                <BOOL/>
+              </type>
+              <initialValue>
+                <simpleValue value="TRUE"/>
+              </initialValue>
+            </variable>
+            <variable name="RESULT">
+              <type>
+                <string/>
+              </type>
+            </variable>
+            <variable name="CONVERSION_TEST0">
+              <type>
+                <derived name="CONVERSION_TEST"/>
+              </type>
+            </variable>
+            <variable name="R_TRIG0">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+            <variable name="LOGGER0">
+              <type>
+                <derived name="LOGGER"/>
+              </type>
+            </variable>
+            <variable name="TEMPO_TEST0">
+              <type>
+                <derived name="TEMPO_TEST"/>
+              </type>
+            </variable>
+            <variable name="LOGGER1">
+              <type>
+                <derived name="LOGGER"/>
+              </type>
+            </variable>
+            <variable name="SR0">
+              <type>
+                <derived name="SR"/>
+              </type>
+            </variable>
+            <variable name="SR1">
+              <type>
+                <derived name="SR"/>
+              </type>
+            </variable>
+            <variable name="LOGGER2">
+              <type>
+                <derived name="LOGGER"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <block localId="4" typeName="CONVERSION_TEST" instanceName="CONVERSION_TEST0" executionOrderId="0" height="72" width="136">
+              <position x="320" y="280"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="5" formalParameter="Q">
+                      <position x="320" y="312"/>
+                      <position x="264" y="312"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="136" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="136" y="56"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="R_TRIG" instanceName="R_TRIG0" executionOrderId="0" height="48" width="64">
+              <position x="200" y="280"/>
+              <inputVariables>
+                <variable formalParameter="CLK">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="6">
+                      <position x="200" y="312"/>
+                      <position x="168" y="312"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="6" executionOrderId="0" height="32" width="48" negated="false">
+              <position x="120" y="296"/>
+              <connectionPointOut>
+                <relPosition x="48" y="16"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <block localId="8" typeName="LOGGER" instanceName="LOGGER0" executionOrderId="0" height="112" width="64">
+              <position x="848" y="304"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="4" formalParameter="ENO">
+                      <position x="848" y="336"/>
+                      <position x="760" y="336"/>
+                      <position x="760" y="312"/>
+                      <position x="456" y="312"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="MSG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="11" formalParameter="OUT">
+                      <position x="848" y="368"/>
+                      <position x="736" y="368"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="LEVEL">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="10">
+                      <position x="848" y="400"/>
+                      <position x="816" y="400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables/>
+            </block>
+            <inVariable localId="9" executionOrderId="0" height="32" width="128" negated="false">
+              <position x="512" y="352"/>
+              <connectionPointOut>
+                <relPosition x="128" y="16"/>
+              </connectionPointOut>
+              <expression>'CONVERSION: '</expression>
+            </inVariable>
+            <inVariable localId="10" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="760" y="384"/>
+              <connectionPointOut>
+                <relPosition x="56" y="16"/>
+              </connectionPointOut>
+              <expression>DEBUG</expression>
+            </inVariable>
+            <block localId="11" typeName="CONCAT" executionOrderId="0" height="64" width="64">
+              <position x="672" y="336"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="9">
+                      <position x="672" y="368"/>
+                      <position x="640" y="368"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="4" formalParameter="RESULT">
+                      <position x="672" y="392"/>
+                      <position x="480" y="392"/>
+                      <position x="480" y="336"/>
+                      <position x="456" y="336"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <comment localId="12" height="40" width="248">
+              <position x="88" y="216"/>
+              <content>
+                <xhtml:p><![CDATA[Execute only on first cycle]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="1" height="40" width="248">
+              <position x="576" y="216"/>
+              <content>
+                <xhtml:p><![CDATA[Log when just executed]]></xhtml:p>
+              </content>
+            </comment>
+            <block localId="13" typeName="TEMPO_TEST" instanceName="TEMPO_TEST0" executionOrderId="0" height="64" width="96">
+              <position x="336" y="600"/>
+              <inputVariables>
+                <variable formalParameter="RUN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="16">
+                      <position x="336" y="632"/>
+                      <position x="248" y="632"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="VALID">
+                  <connectionPointOut>
+                    <relPosition x="96" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="96" y="56"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="2" typeName="LOGGER" instanceName="LOGGER1" executionOrderId="0" height="112" width="64">
+              <position x="824" y="624"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="13" formalParameter="VALID">
+                      <position x="824" y="656"/>
+                      <position x="736" y="656"/>
+                      <position x="736" y="632"/>
+                      <position x="432" y="632"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="MSG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="14" formalParameter="OUT">
+                      <position x="824" y="688"/>
+                      <position x="712" y="688"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="LEVEL">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="7">
+                      <position x="824" y="720"/>
+                      <position x="792" y="720"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables/>
+            </block>
+            <inVariable localId="3" executionOrderId="0" height="32" width="128" negated="false">
+              <position x="488" y="672"/>
+              <connectionPointOut>
+                <relPosition x="128" y="16"/>
+              </connectionPointOut>
+              <expression>'TEMPO: '</expression>
+            </inVariable>
+            <inVariable localId="7" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="736" y="704"/>
+              <connectionPointOut>
+                <relPosition x="56" y="16"/>
+              </connectionPointOut>
+              <expression>DEBUG</expression>
+            </inVariable>
+            <block localId="14" typeName="CONCAT" executionOrderId="0" height="64" width="64">
+              <position x="648" y="656"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="3">
+                      <position x="648" y="688"/>
+                      <position x="616" y="688"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="13" formalParameter="RESULT">
+                      <position x="648" y="712"/>
+                      <position x="464" y="712"/>
+                      <position x="464" y="656"/>
+                      <position x="432" y="656"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <comment localId="15" height="40" width="248">
+              <position x="536" y="536"/>
+              <content>
+                <xhtml:p><![CDATA[Log when valid]]></xhtml:p>
+              </content>
+            </comment>
+            <inVariable localId="16" executionOrderId="0" height="32" width="48" negated="false">
+              <position x="200" y="616"/>
+              <connectionPointOut>
+                <relPosition x="48" y="16"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <block localId="17" typeName="AND" executionOrderId="0" height="64" width="64">
+              <position x="1016" y="568"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="13" formalParameter="VALID">
+                      <position x="1016" y="600"/>
+                      <position x="736" y="600"/>
+                      <position x="736" y="632"/>
+                      <position x="432" y="632"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="18" formalParameter="OUT">
+                      <position x="1016" y="624"/>
+                      <position x="990" y="624"/>
+                      <position x="990" y="752"/>
+                      <position x="976" y="752"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="18" typeName="EQ" executionOrderId="0" height="64" width="64">
+              <position x="912" y="720"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="13" formalParameter="RESULT">
+                      <position x="912" y="752"/>
+                      <position x="464" y="752"/>
+                      <position x="464" y="656"/>
+                      <position x="432" y="656"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="19">
+                      <position x="912" y="776"/>
+                      <position x="864" y="776"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="19" executionOrderId="0" height="32" width="48" negated="false">
+              <position x="816" y="760"/>
+              <connectionPointOut>
+                <relPosition x="48" y="16"/>
+              </connectionPointOut>
+              <expression>'OK'</expression>
+            </inVariable>
+            <block localId="20" typeName="SR" instanceName="SR0" executionOrderId="0" height="64" width="48">
+              <position x="1120" y="568"/>
+              <inputVariables>
+                <variable formalParameter="S1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="17" formalParameter="OUT">
+                      <position x="1120" y="600"/>
+                      <position x="1080" y="600"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="R">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q1">
+                  <connectionPointOut>
+                    <relPosition x="48" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="21" typeName="AND" executionOrderId="0" height="64" width="64">
+              <position x="1296" y="496"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="23" formalParameter="Q1">
+                      <position x="1296" y="528"/>
+                      <position x="1256" y="528"/>
+                      <position x="1256" y="272"/>
+                      <position x="1192" y="272"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="20" formalParameter="Q1">
+                      <position x="1296" y="552"/>
+                      <position x="1232" y="552"/>
+                      <position x="1232" y="600"/>
+                      <position x="1168" y="600"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="22" typeName="AND" executionOrderId="0" height="64" width="64">
+              <position x="1040" y="240"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="4" formalParameter="ENO">
+                      <position x="1040" y="272"/>
+                      <position x="760" y="272"/>
+                      <position x="760" y="312"/>
+                      <position x="456" y="312"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="24" formalParameter="OUT">
+                      <position x="1040" y="296"/>
+                      <position x="1014" y="296"/>
+                      <position x="1014" y="424"/>
+                      <position x="1000" y="424"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="23" typeName="SR" instanceName="SR1" executionOrderId="0" height="64" width="48">
+              <position x="1144" y="240"/>
+              <inputVariables>
+                <variable formalParameter="S1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="22" formalParameter="OUT">
+                      <position x="1144" y="272"/>
+                      <position x="1104" y="272"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="R">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q1">
+                  <connectionPointOut>
+                    <relPosition x="48" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="24" typeName="EQ" executionOrderId="0" height="64" width="64">
+              <position x="936" y="392"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="4" formalParameter="RESULT">
+                      <position x="936" y="424"/>
+                      <position x="480" y="424"/>
+                      <position x="480" y="336"/>
+                      <position x="456" y="336"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="25">
+                      <position x="936" y="448"/>
+                      <position x="888" y="448"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="25" executionOrderId="0" height="32" width="48" negated="false">
+              <position x="840" y="432"/>
+              <connectionPointOut>
+                <relPosition x="48" y="16"/>
+              </connectionPointOut>
+              <expression>'OK'</expression>
+            </inVariable>
+            <inVariable localId="26" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="1464" y="576"/>
+              <connectionPointOut>
+                <relPosition x="56" y="16"/>
+              </connectionPointOut>
+              <expression>DEBUG</expression>
+            </inVariable>
+            <block localId="27" typeName="LOGGER" instanceName="LOGGER2" executionOrderId="0" height="112" width="64">
+              <position x="1552" y="496"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="21" formalParameter="OUT">
+                      <position x="1552" y="528"/>
+                      <position x="1360" y="528"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="MSG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="28">
+                      <position x="1552" y="560"/>
+                      <position x="1512" y="560"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="LEVEL">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="26">
+                      <position x="1552" y="592"/>
+                      <position x="1520" y="592"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables/>
+            </block>
+            <inVariable localId="28" executionOrderId="0" height="32" width="128" negated="false">
+              <position x="1384" y="544"/>
+              <connectionPointOut>
+                <relPosition x="128" y="16"/>
+              </connectionPointOut>
+              <expression>'ALL TESTS OK'</expression>
+            </inVariable>
+            <comment localId="29" height="144" width="504">
+              <position x="80" y="32"/>
+              <content>
+                <xhtml:p><![CDATA[This tests some IEC-61131 languages and standard library.
+
+BEREMIZ_TEST_CYCLES CFLAG is used here. 
+- pre-defined PLC cycles all run with no pause
+- time is emulated]]></xhtml:p>
+              </content>
+            </comment>
+          </FBD>
+        </body>
+      </pou>
+      <pou name="TEMPO_TEST" pouType="functionBlock">
+        <interface>
+          <inputVars>
+            <variable name="RUN">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+          </inputVars>
+          <outputVars>
+            <variable name="VALID">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="RESULT">
+              <type>
+                <string/>
+              </type>
+            </variable>
+          </outputVars>
+          <localVars>
+            <variable name="TON0">
+              <type>
+                <derived name="TON"/>
+              </type>
+            </variable>
+            <variable name="R_TRIG0">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+            <variable name="R_TRIG1">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+            <variable name="RTC1">
+              <type>
+                <derived name="RTC"/>
+              </type>
+            </variable>
+            <variable name="START_TIME">
+              <type>
+                <DT/>
+              </type>
+            </variable>
+            <variable name="MEASURED_DURATION">
+              <type>
+                <TIME/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <block localId="2" typeName="TON" instanceName="TON0" executionOrderId="0" height="64" width="48">
+              <position x="296" y="440"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="9">
+                      <position x="296" y="472"/>
+                      <position x="256" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PT">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="17">
+                      <position x="296" y="496"/>
+                      <position x="208" y="496"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="48" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="ET">
+                  <connectionPointOut>
+                    <relPosition x="48" y="56"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="3" typeName="R_TRIG" instanceName="R_TRIG0" executionOrderId="0" height="48" width="64">
+              <position x="152" y="288"/>
+              <inputVariables>
+                <variable formalParameter="CLK">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="8">
+                      <position x="152" y="320"/>
+                      <position x="120" y="320"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="4" typeName="R_TRIG" instanceName="R_TRIG1" executionOrderId="0" height="48" width="64">
+              <position x="384" y="440"/>
+              <inputVariables>
+                <variable formalParameter="CLK">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="2" formalParameter="Q">
+                      <position x="384" y="472"/>
+                      <position x="344" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="RTC" instanceName="RTC1" executionOrderId="0" height="64" width="64">
+              <position x="80" y="184"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PDT">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="CDT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="56"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <connector name="now" localId="7" height="32" width="72">
+              <position x="176" y="224"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="5" formalParameter="CDT">
+                  <position x="176" y="240"/>
+                  <position x="144" y="240"/>
+                </connection>
+              </connectionPointIn>
+            </connector>
+            <continuation name="now" localId="1" height="32" width="72">
+              <position x="496" y="488"/>
+              <connectionPointOut>
+                <relPosition x="72" y="16"/>
+              </connectionPointOut>
+            </continuation>
+            <inVariable localId="8" executionOrderId="0" height="32" width="40" negated="false">
+              <position x="80" y="304"/>
+              <connectionPointOut>
+                <relPosition x="40" y="16"/>
+              </connectionPointOut>
+              <expression>RUN</expression>
+            </inVariable>
+            <inVariable localId="9" executionOrderId="0" height="32" width="40" negated="false">
+              <position x="216" y="456"/>
+              <connectionPointOut>
+                <relPosition x="40" y="16"/>
+              </connectionPointOut>
+              <expression>RUN</expression>
+            </inVariable>
+            <inVariable localId="10" executionOrderId="0" height="32" width="80" negated="false">
+              <position x="344" y="184"/>
+              <connectionPointOut>
+                <relPosition x="80" y="16"/>
+              </connectionPointOut>
+              <expression>TIME#10s</expression>
+            </inVariable>
+            <continuation name="now" localId="11" height="32" width="72">
+              <position x="232" y="328"/>
+              <connectionPointOut>
+                <relPosition x="72" y="16"/>
+              </connectionPointOut>
+            </continuation>
+            <block localId="12" typeName="MOVE" executionOrderId="0" height="64" width="56">
+              <position x="360" y="288"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="3" formalParameter="Q">
+                      <position x="360" y="320"/>
+                      <position x="216" y="320"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="56"/>
+                    <connection refLocalId="11">
+                      <position x="360" y="344"/>
+                      <position x="304" y="344"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="56" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="56" y="56"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="14" executionOrderId="0" height="32" width="96" negated="false">
+              <position x="464" y="328"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="12" formalParameter="OUT">
+                  <position x="464" y="344"/>
+                  <position x="416" y="344"/>
+                </connection>
+              </connectionPointIn>
+              <expression>START_TIME</expression>
+            </outVariable>
+            <block localId="15" typeName="SUB" executionOrderId="0" height="112" width="64">
+              <position x="616" y="440"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="4" formalParameter="Q">
+                      <position x="616" y="472"/>
+                      <position x="448" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="1">
+                      <position x="616" y="504"/>
+                      <position x="568" y="504"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="6">
+                      <position x="616" y="536"/>
+                      <position x="568" y="536"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="64"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="6" executionOrderId="0" height="32" width="96" negated="false">
+              <position x="472" y="520"/>
+              <connectionPointOut>
+                <relPosition x="96" y="16"/>
+              </connectionPointOut>
+              <expression>START_TIME</expression>
+            </inVariable>
+            <comment localId="21" height="120" width="552">
+              <position x="96" y="32"/>
+              <content>
+                <xhtml:p><![CDATA[Test that TON and TOFF work as expected]]></xhtml:p>
+              </content>
+            </comment>
+            <outVariable localId="22" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="1384" y="456"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="26" formalParameter="ENO">
+                  <position x="1384" y="472"/>
+                  <position x="1328" y="472"/>
+                </connection>
+              </connectionPointIn>
+              <expression>VALID</expression>
+            </outVariable>
+            <block localId="13" typeName="SUB" executionOrderId="0" height="112" width="64">
+              <position x="856" y="440"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="15" formalParameter="ENO">
+                      <position x="856" y="472"/>
+                      <position x="680" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="15" formalParameter="OUT">
+                      <position x="856" y="504"/>
+                      <position x="680" y="504"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="18">
+                      <position x="856" y="536"/>
+                      <position x="816" y="536"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="64"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <connector name="duration" localId="23" height="32" width="104">
+              <position x="496" y="184"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="10">
+                  <position x="496" y="200"/>
+                  <position x="424" y="200"/>
+                </connection>
+              </connectionPointIn>
+            </connector>
+            <continuation name="duration" localId="17" height="32" width="104">
+              <position x="104" y="480"/>
+              <connectionPointOut>
+                <relPosition x="104" y="16"/>
+              </connectionPointOut>
+            </continuation>
+            <continuation name="duration" localId="18" height="32" width="104">
+              <position x="712" y="520"/>
+              <connectionPointOut>
+                <relPosition x="104" y="16"/>
+              </connectionPointOut>
+            </continuation>
+            <block localId="24" typeName="LT" executionOrderId="0" height="104" width="64">
+              <position x="1048" y="440"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="13" formalParameter="ENO">
+                      <position x="1048" y="472"/>
+                      <position x="920" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="13" formalParameter="OUT">
+                      <position x="1048" y="504"/>
+                      <position x="920" y="504"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="88"/>
+                    <connection refLocalId="19">
+                      <position x="1048" y="528"/>
+                      <position x="1016" y="528"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="64"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="19" executionOrderId="0" height="32" width="80" negated="false">
+              <position x="936" y="512"/>
+              <connectionPointOut>
+                <relPosition x="80" y="16"/>
+              </connectionPointOut>
+              <expression>TIME#1s</expression>
+            </inVariable>
+            <block localId="26" typeName="SEL" executionOrderId="0" height="144" width="64">
+              <position x="1264" y="440"/>
+              <inputVariables>
+                <variable formalParameter="EN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="32"/>
+                    <connection refLocalId="24" formalParameter="ENO">
+                      <position x="1264" y="472"/>
+                      <position x="1112" y="472"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="G">
+                  <connectionPointIn>
+                    <relPosition x="0" y="64"/>
+                    <connection refLocalId="24" formalParameter="OUT">
+                      <position x="1264" y="504"/>
+                      <position x="1112" y="504"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN0">
+                  <connectionPointIn>
+                    <relPosition x="0" y="96"/>
+                    <connection refLocalId="28">
+                      <position x="1264" y="536"/>
+                      <position x="1208" y="536"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="128"/>
+                    <connection refLocalId="27">
+                      <position x="1264" y="568"/>
+                      <position x="1208" y="568"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ENO">
+                  <connectionPointOut>
+                    <relPosition x="64" y="32"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="64" y="64"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="27" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="1152" y="552"/>
+              <connectionPointOut>
+                <relPosition x="56" y="16"/>
+              </connectionPointOut>
+              <expression>'OK'</expression>
+            </inVariable>
+            <inVariable localId="28" executionOrderId="0" height="32" width="56" negated="false">
+              <position x="1152" y="520"/>
+              <connectionPointOut>
+                <relPosition x="56" y="16"/>
+              </connectionPointOut>
+              <expression>'BAD'</expression>
+            </inVariable>
+            <outVariable localId="29" executionOrderId="0" height="32" width="64" negated="false">
+              <position x="1384" y="488"/>
+              <connectionPointIn>
+                <relPosition x="0" y="16"/>
+                <connection refLocalId="26" formalParameter="OUT">
+                  <position x="1384" y="504"/>
+                  <position x="1328" y="504"/>
+                </connection>
+              </connectionPointIn>
+              <expression>RESULT</expression>
+            </outVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#20ms">
+            <pouInstance name="instance0" typeName="main_test"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/opcua_0@opcua/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="opcua_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/opcua_0@opcua/confnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <AuthType/>
+</OPCUAClient>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,208 @@
+<?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="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2022-07-16T10:46:25"/>
+  <contentHeader name="Unnamed" modificationDateTime="2022-11-10T17:51:34">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="program0" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0" address="%IL0.2">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+            <variable name="LocalVar1" address="%QL0.3">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+          </localVars>
+          <localVars>
+            <variable name="python_poll0">
+              <type>
+                <derived name="python_poll"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="1" executionOrderId="0" height="25" width="85" negated="false">
+              <position x="160" y="190"/>
+              <connectionPointOut>
+                <relPosition x="85" y="10"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="2" executionOrderId="0" height="24" width="82" negated="false">
+              <position x="238" y="49"/>
+              <connectionPointIn>
+                <relPosition x="0" y="11"/>
+                <connection refLocalId="9">
+                  <position x="238" y="60"/>
+                  <position x="204" y="60"/>
+                </connection>
+              </connectionPointIn>
+              <expression>LocalVar1</expression>
+            </outVariable>
+            <block localId="4" typeName="python_poll" instanceName="python_poll0" executionOrderId="0" height="60" width="98">
+              <position x="658" y="101"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="29"/>
+                    <connection refLocalId="7">
+                      <position x="658" y="130"/>
+                      <position x="623" y="130"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="49"/>
+                    <connection refLocalId="6" formalParameter="OUT">
+                      <position x="658" y="150"/>
+                      <position x="560" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="98" y="29"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="98" y="49"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="LREAL_TO_STRING" executionOrderId="0" height="40" width="130">
+              <position x="280" y="170"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="280" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="245" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="130" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="6" typeName="CONCAT" executionOrderId="0" height="165" width="63">
+              <position x="497" y="108"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="42"/>
+                    <connection refLocalId="3">
+                      <position x="497" y="150"/>
+                      <position x="330" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="92"/>
+                    <connection refLocalId="5" formalParameter="OUT">
+                      <position x="497" y="200"/>
+                      <position x="410" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="142"/>
+                    <connection refLocalId="8">
+                      <position x="497" y="250"/>
+                      <position x="225" y="250"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="63" y="42"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="7" executionOrderId="0" height="24" width="44" negated="false">
+              <position x="579" y="116"/>
+              <connectionPointOut>
+                <relPosition x="44" y="14"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <inVariable localId="3" executionOrderId="0" height="25" width="180" negated="false">
+              <position x="160" y="140"/>
+              <connectionPointOut>
+                <relPosition x="180" y="10"/>
+              </connectionPointOut>
+              <expression>'pfunc("'</expression>
+            </inVariable>
+            <inVariable localId="8" executionOrderId="0" height="25" width="230" negated="false">
+              <position x="165" y="240"/>
+              <connectionPointOut>
+                <relPosition x="230" y="10"/>
+              </connectionPointOut>
+              <expression>'\n")'</expression>
+            </inVariable>
+            <inVariable localId="9" executionOrderId="0" height="29" width="45" negated="false">
+              <position x="159" y="47"/>
+              <connectionPointOut>
+                <relPosition x="45" y="13"/>
+              </connectionPointOut>
+              <expression>3.4</expression>
+            </inVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#100ms">
+            <pouInstance name="instance0" typeName="program0"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables/>
+  <globals>
+    <xhtml:p><![CDATA[
+import sys
+def pfunc(arg):
+    sys.stdout.write(arg)
+    sys.stdout.flush()
+
+pfunc("globals section\n")
+
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+pfunc("init section\n")
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+pfunc("cleanup section\n")
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+pfunc("start section\n")
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+pfunc("stop section\n")
+
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/opcua_0@opcua/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="opcua_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/opcua_0@opcua/confnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8'?>
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <AuthType>
+    <x509 Certificate="my_cert.der" PrivateKey="my_private_key.pem">
+      <Policy/>
+      <Mode/>
+    </x509>
+  </AuthType>
+</OPCUAClient>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,208 @@
+<?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="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2022-07-16T10:46:25"/>
+  <contentHeader name="Unnamed" modificationDateTime="2022-11-10T17:51:34">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="program0" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0" address="%IL0.2">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+            <variable name="LocalVar1" address="%QL0.3">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+          </localVars>
+          <localVars>
+            <variable name="python_poll0">
+              <type>
+                <derived name="python_poll"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="1" executionOrderId="0" height="25" width="85" negated="false">
+              <position x="160" y="190"/>
+              <connectionPointOut>
+                <relPosition x="85" y="10"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="2" executionOrderId="0" height="24" width="82" negated="false">
+              <position x="238" y="49"/>
+              <connectionPointIn>
+                <relPosition x="0" y="11"/>
+                <connection refLocalId="9">
+                  <position x="238" y="60"/>
+                  <position x="204" y="60"/>
+                </connection>
+              </connectionPointIn>
+              <expression>LocalVar1</expression>
+            </outVariable>
+            <block localId="4" typeName="python_poll" instanceName="python_poll0" executionOrderId="0" height="60" width="98">
+              <position x="658" y="101"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="29"/>
+                    <connection refLocalId="7">
+                      <position x="658" y="130"/>
+                      <position x="623" y="130"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="49"/>
+                    <connection refLocalId="6" formalParameter="OUT">
+                      <position x="658" y="150"/>
+                      <position x="560" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="98" y="29"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="98" y="49"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="LREAL_TO_STRING" executionOrderId="0" height="40" width="130">
+              <position x="280" y="170"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="280" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="245" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="130" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="6" typeName="CONCAT" executionOrderId="0" height="165" width="63">
+              <position x="497" y="108"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="42"/>
+                    <connection refLocalId="3">
+                      <position x="497" y="150"/>
+                      <position x="330" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="92"/>
+                    <connection refLocalId="5" formalParameter="OUT">
+                      <position x="497" y="200"/>
+                      <position x="410" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="142"/>
+                    <connection refLocalId="8">
+                      <position x="497" y="250"/>
+                      <position x="225" y="250"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="63" y="42"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="7" executionOrderId="0" height="24" width="44" negated="false">
+              <position x="579" y="116"/>
+              <connectionPointOut>
+                <relPosition x="44" y="14"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <inVariable localId="3" executionOrderId="0" height="25" width="180" negated="false">
+              <position x="160" y="140"/>
+              <connectionPointOut>
+                <relPosition x="180" y="10"/>
+              </connectionPointOut>
+              <expression>'pfunc("'</expression>
+            </inVariable>
+            <inVariable localId="8" executionOrderId="0" height="25" width="230" negated="false">
+              <position x="165" y="240"/>
+              <connectionPointOut>
+                <relPosition x="230" y="10"/>
+              </connectionPointOut>
+              <expression>'\n")'</expression>
+            </inVariable>
+            <inVariable localId="9" executionOrderId="0" height="29" width="45" negated="false">
+              <position x="159" y="47"/>
+              <connectionPointOut>
+                <relPosition x="45" y="13"/>
+              </connectionPointOut>
+              <expression>3.4</expression>
+            </inVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#100ms">
+            <pouInstance name="instance0" typeName="program0"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_browse_encrypted/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables/>
+  <globals>
+    <xhtml:p><![CDATA[
+import sys
+def pfunc(arg):
+    sys.stdout.write(arg)
+    sys.stdout.flush()
+
+pfunc("globals section\n")
+
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+pfunc("init section\n")
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+pfunc("cleanup section\n")
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+pfunc("start section\n")
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+pfunc("stop section\n")
+
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/opcua_0@opcua/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="opcua_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/opcua_0@opcua/confnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/opcua_0@opcua/selected.csv	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+input,TestOut,2,int,2,Double,0
+output,TestIn,2,int,3,Double,0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,208 @@
+<?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="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2022-07-16T10:46:25"/>
+  <contentHeader name="Unnamed" modificationDateTime="2022-07-16T22:47:46">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="program0" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0" address="%IL0.0">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+            <variable name="LocalVar1" address="%QL0.0">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+          </localVars>
+          <localVars>
+            <variable name="python_poll0">
+              <type>
+                <derived name="python_poll"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="1" executionOrderId="0" height="25" width="85" negated="false">
+              <position x="160" y="190"/>
+              <connectionPointOut>
+                <relPosition x="85" y="10"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="2" executionOrderId="0" height="24" width="82" negated="false">
+              <position x="238" y="49"/>
+              <connectionPointIn>
+                <relPosition x="0" y="11"/>
+                <connection refLocalId="9">
+                  <position x="238" y="60"/>
+                  <position x="204" y="60"/>
+                </connection>
+              </connectionPointIn>
+              <expression>LocalVar1</expression>
+            </outVariable>
+            <block localId="4" typeName="python_poll" instanceName="python_poll0" executionOrderId="0" height="60" width="98">
+              <position x="658" y="101"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="29"/>
+                    <connection refLocalId="7">
+                      <position x="658" y="130"/>
+                      <position x="623" y="130"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="49"/>
+                    <connection refLocalId="6" formalParameter="OUT">
+                      <position x="658" y="150"/>
+                      <position x="560" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="98" y="29"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="98" y="49"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="LREAL_TO_STRING" executionOrderId="0" height="40" width="130">
+              <position x="280" y="170"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="280" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="245" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="130" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="6" typeName="CONCAT" executionOrderId="0" height="165" width="63">
+              <position x="497" y="108"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="42"/>
+                    <connection refLocalId="3">
+                      <position x="497" y="150"/>
+                      <position x="330" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="92"/>
+                    <connection refLocalId="5" formalParameter="OUT">
+                      <position x="497" y="200"/>
+                      <position x="410" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="142"/>
+                    <connection refLocalId="8">
+                      <position x="497" y="250"/>
+                      <position x="225" y="250"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="63" y="42"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="7" executionOrderId="0" height="24" width="44" negated="false">
+              <position x="579" y="116"/>
+              <connectionPointOut>
+                <relPosition x="44" y="14"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <inVariable localId="3" executionOrderId="0" height="25" width="180" negated="false">
+              <position x="160" y="140"/>
+              <connectionPointOut>
+                <relPosition x="180" y="10"/>
+              </connectionPointOut>
+              <expression>'pfunc("'</expression>
+            </inVariable>
+            <inVariable localId="8" executionOrderId="0" height="25" width="230" negated="false">
+              <position x="165" y="240"/>
+              <connectionPointOut>
+                <relPosition x="230" y="10"/>
+              </connectionPointOut>
+              <expression>'\n")'</expression>
+            </inVariable>
+            <inVariable localId="9" executionOrderId="0" height="29" width="45" negated="false">
+              <position x="159" y="47"/>
+              <connectionPointOut>
+                <relPosition x="45" y="13"/>
+              </connectionPointOut>
+              <expression>3.4</expression>
+            </inVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#100ms">
+            <pouInstance name="instance0" typeName="program0"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables/>
+  <globals>
+    <xhtml:p><![CDATA[
+import sys
+def pfunc(arg):
+    sys.stdout.write(arg)
+    sys.stdout.flush()
+
+pfunc("globals section\n")
+
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+pfunc("init section\n")
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+pfunc("cleanup section\n")
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+pfunc("start section\n")
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+pfunc("stop section\n")
+
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
+  <TargetType/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/opcua_0@opcua/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="opcua_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/opcua_0@opcua/confnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='utf-8'?>
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://127.0.0.1:4840/freeopcua/server/">
+  <AuthType>
+    <x509 Certificate="my_cert.der" PrivateKey="my_private_key.pem">
+      <Policy/>
+      <Mode/>
+    </x509>
+  </AuthType>
+</OPCUAClient>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/opcua_0@opcua/selected.csv	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+input,TestOut,2,int,2,Double,0
+output,TestIn,2,int,3,Double,0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,208 @@
+<?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="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2022-07-16T10:46:25"/>
+  <contentHeader name="Unnamed" modificationDateTime="2022-09-15T20:56:16">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="5" y="5"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="program0" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="LocalVar0" address="%IL0.0">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+            <variable name="LocalVar1" address="%QL0.0">
+              <type>
+                <LREAL/>
+              </type>
+            </variable>
+          </localVars>
+          <localVars>
+            <variable name="python_poll0">
+              <type>
+                <derived name="python_poll"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <inVariable localId="1" executionOrderId="0" height="25" width="85" negated="false">
+              <position x="160" y="190"/>
+              <connectionPointOut>
+                <relPosition x="85" y="10"/>
+              </connectionPointOut>
+              <expression>LocalVar0</expression>
+            </inVariable>
+            <outVariable localId="2" executionOrderId="0" height="24" width="82" negated="false">
+              <position x="238" y="49"/>
+              <connectionPointIn>
+                <relPosition x="0" y="11"/>
+                <connection refLocalId="9">
+                  <position x="238" y="60"/>
+                  <position x="204" y="60"/>
+                </connection>
+              </connectionPointIn>
+              <expression>LocalVar1</expression>
+            </outVariable>
+            <block localId="4" typeName="python_poll" instanceName="python_poll0" executionOrderId="0" height="60" width="98">
+              <position x="658" y="101"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="29"/>
+                    <connection refLocalId="7">
+                      <position x="658" y="130"/>
+                      <position x="623" y="130"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="49"/>
+                    <connection refLocalId="6" formalParameter="OUT">
+                      <position x="658" y="150"/>
+                      <position x="560" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="98" y="29"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="98" y="49"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="5" typeName="LREAL_TO_STRING" executionOrderId="0" height="40" width="130">
+              <position x="280" y="170"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="1">
+                      <position x="280" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="255" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="300" y="200"/>
+                      <position x="245" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="130" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="6" typeName="CONCAT" executionOrderId="0" height="165" width="63">
+              <position x="497" y="108"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="42"/>
+                    <connection refLocalId="3">
+                      <position x="497" y="150"/>
+                      <position x="330" y="150"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="92"/>
+                    <connection refLocalId="5" formalParameter="OUT">
+                      <position x="497" y="200"/>
+                      <position x="410" y="200"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN3">
+                  <connectionPointIn>
+                    <relPosition x="0" y="142"/>
+                    <connection refLocalId="8">
+                      <position x="497" y="250"/>
+                      <position x="225" y="250"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="63" y="42"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="7" executionOrderId="0" height="24" width="44" negated="false">
+              <position x="579" y="116"/>
+              <connectionPointOut>
+                <relPosition x="44" y="14"/>
+              </connectionPointOut>
+              <expression>TRUE</expression>
+            </inVariable>
+            <inVariable localId="3" executionOrderId="0" height="25" width="180" negated="false">
+              <position x="160" y="140"/>
+              <connectionPointOut>
+                <relPosition x="180" y="10"/>
+              </connectionPointOut>
+              <expression>'pfunc("'</expression>
+            </inVariable>
+            <inVariable localId="8" executionOrderId="0" height="25" width="230" negated="false">
+              <position x="165" y="240"/>
+              <connectionPointOut>
+                <relPosition x="230" y="10"/>
+              </connectionPointOut>
+              <expression>'\n")'</expression>
+            </inVariable>
+            <inVariable localId="9" executionOrderId="0" height="29" width="45" negated="false">
+              <position x="159" y="47"/>
+              <connectionPointOut>
+                <relPosition x="45" y="13"/>
+              </connectionPointOut>
+              <expression>3.4</expression>
+            </inVariable>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="task0" priority="0" interval="T#100ms">
+            <pouInstance name="instance0" typeName="program0"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/py_ext_0@py_ext/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="py_ext_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/opcua_client_encrypted/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+  <variables/>
+  <globals>
+    <xhtml:p><![CDATA[
+import sys
+def pfunc(arg):
+    sys.stdout.write(arg)
+    sys.stdout.flush()
+
+pfunc("globals section\n")
+
+]]></xhtml:p>
+  </globals>
+  <init>
+    <xhtml:p><![CDATA[
+pfunc("init section\n")
+]]></xhtml:p>
+  </init>
+  <cleanup>
+    <xhtml:p><![CDATA[
+pfunc("cleanup section\n")
+]]></xhtml:p>
+  </cleanup>
+  <start>
+    <xhtml:p><![CDATA[
+pfunc("start section\n")
+]]></xhtml:p>
+  </start>
+  <stop>
+    <xhtml:p><![CDATA[
+pfunc("stop section\n")
+
+]]></xhtml:p>
+  </stop>
+</PyFile>
--- a/tests/projects/svghmi/py_ext_0@py_ext/pyfile.xml	Fri Aug 18 11:38:20 2023 +0200
+++ b/tests/projects/svghmi/py_ext_0@py_ext/pyfile.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -87,7 +87,7 @@
   <start>
     <xhtml:p><![CDATA[
 
-AddPathToSVGHMIServers("alarms", AlarmJsonResource)
+AddPathToSVGHMIServers(b"alarms", AlarmJsonResource)
 
 
 ]]></xhtml:p>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/svghmi_basic/beremiz.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,5 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BeremizRoot URI_location="LOCAL://">
+  <TargetType/>
+  <Libraries Enable_SVGHMI_Library="true"/>
+</BeremizRoot>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/svghmi_basic/plc.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,1490 @@
+<?xml version='1.0' encoding='utf-8'?>
+<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
+  <fileHeader companyName="Beremiz" productName="Unnamed" productVersion="1" creationDateTime="2012-09-04T16:16:33"/>
+  <contentHeader name="traffic_lights" modificationDateTime="2023-08-01T20:50:25">
+    <coordinateInfo>
+      <fbd>
+        <scaling x="0" y="0"/>
+      </fbd>
+      <ld>
+        <scaling x="0" y="0"/>
+      </ld>
+      <sfc>
+        <scaling x="0" y="0"/>
+      </sfc>
+    </coordinateInfo>
+  </contentHeader>
+  <types>
+    <dataTypes/>
+    <pous>
+      <pou name="traffic_light_sequence" pouType="functionBlock">
+        <interface>
+          <inputVars>
+            <variable name="SWITCH_BUTTON">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="PEDESTRIAN_BUTTON">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+          </inputVars>
+          <outputVars>
+            <variable name="RED_LIGHT">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="ORANGE_LIGHT">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="GREEN_LIGHT">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="PEDESTRIAN_RED_LIGHT">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="PEDESTRIAN_GREEN_LIGHT">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+          </outputVars>
+          <localVars>
+            <variable name="TON1">
+              <type>
+                <derived name="TON"/>
+              </type>
+            </variable>
+            <variable name="TON2">
+              <type>
+                <derived name="TON"/>
+              </type>
+            </variable>
+            <variable name="ALLOW_CARS">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="WARN_CARS">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="STOP_CARS">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="ALLOW_PEDESTRIANS">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="STOP_PEDESTRIANS">
+              <type>
+                <BOOL/>
+              </type>
+            </variable>
+            <variable name="TON3">
+              <type>
+                <derived name="TON"/>
+              </type>
+            </variable>
+            <variable name="R_TRIG0">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+            <variable name="R_TRIG1">
+              <type>
+                <derived name="R_TRIG"/>
+              </type>
+            </variable>
+            <variable name="SR0">
+              <type>
+                <derived name="SR"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <actions>
+          <action name="BLINK_ORANGE_LIGHT">
+            <body>
+              <LD>
+                <leftPowerRail localId="1" height="40" width="3">
+                  <position x="54" y="123"/>
+                  <connectionPointOut formalParameter="">
+                    <relPosition x="3" y="20"/>
+                  </connectionPointOut>
+                </leftPowerRail>
+                <contact localId="2" height="15" width="21" negated="true">
+                  <position x="121" y="135"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="8"/>
+                    <connection refLocalId="1">
+                      <position x="121" y="143"/>
+                      <position x="56" y="143"/>
+                    </connection>
+                  </connectionPointIn>
+                  <connectionPointOut>
+                    <relPosition x="21" y="8"/>
+                  </connectionPointOut>
+                  <variable>ORANGE_LIGHT</variable>
+                </contact>
+                <block localId="3" width="97" height="102" typeName="TON" instanceName="TON1">
+                  <position x="216" y="103"/>
+                  <inputVariables>
+                    <variable formalParameter="IN">
+                      <connectionPointIn>
+                        <relPosition x="0" y="40"/>
+                        <connection refLocalId="2">
+                          <position x="216" y="143"/>
+                          <position x="142" y="143"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                    <variable formalParameter="PT">
+                      <connectionPointIn>
+                        <relPosition x="0" y="81"/>
+                        <connection refLocalId="4">
+                          <position x="216" y="184"/>
+                          <position x="151" y="184"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                  </inputVariables>
+                  <inOutVariables/>
+                  <outputVariables>
+                    <variable formalParameter="Q">
+                      <connectionPointOut>
+                        <relPosition x="97" y="40"/>
+                      </connectionPointOut>
+                    </variable>
+                    <variable formalParameter="ET">
+                      <connectionPointOut>
+                        <relPosition x="97" y="81"/>
+                      </connectionPointOut>
+                    </variable>
+                  </outputVariables>
+                </block>
+                <inVariable localId="4" height="37" width="76" negated="false">
+                  <position x="75" y="166"/>
+                  <connectionPointOut>
+                    <relPosition x="76" y="18"/>
+                  </connectionPointOut>
+                  <expression>T#500ms</expression>
+                </inVariable>
+                <block localId="5" width="97" height="106" typeName="TON" instanceName="TON2">
+                  <position x="216" y="251"/>
+                  <inputVariables>
+                    <variable formalParameter="IN">
+                      <connectionPointIn>
+                        <relPosition x="0" y="41"/>
+                        <connection refLocalId="14">
+                          <position x="216" y="292"/>
+                          <position x="155" y="292"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                    <variable formalParameter="PT">
+                      <connectionPointIn>
+                        <relPosition x="0" y="84"/>
+                        <connection refLocalId="15">
+                          <position x="216" y="335"/>
+                          <position x="162" y="335"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                  </inputVariables>
+                  <inOutVariables/>
+                  <outputVariables>
+                    <variable formalParameter="Q">
+                      <connectionPointOut>
+                        <relPosition x="97" y="41"/>
+                      </connectionPointOut>
+                    </variable>
+                    <variable formalParameter="ET">
+                      <connectionPointOut>
+                        <relPosition x="97" y="84"/>
+                      </connectionPointOut>
+                    </variable>
+                  </outputVariables>
+                </block>
+                <coil localId="6" height="15" width="21" storage="reset">
+                  <position x="517" y="284"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="8"/>
+                    <connection refLocalId="10" formalParameter="Q">
+                      <position x="517" y="292"/>
+                      <position x="427" y="292"/>
+                    </connection>
+                  </connectionPointIn>
+                  <connectionPointOut>
+                    <relPosition x="21" y="8"/>
+                  </connectionPointOut>
+                  <variable>ORANGE_LIGHT</variable>
+                </coil>
+                <rightPowerRail localId="7" height="40" width="3">
+                  <position x="598" y="123"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="20"/>
+                    <connection refLocalId="8">
+                      <position x="598" y="143"/>
+                      <position x="530" y="143"/>
+                    </connection>
+                  </connectionPointIn>
+                </rightPowerRail>
+                <coil localId="8" height="15" width="21" storage="set">
+                  <position x="509" y="135"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="8"/>
+                    <connection refLocalId="11" formalParameter="Q">
+                      <position x="509" y="143"/>
+                      <position x="428" y="143"/>
+                    </connection>
+                  </connectionPointIn>
+                  <connectionPointOut>
+                    <relPosition x="21" y="8"/>
+                  </connectionPointOut>
+                  <variable>ORANGE_LIGHT</variable>
+                </coil>
+                <comment localId="9" height="52" width="318">
+                  <position x="51" y="11"/>
+                  <content>
+                    <xhtml:p><![CDATA[This action makes the orange light blink]]></xhtml:p>
+                  </content>
+                </comment>
+                <block localId="10" width="58" height="40" typeName="R_TRIG" instanceName="R_TRIG0">
+                  <position x="370" y="262"/>
+                  <inputVariables>
+                    <variable formalParameter="CLK">
+                      <connectionPointIn>
+                        <relPosition x="0" y="30"/>
+                        <connection refLocalId="5" formalParameter="Q">
+                          <position x="370" y="292"/>
+                          <position x="313" y="292"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                  </inputVariables>
+                  <inOutVariables/>
+                  <outputVariables>
+                    <variable formalParameter="Q">
+                      <connectionPointOut>
+                        <relPosition x="58" y="30"/>
+                      </connectionPointOut>
+                    </variable>
+                  </outputVariables>
+                </block>
+                <block localId="11" width="58" height="40" typeName="R_TRIG" instanceName="R_TRIG1">
+                  <position x="371" y="113"/>
+                  <inputVariables>
+                    <variable formalParameter="CLK">
+                      <connectionPointIn>
+                        <relPosition x="0" y="30"/>
+                        <connection refLocalId="3" formalParameter="Q">
+                          <position x="371" y="143"/>
+                          <position x="313" y="143"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                  </inputVariables>
+                  <inOutVariables/>
+                  <outputVariables>
+                    <variable formalParameter="Q">
+                      <connectionPointOut>
+                        <relPosition x="58" y="30"/>
+                      </connectionPointOut>
+                    </variable>
+                  </outputVariables>
+                </block>
+                <rightPowerRail localId="12" height="40" width="3">
+                  <position x="597" y="272"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="20"/>
+                    <connection refLocalId="6">
+                      <position x="597" y="292"/>
+                      <position x="538" y="292"/>
+                    </connection>
+                  </connectionPointIn>
+                </rightPowerRail>
+                <leftPowerRail localId="13" height="40" width="3">
+                  <position x="67" y="272"/>
+                  <connectionPointOut formalParameter="">
+                    <relPosition x="3" y="20"/>
+                  </connectionPointOut>
+                </leftPowerRail>
+                <contact localId="14" height="15" width="21">
+                  <position x="134" y="284"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="8"/>
+                    <connection refLocalId="13">
+                      <position x="134" y="292"/>
+                      <position x="69" y="292"/>
+                    </connection>
+                  </connectionPointIn>
+                  <connectionPointOut>
+                    <relPosition x="21" y="8"/>
+                  </connectionPointOut>
+                  <variable>ORANGE_LIGHT</variable>
+                </contact>
+                <inVariable localId="15" height="36" width="77" negated="false">
+                  <position x="85" y="317"/>
+                  <connectionPointOut>
+                    <relPosition x="77" y="18"/>
+                  </connectionPointOut>
+                  <expression>T#500ms</expression>
+                </inVariable>
+              </LD>
+            </body>
+          </action>
+        </actions>
+        <transitions>
+          <transition name="STOP">
+            <body>
+              <FBD>
+                <block localId="42" width="59" height="53" typeName="NOT" executionOrderId="0">
+                  <position x="237" y="31"/>
+                  <inputVariables>
+                    <variable formalParameter="IN">
+                      <connectionPointIn>
+                        <relPosition x="0" y="36"/>
+                        <connection refLocalId="43">
+                          <position x="237" y="67"/>
+                          <position x="202" y="67"/>
+                        </connection>
+                      </connectionPointIn>
+                    </variable>
+                  </inputVariables>
+                  <inOutVariables/>
+                  <outputVariables>
+                    <variable formalParameter="OUT">
+                      <connectionPointOut>
+                        <relPosition x="59" y="36"/>
+                      </connectionPointOut>
+                    </variable>
+                  </outputVariables>
+                </block>
+                <inVariable localId="43" height="39" width="164" executionOrderId="0" negated="false">
+                  <position x="38" y="48"/>
+                  <connectionPointOut>
+                    <relPosition x="164" y="19"/>
+                  </connectionPointOut>
+                  <expression>SWITCH_BUTTON</expression>
+                </inVariable>
+                <outVariable localId="44" height="40" width="46" executionOrderId="0" negated="false">
+                  <position x="351" y="47"/>
+                  <connectionPointIn>
+                    <relPosition x="0" y="20"/>
+                    <connection refLocalId="42" formalParameter="OUT">
+                      <position x="351" y="67"/>
+                      <position x="296" y="67"/>
+                    </connection>
+                  </connectionPointIn>
+                  <expression>STOP</expression>
+                </outVariable>
+              </FBD>
+            </body>
+          </transition>
+        </transitions>
+        <body>
+          <SFC>
+            <step localId="1" height="37" width="121" name="Standstill" initialStep="true">
+              <position x="509" y="31"/>
+              <connectionPointIn>
+                <relPosition x="60" y="0"/>
+                <connection refLocalId="39">
+                  <position x="569" y="31"/>
+                  <position x="569" y="11"/>
+                  <position x="963" y="11"/>
+                  <position x="963" y="1151"/>
+                  <position x="776" y="1151"/>
+                  <position x="776" y="1097"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="60" y="37"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="121" y="18"/>
+              </connectionPointOutAction>
+            </step>
+            <transition localId="2" height="2" width="20">
+              <position x="559" y="222"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="1">
+                  <position x="569" y="222"/>
+                  <position x="569" y="68"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[SWITCH_BUTTON]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <step localId="3" height="30" width="118" name="ORANGE">
+              <position x="510" y="250"/>
+              <connectionPointIn>
+                <relPosition x="59" y="0"/>
+                <connection refLocalId="2">
+                  <position x="569" y="250"/>
+                  <position x="569" y="224"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="59" y="30"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="118" y="15"/>
+              </connectionPointOutAction>
+            </step>
+            <transition localId="6" height="2" width="20">
+              <position x="559" y="376"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="15">
+                  <position x="569" y="376"/>
+                  <position x="569" y="336"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[STOP_CARS]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <actionBlock localId="8" width="231" height="162">
+              <position x="711" y="34"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="1">
+                  <position x="711" y="49"/>
+                  <position x="630" y="49"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="P">
+                <relPosition x="0" y="0"/>
+                <inline>
+                  <ST>
+                    <xhtml:p><![CDATA[ORANGE_LIGHT := 1;]]></xhtml:p>
+                  </ST>
+                </inline>
+              </action>
+              <action localId="0">
+                <relPosition x="0" y="0"/>
+                <reference name="BLINK_ORANGE_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_GREEN_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="GREEN_LIGHT"/>
+              </action>
+            </actionBlock>
+            <actionBlock localId="9" width="232" height="125">
+              <position x="711" y="250"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="3">
+                  <position x="711" y="265"/>
+                  <position x="628" y="265"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="GREEN_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="ORANGE_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="D" duration="T#2s">
+                <relPosition x="0" y="0"/>
+                <reference name="STOP_CARS"/>
+              </action>
+            </actionBlock>
+            <step localId="10" height="34" width="92" name="RED">
+              <position x="523" y="411"/>
+              <connectionPointIn>
+                <relPosition x="46" y="0"/>
+                <connection refLocalId="6">
+                  <position x="569" y="411"/>
+                  <position x="569" y="378"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="46" y="34"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="92" y="17"/>
+              </connectionPointOutAction>
+            </step>
+            <actionBlock localId="11" width="235" height="103">
+              <position x="710" y="413"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="10">
+                  <position x="710" y="428"/>
+                  <position x="615" y="428"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="ORANGE_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="D" duration="T#2s">
+                <relPosition x="0" y="0"/>
+                <reference name="ALLOW_PEDESTRIANS"/>
+              </action>
+            </actionBlock>
+            <transition localId="12" height="2" width="20">
+              <position x="559" y="533"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="7">
+                  <position x="569" y="533"/>
+                  <position x="569" y="487"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[ALLOW_PEDESTRIANS]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <selectionDivergence localId="15" height="1" width="154">
+              <position x="415" y="335"/>
+              <connectionPointIn>
+                <relPosition x="154" y="0"/>
+                <connection refLocalId="3">
+                  <position x="569" y="335"/>
+                  <position x="569" y="280"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="0" y="1"/>
+              </connectionPointOut>
+              <connectionPointOut formalParameter="">
+                <relPosition x="154" y="1"/>
+              </connectionPointOut>
+            </selectionDivergence>
+            <transition localId="16" height="2" width="20">
+              <position x="405" y="377"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="15">
+                  <position x="415" y="377"/>
+                  <position x="415" y="336"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <reference name="STOP"/>
+              </condition>
+            </transition>
+            <jumpStep localId="17" height="13" width="12" targetName="Standstill">
+              <position x="409" y="418"/>
+              <connectionPointIn>
+                <relPosition x="6" y="0"/>
+                <connection refLocalId="16">
+                  <position x="415" y="418"/>
+                  <position x="415" y="379"/>
+                </connection>
+              </connectionPointIn>
+            </jumpStep>
+            <transition localId="4" height="2" width="20">
+              <position x="400" y="528"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="7">
+                  <position x="410" y="528"/>
+                  <position x="410" y="487"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <reference name="STOP"/>
+              </condition>
+            </transition>
+            <jumpStep localId="5" height="13" width="12" targetName="Standstill">
+              <position x="404" y="553"/>
+              <connectionPointIn>
+                <relPosition x="6" y="0"/>
+                <connection refLocalId="4">
+                  <position x="410" y="553"/>
+                  <position x="410" y="530"/>
+                </connection>
+              </connectionPointIn>
+            </jumpStep>
+            <selectionDivergence localId="7" height="1" width="159">
+              <position x="410" y="486"/>
+              <connectionPointIn>
+                <relPosition x="159" y="0"/>
+                <connection refLocalId="10">
+                  <position x="569" y="486"/>
+                  <position x="569" y="445"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="0" y="1"/>
+              </connectionPointOut>
+              <connectionPointOut formalParameter="">
+                <relPosition x="159" y="1"/>
+              </connectionPointOut>
+            </selectionDivergence>
+            <step localId="18" height="32" width="177" name="PEDESTRIAN_GREEN">
+              <position x="481" y="572"/>
+              <connectionPointIn>
+                <relPosition x="88" y="0"/>
+                <connection refLocalId="12">
+                  <position x="569" y="572"/>
+                  <position x="569" y="535"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="88" y="32"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="177" y="16"/>
+              </connectionPointOutAction>
+            </step>
+            <actionBlock localId="19" width="247" height="110">
+              <position x="708" y="573"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="18">
+                  <position x="708" y="588"/>
+                  <position x="658" y="588"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_GREEN_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="D" duration="T#10s">
+                <relPosition x="0" y="0"/>
+                <reference name="STOP_PEDESTRIANS"/>
+              </action>
+            </actionBlock>
+            <transition localId="20" height="2" width="20">
+              <position x="400" y="653"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="22">
+                  <position x="410" y="653"/>
+                  <position x="410" y="626"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[NOT SWITCH_BUTTON]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <jumpStep localId="21" height="13" width="12" targetName="Standstill">
+              <position x="404" y="694"/>
+              <connectionPointIn>
+                <relPosition x="6" y="0"/>
+                <connection refLocalId="20">
+                  <position x="410" y="694"/>
+                  <position x="410" y="655"/>
+                </connection>
+              </connectionPointIn>
+            </jumpStep>
+            <selectionDivergence localId="22" height="1" width="159">
+              <position x="410" y="625"/>
+              <connectionPointIn>
+                <relPosition x="159" y="0"/>
+                <connection refLocalId="18">
+                  <position x="569" y="625"/>
+                  <position x="569" y="615"/>
+                  <position x="569" y="615"/>
+                  <position x="569" y="604"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="0" y="1"/>
+              </connectionPointOut>
+              <connectionPointOut formalParameter="">
+                <relPosition x="159" y="1"/>
+              </connectionPointOut>
+            </selectionDivergence>
+            <transition localId="23" height="2" width="20">
+              <position x="559" y="709"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="22">
+                  <position x="569" y="709"/>
+                  <position x="569" y="626"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[STOP_PEDESTRIANS]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <step localId="24" height="30" width="148" name="PEDESTRIAN_RED">
+              <position x="495" y="748"/>
+              <connectionPointIn>
+                <relPosition x="74" y="0"/>
+                <connection refLocalId="23">
+                  <position x="569" y="748"/>
+                  <position x="569" y="711"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="74" y="30"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="148" y="15"/>
+              </connectionPointOutAction>
+            </step>
+            <actionBlock localId="25" width="239" height="110">
+              <position x="708" y="748"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="24">
+                  <position x="708" y="763"/>
+                  <position x="643" y="763"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="PEDESTRIAN_GREEN_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="D" duration="T#2s">
+                <relPosition x="0" y="0"/>
+                <reference name="ALLOW_CARS"/>
+              </action>
+            </actionBlock>
+            <transition localId="26" height="2" width="20">
+              <position x="400" y="857"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="28">
+                  <position x="410" y="857"/>
+                  <position x="410" y="816"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <connectionPointIn>
+                  <connection refLocalId="48">
+                    <position x="400" y="858"/>
+                    <position x="290" y="858"/>
+                  </connection>
+                </connectionPointIn>
+              </condition>
+            </transition>
+            <jumpStep localId="27" height="13" width="12" targetName="Standstill">
+              <position x="404" y="898"/>
+              <connectionPointIn>
+                <relPosition x="6" y="0"/>
+                <connection refLocalId="26">
+                  <position x="410" y="898"/>
+                  <position x="410" y="859"/>
+                </connection>
+              </connectionPointIn>
+            </jumpStep>
+            <selectionDivergence localId="28" height="1" width="159">
+              <position x="410" y="815"/>
+              <connectionPointIn>
+                <relPosition x="159" y="0"/>
+                <connection refLocalId="24">
+                  <position x="569" y="815"/>
+                  <position x="569" y="778"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="0" y="1"/>
+              </connectionPointOut>
+              <connectionPointOut formalParameter="">
+                <relPosition x="159" y="1"/>
+              </connectionPointOut>
+            </selectionDivergence>
+            <transition localId="29" height="2" width="20">
+              <position x="559" y="879"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="28">
+                  <position x="569" y="879"/>
+                  <position x="569" y="816"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[ALLOW_CARS]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <step localId="30" height="33" width="92" name="GREEN">
+              <position x="523" y="930"/>
+              <connectionPointIn>
+                <relPosition x="46" y="0"/>
+                <connection refLocalId="29">
+                  <position x="569" y="930"/>
+                  <position x="569" y="881"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="46" y="33"/>
+              </connectionPointOut>
+              <connectionPointOutAction formalParameter="">
+                <relPosition x="92" y="16"/>
+              </connectionPointOutAction>
+            </step>
+            <actionBlock localId="31" width="227" height="110">
+              <position x="709" y="931"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="30">
+                  <position x="709" y="946"/>
+                  <position x="615" y="946"/>
+                </connection>
+              </connectionPointIn>
+              <action localId="0" qualifier="S">
+                <relPosition x="0" y="0"/>
+                <reference name="GREEN_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="R">
+                <relPosition x="0" y="0"/>
+                <reference name="RED_LIGHT"/>
+              </action>
+              <action localId="0" qualifier="D" duration="T#20s">
+                <relPosition x="0" y="0"/>
+                <reference name="WARN_CARS"/>
+              </action>
+            </actionBlock>
+            <block localId="32" width="89" height="94" typeName="TON" instanceName="TON3">
+              <position x="308" y="1053"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="38"/>
+                    <connection refLocalId="44" formalParameter="Q1">
+                      <position x="308" y="1091"/>
+                      <position x="291" y="1091"/>
+                      <position x="291" y="1065"/>
+                      <position x="275" y="1065"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PT">
+                  <connectionPointIn>
+                    <relPosition x="0" y="75"/>
+                    <connection refLocalId="34">
+                      <position x="308" y="1128"/>
+                      <position x="270" y="1128"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="89" y="38"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="ET">
+                  <connectionPointOut>
+                    <relPosition x="89" y="75"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="33" height="36" width="168" negated="false">
+              <position x="15" y="1047"/>
+              <connectionPointOut>
+                <relPosition x="168" y="18"/>
+              </connectionPointOut>
+              <expression>PEDESTRIAN_BUTTON</expression>
+            </inVariable>
+            <inVariable localId="34" height="33" width="53" negated="false">
+              <position x="217" y="1112"/>
+              <connectionPointOut>
+                <relPosition x="53" y="16"/>
+              </connectionPointOut>
+              <expression>T#2s</expression>
+            </inVariable>
+            <block localId="35" width="67" height="60" typeName="OR">
+              <position x="459" y="1061"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="32" formalParameter="Q">
+                      <position x="459" y="1091"/>
+                      <position x="397" y="1091"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="36">
+                      <position x="459" y="1111"/>
+                      <position x="427" y="1111"/>
+                      <position x="427" y="1195"/>
+                      <position x="260" y="1195"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="67" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <inVariable localId="36" height="30" width="97" negated="false">
+              <position x="163" y="1182"/>
+              <connectionPointOut>
+                <relPosition x="97" y="15"/>
+              </connectionPointOut>
+              <expression>WARN_CARS</expression>
+            </inVariable>
+            <transition localId="37" height="2" width="20">
+              <position x="559" y="1090"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="38">
+                  <position x="569" y="1090"/>
+                  <position x="569" y="1060"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <connectionPointIn>
+                  <connection refLocalId="35" formalParameter="OUT">
+                    <position x="559" y="1091"/>
+                    <position x="526" y="1091"/>
+                  </connection>
+                </connectionPointIn>
+              </condition>
+            </transition>
+            <selectionDivergence localId="38" height="1" width="207">
+              <position x="569" y="1059"/>
+              <connectionPointIn>
+                <relPosition x="0" y="0"/>
+                <connection refLocalId="30">
+                  <position x="569" y="1059"/>
+                  <position x="569" y="963"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut formalParameter="">
+                <relPosition x="0" y="1"/>
+              </connectionPointOut>
+              <connectionPointOut formalParameter="">
+                <relPosition x="207" y="1"/>
+              </connectionPointOut>
+            </selectionDivergence>
+            <transition localId="39" height="2" width="20">
+              <position x="766" y="1095"/>
+              <connectionPointIn>
+                <relPosition x="10" y="0"/>
+                <connection refLocalId="38">
+                  <position x="776" y="1095"/>
+                  <position x="776" y="1060"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="10" y="2"/>
+              </connectionPointOut>
+              <condition>
+                <inline name="">
+                  <ST>
+                    <xhtml:p><![CDATA[NOT SWITCH_BUTTON]]></xhtml:p>
+                  </ST>
+                </inline>
+              </condition>
+            </transition>
+            <jumpStep localId="41" height="13" width="12" targetName="ORANGE">
+              <position x="563" y="1137"/>
+              <connectionPointIn>
+                <relPosition x="6" y="0"/>
+                <connection refLocalId="37">
+                  <position x="569" y="1137"/>
+                  <position x="569" y="1092"/>
+                </connection>
+              </connectionPointIn>
+            </jumpStep>
+            <block localId="44" width="51" height="60" typeName="SR" instanceName="SR0">
+              <position x="224" y="1035"/>
+              <inputVariables>
+                <variable formalParameter="S1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="33">
+                      <position x="224" y="1065"/>
+                      <position x="183" y="1065"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="R">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="32" formalParameter="Q">
+                      <position x="224" y="1085"/>
+                      <position x="203" y="1085"/>
+                      <position x="203" y="1167"/>
+                      <position x="416" y="1167"/>
+                      <position x="416" y="1091"/>
+                      <position x="397" y="1091"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q1">
+                  <connectionPointOut>
+                    <relPosition x="51" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <comment localId="45" height="767" width="753">
+              <position x="973" y="21"/>
+              <content>
+                <xhtml:p><![CDATA[*** Description of SFC action qualifiers ***
+
+N : non-stored - The action code body is executed or the Boolean variable is set as
+long as the step is active.
+
+R : overriding reset &#8211; When the step has previously been executed with the S
+(including DS, DS, and SL) qualifier, the R qualifier will stop the execution of the
+code or reset the Boolean variable.
+
+S : set (stored) - The action code body is executed or the Boolean variable is set.
+This state is stored as soon as the step becomes active. It can only be reset
+explicitly by associating the same action to a different step using the qualifier 'R'.
+
+L : time limited - The action code body is executed or the Boolean variable is set as
+long as the step is active but maximal for the fixed time interval.
+
+D : time delayed - The action code body is executed or the Boolean variable is set
+after the fixed delay time has elapsed. The action remains active as long as the step
+is active. If the step is active shorter than the fixed delay time the action does not
+become active.
+
+P : pulse - As soon as the step is active the action code body is executed or the
+Boolean variable is set for one operating cycle. (Note: The code body will then
+execute for one additional operating cycle with the Step.X variable FALSE.)
+
+SD : stored and time delayed - the action code body is executed or the Boolean
+variable is stored and set when the fixed delay time has elapsed after the step
+activation, even if the step becomes inactive. The action remains active until it is
+reset. If the step is active shorter than the fixed delay time the action becomes active
+anyway.
+
+DS : delayed and stored - The action code body is executed or the Boolean variable
+is set when the fixed delay time has elapsed after the step activation. The action
+remains active until it is reset. If the step is active shorter than the fixed delay time
+the action does not become active.
+
+SL : stored and time limited - The action code body is executed or the Boolean
+variable is set and stored for a fixed time interval as soon as the step is active. If the
+step is active shorter than the time interval the action is active for the whole time
+interval anyway. If the action is reset during the time interval the action becomes
+inactive as soon as the action is reset.
+]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="46" height="224" width="375">
+              <position x="8" y="326"/>
+              <content>
+                <xhtml:p><![CDATA[Conditions can be written in any IEC 61131-3 language.
+They can be implemented in defferent ways:
+- reference to external implementation;
+- inline implementation;
+- written in FBD or LD on SFC diagram and connected to the condition.
+
+See below examples of all these types.]]></xhtml:p>
+              </content>
+            </comment>
+            <leftPowerRail localId="47" height="40" width="3">
+              <position x="189" y="838"/>
+              <connectionPointOut formalParameter="">
+                <relPosition x="3" y="20"/>
+              </connectionPointOut>
+            </leftPowerRail>
+            <contact localId="48" height="15" width="21" negated="true">
+              <position x="269" y="850"/>
+              <connectionPointIn>
+                <relPosition x="0" y="8"/>
+                <connection refLocalId="47">
+                  <position x="269" y="858"/>
+                  <position x="192" y="858"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="21" y="8"/>
+              </connectionPointOut>
+              <variable>SWITCH_BUTTON</variable>
+            </contact>
+            <comment localId="13" height="86" width="379">
+              <position x="9" y="28"/>
+              <content>
+                <xhtml:p><![CDATA[Sequential function chart (SFC) is commonly used to describe state machines.]]></xhtml:p>
+              </content>
+            </comment>
+          </SFC>
+        </body>
+      </pou>
+      <pou name="main_program" pouType="program">
+        <interface>
+          <localVars>
+            <variable name="trafic_light_sequence0">
+              <type>
+                <derived name="traffic_light_sequence"/>
+              </type>
+            </variable>
+            <variable name="SwitchButton">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="PedestrianButton">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="RedLight">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="OrangeLight">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="GreenLight">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="PedestrianRedLight">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="PedestrianGreenLight">
+              <type>
+                <derived name="HMI_BOOL"/>
+              </type>
+            </variable>
+            <variable name="Block1">
+              <type>
+                <derived name="python_eval"/>
+              </type>
+            </variable>
+          </localVars>
+        </interface>
+        <body>
+          <FBD>
+            <block localId="1" width="350" height="836" typeName="traffic_light_sequence" instanceName="trafic_light_sequence0" executionOrderId="0">
+              <position x="494" y="462"/>
+              <inputVariables>
+                <variable formalParameter="SWITCH_BUTTON">
+                  <connectionPointIn>
+                    <relPosition x="0" y="101"/>
+                    <connection refLocalId="103">
+                      <position x="494" y="563"/>
+                      <position x="470" y="563"/>
+                      <position x="470" y="564"/>
+                      <position x="446" y="564"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PEDESTRIAN_BUTTON">
+                  <connectionPointIn>
+                    <relPosition x="0" y="264"/>
+                    <connection refLocalId="104">
+                      <position x="494" y="726"/>
+                      <position x="466" y="726"/>
+                      <position x="466" y="727"/>
+                      <position x="438" y="727"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="RED_LIGHT">
+                  <connectionPointOut>
+                    <relPosition x="350" y="101"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="ORANGE_LIGHT">
+                  <connectionPointOut>
+                    <relPosition x="350" y="264"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="GREEN_LIGHT">
+                  <connectionPointOut>
+                    <relPosition x="350" y="427"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="PEDESTRIAN_RED_LIGHT">
+                  <connectionPointOut>
+                    <relPosition x="350" y="590"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="PEDESTRIAN_GREEN_LIGHT">
+                  <connectionPointOut>
+                    <relPosition x="350" y="753"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <comment localId="24" height="287" width="1008">
+              <position x="22" y="13"/>
+              <content>
+                <xhtml:p><![CDATA[This example implements control of traffic lights.
+
+Basically it shows following features of Beremiz:
+- web interface (SCADA) using integrated web server in SVGHMI extension;
+- interaction with web UI;
+- functional blocks in SFC language.
+
+
+
+
+SVGHMI is extensions to build web interface to PLC. It has *integrated* web-server. So it's NOT necessary to install Apache, lighttpd or nginx for that!!!
+
+As the program is running in PLC, web UI will be available at http://localhost:8009/.
+
+Web interface is build as SVG file in Inkscape. To edit SVG file click 'Inkscape' button in 0x: SVGHMI extension. 
+Inkscape is a free and open-source vector graphics editor. It's not part of Beremiz and needs to be installed separately.
+]]></xhtml:p>
+              </content>
+            </comment>
+            <comment localId="102" height="134" width="734">
+              <position x="21" y="303"/>
+              <content>
+                <xhtml:p><![CDATA[In this example FB like 'Button', 'Led' and 'Text' are used. 
+Back_id and sele_id inputs of these blocks are IDs  of graphic primitives in SVG file.
+This is the way how elements in SVG are bound to elements in PLC program.
+You can find out or edit these IDs in Inkscape.]]></xhtml:p>
+              </content>
+            </comment>
+            <inVariable localId="103" executionOrderId="0" height="27" width="106" negated="false">
+              <position x="340" y="551"/>
+              <connectionPointOut>
+                <relPosition x="106" y="13"/>
+              </connectionPointOut>
+              <expression>SwitchButton</expression>
+            </inVariable>
+            <inVariable localId="104" executionOrderId="0" height="27" width="138" negated="false">
+              <position x="300" y="714"/>
+              <connectionPointOut>
+                <relPosition x="138" y="13"/>
+              </connectionPointOut>
+              <expression>PedestrianButton</expression>
+            </inVariable>
+            <outVariable localId="105" executionOrderId="0" height="27" width="74" negated="false">
+              <position x="891" y="551"/>
+              <connectionPointIn>
+                <relPosition x="0" y="13"/>
+                <connection refLocalId="1" formalParameter="RED_LIGHT">
+                  <position x="891" y="564"/>
+                  <position x="867" y="564"/>
+                  <position x="867" y="563"/>
+                  <position x="844" y="563"/>
+                </connection>
+              </connectionPointIn>
+              <expression>RedLight</expression>
+            </outVariable>
+            <outVariable localId="106" executionOrderId="0" height="27" width="98" negated="false">
+              <position x="880" y="714"/>
+              <connectionPointIn>
+                <relPosition x="0" y="13"/>
+                <connection refLocalId="1" formalParameter="ORANGE_LIGHT">
+                  <position x="880" y="727"/>
+                  <position x="862" y="727"/>
+                  <position x="862" y="726"/>
+                  <position x="844" y="726"/>
+                </connection>
+              </connectionPointIn>
+              <expression>OrangeLight</expression>
+            </outVariable>
+            <outVariable localId="107" executionOrderId="0" height="27" width="90" negated="false">
+              <position x="881" y="876"/>
+              <connectionPointIn>
+                <relPosition x="0" y="13"/>
+                <connection refLocalId="1" formalParameter="GREEN_LIGHT">
+                  <position x="881" y="889"/>
+                  <position x="844" y="889"/>
+                </connection>
+              </connectionPointIn>
+              <expression>GreenLight</expression>
+            </outVariable>
+            <outVariable localId="108" executionOrderId="0" height="27" width="154" negated="false">
+              <position x="882" y="1040"/>
+              <connectionPointIn>
+                <relPosition x="0" y="13"/>
+                <connection refLocalId="1" formalParameter="PEDESTRIAN_RED_LIGHT">
+                  <position x="882" y="1053"/>
+                  <position x="863" y="1053"/>
+                  <position x="863" y="1052"/>
+                  <position x="844" y="1052"/>
+                </connection>
+              </connectionPointIn>
+              <expression>PedestrianRedLight</expression>
+            </outVariable>
+            <outVariable localId="109" executionOrderId="0" height="27" width="170" negated="false">
+              <position x="873" y="1203"/>
+              <connectionPointIn>
+                <relPosition x="0" y="13"/>
+                <connection refLocalId="1" formalParameter="PEDESTRIAN_GREEN_LIGHT">
+                  <position x="873" y="1216"/>
+                  <position x="858" y="1216"/>
+                  <position x="858" y="1215"/>
+                  <position x="844" y="1215"/>
+                </connection>
+              </connectionPointIn>
+              <expression>PedestrianGreenLight</expression>
+            </outVariable>
+            <inVariable localId="9" height="30" width="490" executionOrderId="0" negated="false">
+              <position x="874" y="644"/>
+              <connectionPointOut>
+                <relPosition x="490" y="15"/>
+              </connectionPointOut>
+              <expression>'sys.stdout.write("ALL GREEN LIGHTS\n"), sys.stdout.flush()'</expression>
+            </inVariable>
+            <block localId="8" width="125" height="80" typeName="python_eval" instanceName="Block1" executionOrderId="0">
+              <position x="1425" y="594"/>
+              <inputVariables>
+                <variable formalParameter="TRIG">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="1" formalParameter="RED_LIGHT">
+                      <position x="1425" y="629"/>
+                      <position x="866" y="629"/>
+                      <position x="866" y="563"/>
+                      <position x="844" y="563"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="CODE">
+                  <connectionPointIn>
+                    <relPosition x="0" y="65"/>
+                    <connection refLocalId="9">
+                      <position x="1425" y="659"/>
+                      <position x="1356" y="659"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="ACK">
+                  <connectionPointOut>
+                    <relPosition x="125" y="35"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="RESULT">
+                  <connectionPointOut>
+                    <relPosition x="125" y="65"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+          </FBD>
+        </body>
+      </pou>
+    </pous>
+  </types>
+  <instances>
+    <configurations>
+      <configuration name="config">
+        <resource name="resource1">
+          <task name="test_task" interval="T#100ms" priority="0">
+            <pouInstance name="main_instance" typeName="main_program"/>
+          </task>
+        </resource>
+      </configuration>
+    </configurations>
+  </instances>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/svghmi_basic/svghmi_0@svghmi/baseconfnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="svghmi_0"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/svghmi_basic/svghmi_0@svghmi/confnode.xml	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SVGHMI xmlns:xsd="http://www.w3.org/2001/XMLSchema" WatchdogInitial="10" WatchdogInterval="5" EnableWatchdog="true" Path="{name}" OnStart="chromium-browser --no-sandbox  --test-type --single-process --start-fullscreen {url}"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/projects/svghmi_basic/svghmi_0@svghmi/svghmi.svg	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,1546 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="1920"
+   height="1080"
+   id="svg2"
+   version="1.1"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   sodipodi:docname="svghmi.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23716" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23669" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23629" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23580" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23540" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23506" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23466" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23432" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23330" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23257" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23226" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23189" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23118" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23081" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective23044" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22995" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22946" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22891" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22866" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22829" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22795" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22692" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22661" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22630" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22569" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22532" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22501" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22470" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22403" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22318" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22290" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22265" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22090" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective22002" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21911" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21856" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21831" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21776" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21745" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21654" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21626" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21580" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21549" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21518" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21418" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21338" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective21250" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19662" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19613" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19555" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19494" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19325" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19285" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19247" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19201" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19155" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19106" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective19050" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18979" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18945" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18911" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18841" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18807" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18767" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18727" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18693" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18662" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18613" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18555" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18518" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18475" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18429" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18377" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18322" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18273" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18239" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18193" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18150" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18104" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18061" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective18021" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17978" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17950" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17868" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17840" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17812" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17784" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17756" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17728" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17700" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17636" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17605" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17574" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17543" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17512" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17475" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17420" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17386" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17322" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17261" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17212" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17163" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17120" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17074" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective17046" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16994" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16951" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16896" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16856" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16822" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16794" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16766" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16738" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16689" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16640" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16594" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16548" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16493" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16438" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16401" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16370" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16321" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16242" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16187" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16156" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16101" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16061" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective16027" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15972" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15860" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15826" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15789" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15737" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15676" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15627" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15569" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15532" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15477" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15440" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15403" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15360" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15320" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15283" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15207" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15158" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15121" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15084" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective15041" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14998" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14949" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14906" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14863" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14823" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14783" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14743" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14703" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14642" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14572" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14461" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14421" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14365" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14328" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14291" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14254" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14217" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14174" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14137" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14100" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14057" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective14020" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective13983" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective13946" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective13909" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 840.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 840.5 : 1"
+       inkscape:persp3d-origin="0.5 : 840.33333 : 1"
+       id="perspective13862" />
+    <inkscape:perspective
+       id="perspective13880"
+       inkscape:persp3d-origin="372.04724 : 1190.7874 : 1"
+       inkscape:vp_z="744.09448 : 1366.1811 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 1366.1811 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="636.64864"
+     inkscape:cy="149.25004"
+     inkscape:document-units="px"
+     inkscape:current-layer="g220"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="2096"
+     inkscape:window-x="20"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:pagecheckerboard="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(37.474617,-760.93329)">
+    <rect
+       style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:5.19615;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
+       id="rect250"
+       width="1920"
+       height="1080"
+       x="-37.474617"
+       y="760.93329"
+       inkscape:label="HMI:Page:Home" />
+    <path
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#282828;fill-opacity:1;stroke:none;stroke-width:8.16464;marker:none;enable-background:accumulate"
+       d="m 889.54887,843.77425 v 519.99995 h 75.74999 v 374.2501 h 22.87504 v -245.625 h 35.625 v 62.875 h 145.75 v -130 h -145.75 v 48.5 h -35.625 v -110 h 87.125 V 843.77425 Z"
+       id="rect2985"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccccccccccccc" />
+    <rect
+       style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffac2c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8.16464;marker:none;enable-background:accumulate"
+       id="rect3761"
+       width="78.571426"
+       height="75.714287"
+       x="936.70953"
+       y="1596.6492"
+       ry="14.285714" />
+    <g
+       id="g244"
+       inkscape:label="HMI:Switch@/REDLIGHT"
+       transform="matrix(4,0,0,4,432.42386,-2222.7999)">
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.071429,724.14799)"
+         id="RED_OFF"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#505050;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="false" />
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.07143,724.14799)"
+         id="RED_ON"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#e20f10;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="true" />
+    </g>
+    <g
+       id="g248"
+       inkscape:label="HMI:Switch@/ORANGELIGHT"
+       transform="matrix(4,0,0,4,432.42386,-2222.7999)">
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.071429,764.14799)"
+         id="ORANGE_OFF"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#505050;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="false" />
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.07143,764.14799)"
+         id="ORANGE_ON"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#f06414;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="true" />
+    </g>
+    <g
+       id="g240"
+       inkscape:label="HMI:Switch@/GREENLIGHT"
+       transform="matrix(4,0,0,4,432.42386,-2222.7999)">
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.071429,804.14799)"
+         id="GREEN_OFF"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#505050;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="false" />
+      <circle
+         r="13.214286"
+         cy="63.92857"
+         cx="76.071426"
+         transform="translate(61.07143,804.14799)"
+         id="GREEN_ON"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#50a00e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="true" />
+    </g>
+    <g
+       id="g224"
+       inkscape:label="HMI:Button@/PEDESTRIANBUTTON"
+       transform="matrix(4,0,0,4,432.42386,-2382.7999)">
+      <circle
+         r="5.3571429"
+         cy="252.5"
+         cx="136.78572"
+         transform="matrix(1.3666667,0,0,1.3666667,-51.047621,659.24323)"
+         id="PEDESTRIAN_OFF"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#e20f10;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="inactive" />
+      <circle
+         r="5.3571429"
+         cy="252.5"
+         cx="136.78572"
+         transform="matrix(1.2333334,0,0,1.2333334,-32.809525,692.9099)"
+         id="PEDESTRIAN_ON"
+         style="color:#000000;overflow:visible;visibility:visible;fill:#e20f10;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.041;marker:none;enable-background:accumulate"
+         inkscape:label="active" />
+    </g>
+    <g
+       id="g232"
+       inkscape:label="HMI:Switch@/PEDESTRIANREDLIGHT"
+       transform="matrix(4,0,0,4,432.42386,-2302.7999)">
+      <path
+         inkscape:connector-curvature="0"
+         id="PEDESTRIAN_RED_OFF"
+         style="fill:#505050;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 157.9184,937.65033 c 0.98406,-0.0329 1.66207,0.64458 1.66207,1.76564 0,1.1159 -0.57443,1.01655 -0.57443,1.38898 0,0.40492 0.23543,1.04997 1.08294,1.45489 0.9511,0.47555 1.49256,0.71568 1.72797,2.10466 0.20247,1.25196 0.33901,4.06805 0.33901,4.06805 0.0329,0.37197 -0.13654,1.32259 0,1.55848 0.27309,0.50851 -0.0329,1.18652 -0.37196,0.98405 -0.37197,-0.20246 -0.78159,-0.678 -0.67801,-1.04997 0.0989,-0.4101 0,-0.84751 -0.0329,-1.08764 -0.0707,-0.23541 -0.0707,-2.91449 -0.57914,-3.08399 -0.26837,-0.0659 -0.1695,2.26944 0.033,3.45596 0.10358,0.5457 0.10358,3.3566 0.10358,4.27474 0,0.88095 -0.20246,3.31942 -0.0659,3.72953 0.16951,0.37196 1.38898,0.97935 1.38898,1.42193 0,0.44259 -0.57443,0.339 -1.01702,0.27262 -0.40963,-0.0372 -1.89748,-0.64458 -2.00106,-1.76565 -0.10359,-1.11542 -0.13654,-5.08459 -0.57443,-7.2175 l -0.1695,2.26945 c 0,0 -0.0377,3.5925 -0.13654,4.27004 -0.10359,0.71097 -0.40493,1.83204 -0.78159,2.13808 -0.16951,0.13655 -1.69503,0.67801 -1.89749,0.50851 -0.40492,-0.339 -0.40492,-0.6121 0.0707,-0.98406 0.47083,-0.40539 0.71096,-0.61209 0.74392,-1.52552 0.0659,-0.88047 0,-2.60892 -0.0329,-3.28646 -0.0707,-0.71567 -0.13654,-3.69608 -0.10358,-4.10571 0.44259,-2.81091 -0.0989,-3.55955 -0.0989,-3.55955 l -0.40963,2.54254 c -0.033,1.08763 -0.0989,1.42664 -0.13655,2.00059 -0.0659,0.78206 -0.43788,1.18652 -0.71096,0.81455 -0.23543,-0.40444 -0.43788,-1.83109 -0.33901,-2.09994 0.10358,-0.30557 -0.0989,-0.95063 0.0707,-1.69455 0.13183,-0.71568 0.26838,-2.91921 0.30134,-3.18759 0.0706,-0.24013 0.10358,-0.88093 0.88046,-1.2901 0.7816,-0.37196 1.25714,-1.01748 1.35602,-1.55847 0.13655,-0.54147 -0.54147,-1.08341 -0.50851,-1.86453 0.0707,-0.9511 0.24014,-1.6291 1.4596,-1.66206"
+         inkscape:label="false" />
+      <path
+         inkscape:connector-curvature="0"
+         id="PEDESTRIAN_RED_ON"
+         style="fill:#e20f10;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 157.9184,937.65033 c 0.98406,-0.0329 1.66207,0.64458 1.66207,1.76564 0,1.1159 -0.57443,1.01655 -0.57443,1.38898 0,0.40492 0.23543,1.04997 1.08294,1.45489 0.9511,0.47555 1.49256,0.71568 1.72797,2.10466 0.20247,1.25196 0.33901,4.06805 0.33901,4.06805 0.0329,0.37197 -0.13654,1.32259 0,1.55848 0.27309,0.50851 -0.0329,1.18652 -0.37196,0.98405 -0.37197,-0.20246 -0.78159,-0.678 -0.67801,-1.04997 0.0989,-0.4101 0,-0.84751 -0.0329,-1.08764 -0.0707,-0.23541 -0.0707,-2.91449 -0.57914,-3.08399 -0.26837,-0.0659 -0.1695,2.26944 0.033,3.45596 0.10358,0.5457 0.10358,3.3566 0.10358,4.27474 0,0.88095 -0.20246,3.31942 -0.0659,3.72953 0.16951,0.37196 1.38898,0.97935 1.38898,1.42193 0,0.44259 -0.57443,0.339 -1.01702,0.27262 -0.40963,-0.0372 -1.89748,-0.64458 -2.00106,-1.76565 -0.10359,-1.11542 -0.13654,-5.08459 -0.57443,-7.2175 l -0.1695,2.26945 c 0,0 -0.0377,3.5925 -0.13654,4.27004 -0.10359,0.71097 -0.40493,1.83204 -0.78159,2.13808 -0.16951,0.13655 -1.69503,0.67801 -1.89749,0.50851 -0.40492,-0.339 -0.40492,-0.6121 0.0707,-0.98406 0.47083,-0.40539 0.71096,-0.61209 0.74392,-1.52552 0.0659,-0.88047 0,-2.60892 -0.0329,-3.28646 -0.0707,-0.71567 -0.13654,-3.69608 -0.10358,-4.10571 0.44259,-2.81091 -0.0989,-3.55955 -0.0989,-3.55955 l -0.40963,2.54254 c -0.033,1.08763 -0.0989,1.42664 -0.13655,2.00059 -0.0659,0.78206 -0.43788,1.18652 -0.71096,0.81455 -0.23543,-0.40444 -0.43788,-1.83109 -0.33901,-2.09994 0.10358,-0.30557 -0.0989,-0.95063 0.0707,-1.69455 0.13183,-0.71568 0.26838,-2.91921 0.30134,-3.18759 0.0706,-0.24013 0.10358,-0.88093 0.88046,-1.2901 0.7816,-0.37196 1.25714,-1.01748 1.35602,-1.55847 0.13655,-0.54147 -0.54147,-1.08341 -0.50851,-1.86453 0.0707,-0.9511 0.24014,-1.6291 1.4596,-1.66206"
+         inkscape:label="true" />
+    </g>
+    <g
+       id="g228"
+       inkscape:label="HMI:Switch@/PEDESTRIANGREENLIGHT"
+       transform="matrix(4,0,0,4,432.42386,-2302.7999)">
+      <path
+         inkscape:connector-curvature="0"
+         id="PEDESTRIAN_GREEN_OFF"
+         style="fill:#505050;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 171.65012,940.43176 c -0.3444,-0.68878 -0.41136,-2.7886 1.13839,-2.7886 1.10014,0 1.44453,1.82718 1.51628,2.06634 0.067,0.27743 -0.41614,1.23885 0.067,1.68847 0.44962,0.44482 2.03285,1.75542 2.7886,2.78859 0.72226,0.89399 0.89446,4.68227 0.89446,5.0654 0,0.75575 -0.65052,0.82223 -0.86097,-0.0383 -0.33962,-1.37757 -1.06666,-3.20044 -1.58324,-3.61611 0.34439,1.06667 -0.27264,2.54897 -0.13873,3.4439 0.21047,1.31059 0.13873,2.85988 0.86577,3.82176 0.684,0.93273 2.33898,3.37694 2.71685,3.65437 0.41613,0.23868 0.79402,0.61656 0.48311,0.89446 -0.3444,0.23915 -1.65499,1.54975 -1.92763,1.47799 -0.27743,-0.067 -0.0718,-0.58401 0.13871,-0.96141 0,-0.31092 0.067,-0.58355 -0.99969,-1.68847 -1.06665,-1.10012 -2.44421,-3.44389 -2.72163,-4.02744 -0.27743,-0.55533 -0.72227,-0.62182 -1.1384,-0.31138 -0.37787,0.31138 -1.65019,3.16694 -1.8224,3.61656 -0.20568,0.44436 -0.75573,1.96063 -0.75573,2.47721 0,0.51707 0.13392,0.65579 -0.31092,0.89493 -0.4831,0.27743 -1.65497,0.72706 -2.34375,0.21047 -0.1722,-0.27742 0.27742,-0.44961 0.55007,-0.58833 0.2774,-0.13871 0.72225,-1.10013 0.86095,-2.20027 0.13872,-1.1054 0.44963,-2.86082 0.93273,-4.41057 0.44484,-1.54929 1.51627,-1.9989 1.58324,-2.482 0.067,-0.44484 0.6553,-2.06682 0.20567,-3.02776 -0.20567,-0.20567 -0.067,0.2052 -0.72226,0.86098 -0.41135,0.37786 -1.48278,1.41103 -2.23852,1.82238 -0.79402,0.41615 -0.89446,-0.3396 -0.96621,-0.47831 -0.20567,-0.3109 1.65498,-1.54977 1.9324,-1.9324 0.27265,-0.41137 1.06665,-2.13331 1.13362,-2.78909 0.067,-0.65482 1.17187,-2.47721 0.72226,-3.44342"
+         inkscape:label="false" />
+      <path
+         inkscape:connector-curvature="0"
+         id="PEDESTRIAN_GREEN_ON"
+         style="fill:#50a00e;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         d="m 171.65012,940.43176 c -0.3444,-0.68878 -0.41136,-2.7886 1.13839,-2.7886 1.10014,0 1.44453,1.82718 1.51628,2.06634 0.067,0.27743 -0.41614,1.23885 0.067,1.68847 0.44962,0.44482 2.03285,1.75542 2.7886,2.78859 0.72226,0.89399 0.89446,4.68227 0.89446,5.0654 0,0.75575 -0.65052,0.82223 -0.86097,-0.0383 -0.33962,-1.37757 -1.06666,-3.20044 -1.58324,-3.61611 0.34439,1.06667 -0.27264,2.54897 -0.13873,3.4439 0.21047,1.31059 0.13873,2.85988 0.86577,3.82176 0.684,0.93273 2.33898,3.37694 2.71685,3.65437 0.41613,0.23868 0.79402,0.61656 0.48311,0.89446 -0.3444,0.23915 -1.65499,1.54975 -1.92763,1.47799 -0.27743,-0.067 -0.0718,-0.58401 0.13871,-0.96141 0,-0.31092 0.067,-0.58355 -0.99969,-1.68847 -1.06665,-1.10012 -2.44421,-3.44389 -2.72163,-4.02744 -0.27743,-0.55533 -0.72227,-0.62182 -1.1384,-0.31138 -0.37787,0.31138 -1.65019,3.16694 -1.8224,3.61656 -0.20568,0.44436 -0.75573,1.96063 -0.75573,2.47721 0,0.51707 0.13392,0.65579 -0.31092,0.89493 -0.4831,0.27743 -1.65497,0.72706 -2.34375,0.21047 -0.1722,-0.27742 0.27742,-0.44961 0.55007,-0.58833 0.2774,-0.13871 0.72225,-1.10013 0.86095,-2.20027 0.13872,-1.1054 0.44963,-2.86082 0.93273,-4.41057 0.44484,-1.54929 1.51627,-1.9989 1.58324,-2.482 0.067,-0.44484 0.6553,-2.06682 0.20567,-3.02776 -0.20567,-0.20567 -0.067,0.2052 -0.72226,0.86098 -0.41135,0.37786 -1.48278,1.41103 -2.23852,1.82238 -0.79402,0.41615 -0.89446,-0.3396 -0.96621,-0.47831 -0.20567,-0.3109 1.65498,-1.54977 1.9324,-1.9324 0.27265,-0.41137 1.06665,-2.13331 1.13362,-2.78909 0.067,-0.65482 1.17187,-2.47721 0.72226,-3.44342"
+         inkscape:label="true" />
+    </g>
+    <g
+       id="g220"
+       inkscape:label="HMI:ToggleButton@/SWITCHBUTTON"
+       transform="matrix(4,0,0,4,432.42386,-2222.7999)">
+      <rect
+         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000"
+         id="rect1559"
+         width="72.710266"
+         height="59.289749"
+         x="7.6521072"
+         y="774.39429" />
+      <g
+         transform="rotate(-90,37.09909,809.86228)"
+         id="SWITCH_OFF"
+         inkscape:label="inactive">
+        <circle
+           r="16.785715"
+           cy="57.5"
+           cx="37.142857"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e9ddaf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="path4546"
+           transform="translate(0,752.36228)" />
+        <ellipse
+           ry="3.75"
+           rx="16.964287"
+           cy="57.857143"
+           cx="38.214287"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e9ddaf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+           id="path4548"
+           transform="matrix(0.98958091,0,0,1,-0.76159828,752.36228)" />
+        <path
+           sodipodi:type="star"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
+           id="path4552"
+           sodipodi:sides="3"
+           sodipodi:cx="52.142857"
+           sodipodi:cy="89.285713"
+           sodipodi:r1="2.7027807"
+           sodipodi:r2="1.4117311"
+           sodipodi:arg1="-0.0025098425"
+           sodipodi:arg2="1.0446877"
+           inkscape:flatsided="false"
+           inkscape:rounded="0"
+           inkscape:randomized="0"
+           d="m 54.845629,89.27893 -1.993841,1.227603 -2.054443,1.123241 -0.06621,-2.340518 0.05447,-2.34082 2.060055,1.112914 z"
+           transform="matrix(0.65194108,0,0,0.65194108,15.383639,752.1041)"
+           inkscape:transform-center-x="-0.012953186"
+           inkscape:transform-center-y="-0.16341378" />
+      </g>
+      <g
+         id="SWITCH_ON"
+         inkscape:label="active">
+        <circle
+           r="16.785715"
+           cy="57.5"
+           cx="37.142857"
+           transform="translate(0,752.36228)"
+           id="path4576"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e9ddaf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
+        <ellipse
+           ry="3.75"
+           rx="16.964287"
+           cy="57.857143"
+           cx="38.214287"
+           transform="matrix(0.98958091,0,0,1,-0.76159828,752.36228)"
+           id="path4578"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e9ddaf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+        <path
+           inkscape:transform-center-y="-0.16341378"
+           inkscape:transform-center-x="-0.012953186"
+           transform="matrix(0.65194108,0,0,0.65194108,15.383639,752.1041)"
+           d="m 54.845629,89.27893 -1.993841,1.227603 -2.054443,1.123241 -0.06621,-2.340518 0.05447,-2.34082 2.060055,1.112914 z"
+           inkscape:randomized="0"
+           inkscape:rounded="0"
+           inkscape:flatsided="false"
+           sodipodi:arg2="1.0446877"
+           sodipodi:arg1="-0.0025098425"
+           sodipodi:r2="1.4117311"
+           sodipodi:r1="2.7027807"
+           sodipodi:cy="89.285713"
+           sodipodi:cx="52.142857"
+           sodipodi:sides="3"
+           id="path4580"
+           style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
+           sodipodi:type="star" />
+      </g>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 37.67857,786.29085 v 3.75"
+         id="path4582"
+         inkscape:connector-curvature="0" />
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="m 60.982143,810.04086 h -3.75"
+         id="path4582-4"
+         inkscape:connector-curvature="0" />
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.2065px;line-height:0%;font-family:FreeSans;-inkscape-font-specification:'FreeSans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.85055"
+         x="62.640198"
+         y="813.53357"
+         id="text4613"><tspan
+           sodipodi:role="line"
+           id="tspan4615"
+           x="62.640198"
+           y="813.53357"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.2065px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:'FreeSans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.85055">ON</tspan></text>
+      <text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.2065px;line-height:0%;font-family:FreeSans;-inkscape-font-specification:'FreeSans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.85055"
+         x="27.640198"
+         y="784.53357"
+         id="text1542"><tspan
+           sodipodi:role="line"
+           id="tspan1540"
+           x="27.640198"
+           y="784.53357"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.2065px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:'FreeSans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.85055">OFF</tspan></text>
+    </g>
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/Dockerfile	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,114 @@
+#
+# Dockerfile for Beremiz
+# This container is used to run tests for Beremiz
+#
+FROM ubuntu:jammy  
+                                        
+ENV TERM xterm-256color
+    
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+ARG UNAME=testing
+ENV UNAME ${UNAME}
+ARG UID=1000
+ARG GID=1000
+RUN groupadd -g $GID $UNAME
+RUN useradd -m -u $UID -g $GID -s /bin/bash $UNAME
+
+RUN set -xe \
+    && apt-get update \
+    && apt-get install locales \
+    && locale-gen en_US.UTF-8 \
+    && update-locale LANG=en_US.UTF-8
+
+RUN set -xe \
+    && TZ="America/Paris" \
+       DEBIAN_FRONTEND="noninteractive" \
+       apt-get install -y --no-install-recommends \
+               `# to run sikuli` \
+               wget \
+               openjdk-11-jre \
+               libtesseract4 \
+               \
+               `# to run X based tests` \
+               fluxbox \
+               wmctrl xdotool xvfb \
+               x11vnc xterm xnest \
+               materia-gtk-theme \
+               \
+               `# to build tested apps` \
+               build-essential automake flex bison mercurial \
+               libgtk-3-dev libgl1-mesa-dev libglu1-mesa-dev \
+               libpython3.10-dev libssl-dev \
+               python3.10 virtualenv cmake git
+
+
+# force bigger font and flat theme for GTK in order to make OCR more reliable
+RUN mkdir -p /etc/gtk-3.0
+RUN env echo -e '[Settings]\ngtk-font-name=FreeSans,12\ngtk-theme-name=Materia\n' > /etc/gtk-3.0/settings.ini
+
+# link obtained from https://raiman.github.io/SikuliX1/downloads.html
+RUN set -xe && \
+    wget -qP /usr/local/bin \
+        https://launchpad.net/sikuli/sikulix/2.0.5/+download/sikulixide-2.0.5.jar && \
+    echo 0795f1e0866ee5a7a84e4c89793ea78c /usr/local/bin/sikulixide-2.0.5.jar | md5sum -c && \
+    ( echo '#!/bin/sh' && \
+      echo "exec java -jar /usr/local/bin/sikulixide-*.jar \"\$@\"" \
+    ) | install /dev/stdin /usr/local/bin/sikulix
+
+
+### SVGHMI dependencies : Chromium browser + Inkscape ###
+#
+# On ubuntu chromium is distrinuted as a snap.
+# Running snapd on docker is a mess.
+# As a workaround, there is a PPA where chromium .deb packges build is still beeing maintained :
+#
+# https://launchpad.net/~savoury1/+archive/ubuntu/chromium
+#     ppa:savoury1, maintained by Rob Savoury at the time of writing
+#
+# As a side effect of docker limitations, chromium need --no-sandbox command line argument.
+#
+RUN apt-get install -qqy --no-install-recommends gnupg software-properties-common \
+    && add-apt-repository -y ppa:savoury1/chromium \
+    && add-apt-repository -y ppa:savoury1/ffmpeg4 \
+    && apt-get install -qqy --no-install-recommends chromium-browser inkscape
+
+# easy to remember 'do_tests' alias to invoke main makefile
+RUN env echo -e '#!/bin/bash\nmake -f /home/testing/src/beremiz/tests/Makefile $*' > /usr/local/bin/do_tests
+RUN chmod +x /usr/local/bin/do_tests
+
+USER $UNAME
+
+RUN mkdir /home/$UNAME/build /home/$UNAME/src /home/$UNAME/test
+
+RUN virtualenv ~/beremizenv
+
+COPY requirements.txt /home/$UNAME
+
+# wxpython build requirements
+RUN ~/beremizenv/bin/pip install `grep gattrdict /home/$UNAME/requirements.txt`
+
+# beremiz python requirements
+RUN ~/beremizenv/bin/pip install -r /home/$UNAME/requirements.txt
+
+# tests python requirements
+RUN ~/beremizenv/bin/pip install \
+        pytest pytest-timeout ddt
+        
+#TODO sslpsk posix_spawn
+     
+RUN set -xe && \
+    cd  /home/$UNAME && mkdir tessdata && \
+    wget -q https://github.com/tesseract-ocr/tessdata/archive/refs/tags/4.1.0.tar.gz \
+         -O tessdata.tar.gz && \
+    echo 89e25c7c40a59be7195422a01f57fcb2 tessdata.tar.gz | md5sum -c && \
+    tar --strip-components=1 -C tessdata -x -v -z -f tessdata.tar.gz && \
+    rm tessdata.tar.gz
+
+ENV TESSDATAPATH /home/$UNAME/tessdata
+
+# Points to python binary that test will use
+ENV BEREMIZPYTHONPATH /home/$UNAME/beremizenv/bin/python
+
--- a/tests/tools/Docker/beremiz-requirements/Dockerfile	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-#
-# 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/build_docker_image.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+echo "Building docker image"
+cp -f ../../../requirements.txt requirements.txt
+docker build \
+    --build-arg UID=$(id -u) \
+    --build-arg GID=$(id -g) \
+    -t beremiz_sikuli .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/clean_docker_container.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# delete container
+docker rm beremiz_sikuli_current
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/clean_docker_image.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+# delete image
+docker rmi beremiz_sikuli
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/create_docker_container.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+# source directory containing beremiz, matiec, etc..
+SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd ../../../.. && pwd )"
+echo "SOURCE direcory : $SRCDIR"
+
+# absolute path to test directory. ~/test if not given as only argument
+TESTDIR=${1:-~/test}
+mkdir -p $TESTDIR
+echo "TEST direcory : $TESTDIR"
+
+UNAME=testing
+UHOME=/home/$UNAME
+
+# define TESTDEBUG in env to enable dev-mode. This enables :
+#   - debug pasthrough for Xnest
+#   - VNC port passthrough
+DEBUGARGS="-v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 -e DISPLAY=$DISPLAY -p 5900:5900"
+
+echo "Creating docker container"
+docker create \
+       --name beremiz_sikuli_current \
+       -v $SRCDIR:$UHOME/src \
+       -v $TESTDIR:$UHOME/test \
+       `if [ "$TESTDEBUG" == "YES" ]; then echo $DEBUGARGS; fi` \
+       -w $UHOME/test \
+       -i -t beremiz_sikuli /bin/bash
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/do_test_in_docker.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+CONTAINER=beremiz_sikuli_current
+
+docker stop $CONTAINER
+docker start $CONTAINER 
+docker exec $CONTAINER bash -c "do_tests $1"
+docker stop $CONTAINER
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/enter_docker.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+CONTAINER=beremiz_sikuli_current
+
+docker start -i $CONTAINER
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/enter_docker_as_root.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+CONTAINER=beremiz_sikuli_current
+
+docker start $CONTAINER
+docker exec -i -t -u root $CONTAINER bash
+docker stop $CONTAINER
--- a/tests/tools/Docker/python2.7-wxpython/Dockerfile	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#
-# 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tools/Docker/rebuild_docker.sh	Wed Oct 04 16:24:00 2023 +0200
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+./clean_docker_container.sh || true
+./clean_docker_image.sh || true
+./build_docker_image.sh
+./create_docker_container.sh $1
+
--- a/tests/tools/conftest.py	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-#!/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) 2017: Andrey Skvortsov
-#
-# 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.
-
-
-from __future__ import absolute_import
-import os
-import sys
-
-# import pytest
-# import xvfbwrapper
-
-
-def init_environment():
-    """Append module root directory to sys.path"""
-    try:
-        import Beremiz as _Beremiz
-    except ImportError:
-        sys.path.append(
-            os.path.abspath(
-                os.path.join(
-                    os.path.dirname(__file__), '..', '..')
-            )
-        )
-
-
-init_environment()
-
-#
-# Something seems to be broken in Beremiz application,
-# because after tests in test_application.py during Xvfb shutdown
-# pytest returns error message:
-# pytest: Fatal IO error 11 (Die Ressource ist zur Zeit nicht verfügbar) on X server :2821.
-#
-# As a result of that pytest returns code 1 as some tests were failed,
-# but they aren't.
-#
-# To avoid this Xvfb is launched and killed not by pytest.
-# $ Xvfb :42 -screen 0 1280x1024x24 &
-# $ export DISPLAY=:42
-# $ pytest --timeout=10 ./tests/tools
-# $ pkill -9 Xvfb
-#
-# TODO: find root of this problem.
-
-
-# vdisplay = None
-#
-# @pytest.fixture(scope="session", autouse=True)
-# def start_xvfb_server(request):
-#     global vdisplay
-#     vdisplay = xvfbwrapper.Xvfb(width=1280, height=720)
-#     vdisplay.start()
-#     request.addfinalizer(stop_xvfb_server)
-#
-# def stop_xvfb_server():
-#     if vdisplay is not None:
-#         vdisplay.stop()
--- a/tests/tools/run_python_tests.sh	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#!/bin/sh
-
-
-
-cleanup()
-{
-    find $PYTEST_DIR -name '*.pyc' -delete
-}
-
-
-
-print_help()
-{
-    echo "Usage: run_python_tests.sh [--on-local-xserver]"
-    echo ""
-    echo "--on-local-xserver"
-    echo "                all tests are run on local X-server. "
-    echo "                User can see test in action."
-    echo "                Any interaction (mouse, keyboard) should be avoided"
-    echo "                By default without arguments script runs pytest on virtual X serverf."
-    echo ""
-
-    exit 1
-}
-
-main()
-{
-    LC_ALL=ru_RU.utf-8
-    PYTEST_DIR=./tests/tools
-
-    if [ ! -d $PYTEST_DIR ]; then
-	echo "Script should be run from top directory in repository"
-	exit 1;
-    fi
-
-    use_xvfb=0
-    if [ "$1" != "--on-local-xserver" ]; then
-	export DISPLAY=:42
-	use_xvfb=1
-	Xvfb $DISPLAY -screen 0 1280x1024x24 &
-	sleep 1
-    fi
-
-
-    cleanup
-
-    ret=0
-    DELAY=400
-    KILL_DELAY=$(($DELAY + 30))
-    timeout -k $KILL_DELAY $DELAY pytest --timeout=10 ./tests/tools
-    ret=$?
-
-    cleanup
-
-    [ $use_xvfb = 1 ] && pkill -9 Xvfb
-    exit $ret
-}
-
-
-[ "$1" = "--help" -o "$1" = "-h" ] && print_help
-main $@
--- a/tests/tools/test_CustomIntCtrl.py	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +0,0 @@
-#!/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) 2017: Andrey Skvortsov
-#
-# 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.
-
-
-from __future__ import absolute_import
-from __future__ import division
-import unittest
-import time
-
-import wx
-import conftest
-import controls.CustomIntCtrl
-
-
-class TestCustomIntCtrl(unittest.TestCase):
-    def setUp(self):
-        self.app = wx.App()
-        self.frame = wx.Frame(None)
-
-    def tearDown(self):
-        self.frame.Destroy()
-        wx.CallAfter(self.app.Exit)
-        self.app.MainLoop()
-
-    def testMaxLimit(self):
-        """Test working upper bound"""
-        self.AddControls()
-        self.int_ctrl.SetValue(self.max_val + 100)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-        self.assertEqual(self.int_ctrl.GetValue(), self.max_val)
-
-    def testMinLimit(self):
-        """Test working lower bound"""
-        self.AddControls()
-        self.int_ctrl.SetValue(self.min_val - 100)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), self.min_val)
-
-    def testCorrectValue(self):
-        """Test case if no limiting is necessary"""
-        self.AddControls()
-        val = (self.max_val + self.min_val) // 2
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), val)
-
-    def testEventBinding(self):
-        """Test event sending after edit and bound checks are done"""
-        self.AddControls()
-        self.event_happend = False
-
-        def EventHandler(event):
-            self.event_happend = True
-            event.Skip()
-
-        self.int_ctrl.Bind(controls.CustomIntCtrl.EVT_CUSTOM_INT, EventHandler)
-
-        val = (self.max_val + self.min_val) // 2
-
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-        self.txt_ctrl.SetFocus()
-
-        self.ProcessEvents()
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(self.int_ctrl.GetValue(), val)
-        self.assertTrue(self.event_happend)
-
-    def testLongNumbers(self):
-        """Test support of long integer"""
-        self.AddControls()
-        val = 40000000000
-        self.int_ctrl.SetMax(val)
-        self.int_ctrl.SetValue(val)
-        self.ProcessEvents()
-
-        self.txt_ctrl.SetFocus()
-        self.ProcessEvents()
-
-        self.assertEqual(val, val)
-
-    def ProcessEvents(self):
-        for dummy in range(0, 10):
-            wx.Yield()
-            time.sleep(0.01)
-
-    def AddControls(self):
-        vs = wx.BoxSizer(wx.VERTICAL)
-        self.int_ctrl = controls.CustomIntCtrl(self.frame)
-        self.txt_ctrl = wx.TextCtrl(self.frame)
-        vs.Add(self.int_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
-        vs.Add(self.txt_ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
-        self.frame.SetSizer(vs)
-        vs.Fit(self.frame)
-        self.frame.Show()
-        self.frame.Raise()
-
-        self.min_val = 50
-        self.max_val = 100
-        self.int_ctrl.SetBounds(self.min_val, self.max_val)
-        self.ProcessEvents()
-
-
-if __name__ == '__main__':
-    conftest.init_environment()
-    unittest.main()
--- a/tests/tools/test_application.py	Fri Aug 18 11:38:20 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-#!/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) 2017: Andrey Skvortsov
-#
-# 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.
-
-
-from __future__ import absolute_import
-from __future__ import print_function
-import os
-import sys
-import unittest
-import time
-
-import six
-import pytest
-import wx
-import ddt
-
-import conftest
-import Beremiz
-import PLCOpenEditor
-
-
-class UserApplicationTest(unittest.TestCase):
-    def InstallExceptionHandler(self):
-        def handle_exception(e_type, e_value, e_traceback, exit=False):
-            # traceback.print_exception(e_type, e_value, e_traceback)
-            self.exc_info = [e_type, e_value, e_traceback]
-        self.exc_info = None
-        self.old_excepthook = sys.excepthook
-        sys.excepthook = handle_exception
-
-    def StartApp(self):
-        self.app = None
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        self.app = None
-
-    def setUp(self):
-        self.app = None
-
-    def tearDown(self):
-        if self.app is not None and self.app.frame is not None:
-            self.FinishApp()
-
-    def RunUIActions(self, actions):
-        for act in actions:
-            wx.CallAfter(*act)
-            self.ProcessEvents()
-
-    def CheckForErrors(self):
-        if self.exc_info is not None:
-            # reraise catched previously exception
-            exc_type = self.exc_info[0]
-            exc_value = self.exc_info[1]
-            exc_traceback = self.exc_info[2]
-            six.reraise(exc_type, exc_value, exc_traceback)
-
-    def ProcessEvents(self):
-        for dummy in range(0, 30):
-            self.CheckForErrors()
-            wx.Yield()
-            time.sleep(0.01)
-
-
-@ddt.ddt
-class BeremizApplicationTest(UserApplicationTest):
-    """Test Beremiz as whole application"""
-
-    def StartApp(self):
-        self.app = Beremiz.BeremizIDELauncher()
-        # disable default exception handler in Beremiz
-        self.app.InstallExceptionHandler = lambda: None
-        self.InstallExceptionHandler()
-        self.app.handle_exception = sys.excepthook
-        self.app.PreStart()
-        self.ProcessEvents()
-        self.app.frame.Show()
-        self.ProcessEvents()
-        self.app.frame.ShowFullScreen(True)
-        self.ProcessEvents()
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        time.sleep(1)
-        self.app = None
-
-    def GetSkippedProjectTreeItems(self):
-        """
-        Returns the list of skipped items in the project tree.
-
-        Beremiz test don't need to skip any elemnts in the project tree.
-        """
-        return []
-
-    def OpenAllProjectElements(self):
-        """Open editor for every object in the project tree"""
-        self.app.frame.ProjectTree.ExpandAll()
-        self.ProcessEvents()
-        item = self.app.frame.ProjectTree.GetRootItem()
-        skip = self.GetSkippedProjectTreeItems()
-        tree_id = self.app.frame.ProjectTree.GetId()
-        while item is not None:
-            self.app.frame.ProjectTree.SelectItem(item, True)
-            self.ProcessEvents()
-            if item not in skip:
-                event = wx.lib.agw.customtreectrl.TreeEvent(
-                    wx.lib.agw.customtreectrl.wxEVT_TREE_ITEM_ACTIVATED,
-                    tree_id, item)
-                self.app.frame.OnProjectTreeItemActivated(event)
-            self.ProcessEvents()
-            item = self.app.frame.ProjectTree.GetNextVisible(item)
-
-    def CheckTestProject(self, project):
-        sys.argv = ["", project]
-        self.StartApp()
-        self.OpenAllProjectElements()
-        user_actions = self.GetUserActions()
-        self.RunUIActions(user_actions)
-        self.FinishApp()
-
-    def GetProjectPath(self, project):
-        return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", project))
-
-    def GetUserActions(self):
-        """
-        Returns list of user actions that will be executed
-        on every test project by testCheckProject test.
-        """
-        user_actions = [
-            [self.app.frame.SwitchFullScrMode, None],
-            [self.app.frame.SwitchFullScrMode, None],
-            [self.app.frame.CTR._Clean],
-            [self.app.frame.CTR._Build],
-            [self.app.frame.CTR._Connect],
-            [self.app.frame.CTR._Transfer],
-            [self.app.frame.CTR._Run],
-            [self.app.frame.CTR._Stop],
-            [self.app.frame.CTR._Disconnect],
-            [self.app.frame.CTR._Clean],
-        ]
-        return user_actions
-
-    def testStartUp(self):
-        """Checks whether the app starts and finishes correctly"""
-        sys.argv = [""]
-        self.StartApp()
-        self.FinishApp()
-
-    @ddt.data(
-        "first_steps",
-        "logging",
-        "traffic_lights",
-        "wxGlade",
-        "python",
-        "wiimote",
-        "wxHMI",
-    )
-    @pytest.mark.timeout(30)
-    def testCheckProject(self, name):
-        """
-        Checks that test PLC project can be open,
-        compiled and run on SoftPLC.
-        """
-        project = self.GetProjectPath(name)
-        print("Testing example " + name)
-        self.CheckTestProject(project)
-
-
-class PLCOpenEditorApplicationTest(BeremizApplicationTest):
-    """Test PLCOpenEditor as whole application"""
-
-    def StartApp(self):
-        self.app = PLCOpenEditor.PLCOpenEditorApp()
-        # disable default exception handler in application
-        self.app.InstallExceptionHandler = lambda: None
-        self.InstallExceptionHandler()
-        self.app.Show()
-        self.ProcessEvents()
-        self.app.frame.ShowFullScreen(True)
-        self.ProcessEvents()
-
-    def FinishApp(self):
-        wx.CallAfter(self.app.frame.Close)
-        self.app.MainLoop()
-        time.sleep(1)
-        self.app = None
-
-    def GetSkippedProjectTreeItems(self):
-        """
-        Returns the list of skipped items in the project tree.
-
-        Root item opens dialog window for project settings.
-        To avoid code that handles closing dialog windows just skip this item.
-        """
-        return [self.app.frame.ProjectTree.GetRootItem()]
-
-    def GetUserActions(self):
-        return []
-
-    def GetProjectPath(self, project):
-        """Open PLC program in every Beremiz test project"""
-        project_dir = BeremizApplicationTest.GetProjectPath(self, project)
-        return os.path.join(project_dir, "plc.xml")
-
-
-if __name__ == '__main__':
-    conftest.init_environment()
-    unittest.main()
--- a/util/BitmapLibrary.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/BitmapLibrary.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 
 import wx
@@ -71,7 +71,7 @@
             height = max(bmp1.GetHeight(), bmp2.GetHeight())
 
             # Create bitmap with both icons
-            bmp = wx.EmptyBitmap(width, height)
+            bmp = wx.Bitmap(width, height)
             dc = wx.MemoryDC()
             dc.SelectObject(bmp)
             dc.Clear()
--- a/util/ExceptionHandler.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/ExceptionHandler.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 import sys
 import time
@@ -49,7 +49,7 @@
         trcbck_lst.append(trcbck)
 
     # Allow clicking....
-    cap = wx.Window_GetCapture()
+    cap = wx.Window.GetCapture()
     if cap:
         cap.ReleaseMouse()
 
@@ -81,19 +81,25 @@
 
 
 def get_last_traceback(tb):
-    while tb.tb_next:
-        tb = tb.tb_next
+    while True:
+        if not hasattr(tb, "tb_next"):
+            break
+        if tb.tb_next:
+            tb = tb.tb_next
+        else:
+            break
+
     return tb
 
 
 def format_namespace(d, indent='    '):
-    return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])
+    return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.items()])
 
 
 ignored_exceptions = []  # a problem with a line in a module is only reported once per session
 
 
-def AddExceptHook(app_version='[No version]'):
+def AddExceptHook(app_version='[No version]', logf = None):
 
     def save_bug_report(e_type, e_value, e_traceback, bug_report_path, date):
         info = {
@@ -111,7 +117,8 @@
         if e_traceback:
             info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
             last_tb = get_last_traceback(e_traceback)
-            exception_locals = last_tb.tb_frame.f_locals  # the locals at the level of the stack trace where the exception actually occurred
+            # save locals at the level of the stack trace where the exception actually occurred
+            exception_locals = last_tb.tb_frame.f_locals if last_tb else {};
             info['locals'] = format_namespace(exception_locals)
             if 'self' in exception_locals:
                 try:
@@ -122,16 +129,20 @@
         if not os.path.exists(path):
             os.mkdir(path)
         output = open(bug_report_path, 'w')
-        lst = info.keys()
+        lst = list(info.keys())
         lst.sort()
         for a in lst:
-            output.write(a + ":\n" + str(info[a]) + "\n\n")
+            line = a + ":\n" + str(info[a]) + "\n\n"
+            output.write(line)
+            if logf is not None:
+                logf.write(line)
         output.close()
 
     def handle_exception(e_type, e_value, e_traceback, exit=False):
         traceback.print_exception(e_type, e_value, e_traceback)  # this is very helpful when there's an exception in the rest of this func
         last_tb = get_last_traceback(e_traceback)
-        ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
+        ex = (last_tb.tb_frame.f_code.co_filename if last_tb else "unknown",
+              last_tb.tb_frame.f_lineno if last_tb else None)
         if ex not in ignored_exceptions:
             ignored_exceptions.append(ex)
             date = time.ctime()
--- a/util/MiniTextControler.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/MiniTextControler.py	Wed Oct 04 16:24:00 2023 +0200
@@ -27,7 +27,7 @@
 """
 
 
-from __future__ import absolute_import
+
 import os
 
 
--- a/util/ProcessLogger.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/ProcessLogger.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import os
 import sys
 import subprocess
@@ -47,6 +46,7 @@
         self.callback = callback
         self.endcallback = endcallback
         self.fd = fd
+        self.daemon = True
 
     def run(self):
         outchunk = None
@@ -78,7 +78,7 @@
                  no_stdout=False, no_stderr=False, no_gui=True,
                  timeout=None, outlimit=None, errlimit=None,
                  endlog=None, keyword=None, kill_it=False, cwd=None,
-                 encoding=None, output_encoding=None):
+                 encoding=None, output_encoding=None, env=None):
         self.logger = logger
         if not isinstance(Command, list):
             self.Command_str = Command
@@ -91,7 +91,7 @@
                 else:
                     self.Command.append(word)
         else:
-            self.Command = Command
+            self.Command = [x if type(x)==str else x.decode() for x in Command]
             self.Command_str = subprocess.list2cmdline(self.Command)
 
         fsencoding = sys.getfilesystemencoding()
@@ -99,8 +99,7 @@
 
         if encoding is None:
             encoding = fsencoding
-        self.Command = [self.Command[0].encode(fsencoding)]+map(
-            lambda x: x.encode(encoding), self.Command[1:])
+        self.Command = [self.Command[0].encode(fsencoding)]+[x.encode(encoding) for x in self.Command[1:]]
 
         self.finish_callback = finish_callback
         self.no_stdout = no_stdout
@@ -123,7 +122,8 @@
             "cwd":    os.getcwd() if cwd is None else cwd,
             "stdin":  subprocess.PIPE,
             "stdout": subprocess.PIPE,
-            "stderr": subprocess.PIPE
+            "stderr": subprocess.PIPE,
+            "env":    env
         }
 
         if no_gui and os.name in ("nt", "ce"):
@@ -142,7 +142,7 @@
         if _debug and self.logger:
             self.logger.write("(DEBUG) launching:\n" + self.Command_str + "\n")
 
-        self.Proc = subprocess.Popen(self.Command, **popenargs)
+        self.Proc = subprocess.Popen(self.Command, encoding="utf-8", **popenargs)
 
         self.outt = outputThread(
             self.Proc,
--- a/util/TranslationCatalogs.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/TranslationCatalogs.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,9 +23,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
 import os
-from six.moves import builtins
+import builtins
 import wx
 
 
--- a/util/misc.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/misc.py	Wed Oct 04 16:24:00 2023 +0200
@@ -27,7 +27,7 @@
 """
 
 
-from __future__ import absolute_import
+
 import os,sys
 import random
 from functools import reduce
--- a/util/paths.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/util/paths.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,19 +23,16 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
 
-from __future__ import absolute_import
 import os
 import sys
-from builtins import str as text
 
 def AbsFile(file):
     if isinstance(file, str):
-        file = text(file, sys.getfilesystemencoding())
+        file = str(file, sys.getfilesystemencoding())
     return file
 
 
 def AbsDir(file):
-    file = AbsFile(file)
     return os.path.dirname(os.path.realpath(file))
 
 
@@ -55,3 +52,8 @@
     """
     return os.path.join(AbsParentDir(__file__, 2), name)
 
+def Bpath(*names):
+    """
+    Return path of files in Beremiz project
+    """
+    return os.path.join(AbsParentDir(__file__, 1), *names)
--- a/version.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/version.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,15 +23,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import unicode_literals
 
-import subprocess
+
+
+
 import os
 
-import util.paths as paths
-
-
 def GetCommunityHelpMsg():
     return _(
         "The best place to ask questions about Beremiz/PLCOpenEditor\n"
@@ -45,54 +42,25 @@
     )
 
 
-def GetAppRevision():
-    rev = None
-    app_dir = paths.AbsDir(__file__)
-    try:
-        pipe = subprocess.Popen(
-            ["hg", "id", "-i"],
-            stdout=subprocess.PIPE,
-            cwd=app_dir
-        )
-        rev = pipe.communicate()[0]
-        if pipe.returncode != 0:
-            rev = None
-    except Exception:
-        pass
+def GetAboutDialogInfo(info):
 
-    # if this is not mercurial repository
-    # try to read revision from file
-    if rev is None:
-        try:
-            f = open(os.path.join(app_dir, "revision"))
-            rev = f.readline()
-        except Exception:
-            pass
-    return rev
-
-
-def GetAboutDialogInfo():
-    import wx
-    info = wx.AboutDialogInfo()
-
-    info.Name = "Beremiz"
     info.Version = app_version
 
     info.Copyright = ""
+    info.Copyright += "(C) 2006-2023 Edouard Tisserant\n"
+    info.Copyright += "(C) 2003-2023 Mario de Sousa\n"
+    info.Copyright += "(C) 2022-2023 GP Orcullo\n"
     info.Copyright += "(C) 2016-2018 Andrey Skvortsov\n"
-    info.Copyright += "(C) 2008-2018 Eduard Tisserant\n"
-    info.Copyright += "(C) 2008-2015 Laurent Bessard"
+    info.Copyright += "(C) 2006-2013 Laurent Bessard\n"
 
     info.WebSite = ("http://beremiz.org", "beremiz.org")
 
-    info.Description = _("Open Source framework for automation, "
-                         "implemented IEC 61131 IDE with constantly growing set of extensions "
-                         "and flexible PLC runtime.")
-
     info.Developers = (
+        "Edouard Tisserant <contact@beremiz.fr>",
+        "Mario de Sousa <msousa@fe.up.pt>",
+        "GP Orcullo <kinsamanka@gmail.com>",
         "Andrey Skvortsov <andrej.skvortzov@gmail.com>",
         "Sergey Surkov <surkov.sv@summatechnology.ru>",
-        "Edouard Tisserant <edouard.tisserant@gmail.com>",
         "Laurent Bessard <laurent.bessard@gmail.com>")
 
     info.License = (
@@ -112,14 +80,12 @@
     )
 
     # read license file
-    path = paths.AbsDir(__file__)
-    license_path = os.path.join(path, "COPYING")
+    license_path = os.path.join(
+        os.path.dirname(os.path.realpath(__file__)), "COPYING")
     if os.path.exists(license_path):
         with open(license_path) as f:
             info.License += f.read()
 
-    info.Icon = wx.Icon(os.path.join(path, "images", "about_brz_logo.png"), wx.BITMAP_TYPE_PNG)
-
     info.Translators = (
         "Basque",
         "José Miguel Andonegi <jm.andonegi@gmail.com>, 2019",
@@ -135,6 +101,7 @@
         "  Yiwei Yan <523136664@qq.com>, 2018",
         "  Ji Wang <2485567515@qq.com>, 2019",
         "  珂 曾 <15627997@qq.com>, 2019",
+        "  Gastonfeng<gastonfeng@gmail.com>, 2019",
         "",
 
         "Dutch (Netherlands)",
@@ -218,7 +185,7 @@
     return info
 
 
-app_version = "1.2"
-rev = GetAppRevision()
-if rev is not None:
-    app_version = app_version + "-" + rev.rstrip()
+app_version = "1.4-beta2"
+
+if __name__ == "__main__":
+    print(app_version)
--- a/wxglade_hmi/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/wxglade_hmi/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -22,5 +22,5 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-from __future__ import absolute_import
+
 from wxglade_hmi.wxglade_hmi import *
--- a/wxglade_hmi/wxglade_hmi.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/wxglade_hmi/wxglade_hmi.py	Wed Oct 04 16:24:00 2023 +0200
@@ -24,7 +24,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
+
 import os
 import sys
 import shutil
--- a/xmlclass/__init__.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/xmlclass/__init__.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,7 +23,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 # Package initialisation
-from __future__ import absolute_import
+
 from .xmlclass import (ClassFactory,
                        GenerateParser,
                        DefaultElementClass,
--- a/xmlclass/xmlclass.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/xmlclass/xmlclass.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,8 +23,6 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
 import re
 import datetime
@@ -32,10 +30,7 @@
 from xml.dom import minidom
 from xml.sax.saxutils import unescape
 from collections import OrderedDict
-from builtins import str as text
-
-from six import string_types
-from six.moves import xrange
+
 from lxml import etree
 
 
@@ -107,7 +102,7 @@
 [
     SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE,
     ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT,
-] = range(13)
+] = list(range(13))
 
 
 def NotSupportedYet(type):
@@ -128,7 +123,7 @@
     """
     first = indent * 2
     second = first + len(balise) + 1
-    return u'\t'.expandtabs(first), u'\t'.expandtabs(second)
+    return '\t'.expandtabs(first), '\t'.expandtabs(second)
 
 
 def GetAttributeValue(attr, extract=True):
@@ -141,14 +136,14 @@
     if not extract:
         return attr
     if len(attr.childNodes) == 1:
-        return text(unescape(attr.childNodes[0].data))
+        return str(unescape(attr.childNodes[0].data))
     else:
         # content is a CDATA
-        txt = u''
+        txt = ''
         for node in attr.childNodes:
-            if not (node.nodeName == "#text" and node.data.strip() == u''):
-                txt += text(unescape(node.data))
-        return text
+            if not (node.nodeName == "#text" and node.data.strip() == ''):
+                txt += str(unescape(node.data))
+        return txt
 
 
 def GetNormalizedString(attr, extract=True):
@@ -576,7 +571,7 @@
         "extract": ExtractAny,
         "generate": GenerateAny,
         "initial": InitialAny,
-        "check": lambda x: isinstance(x, (string_types, etree.ElementBase))
+        "check": lambda x: isinstance(x, (str, etree.ElementBase))
     }
 
 
@@ -598,17 +593,21 @@
         else:
             return ""
 
+    def Initial():
+        p = etree.Element(infos["name"])
+        return p
+
     return {
         "type": TAG,
         "extract": ExtractTag,
         "generate": GenerateTag,
-        "initial": lambda: None,
+        "initial": Initial,
         "check": lambda x: x is None or infos["minOccurs"] == 0 and x
     }
 
 
 def FindTypeInfos(factory, infos):
-    if isinstance(infos, string_types):
+    if isinstance(infos, str):
         namespace, name = DecomposeQualifiedName(infos)
         return factory.GetQualifiedNameInfos(name, namespace)
     return infos
@@ -630,7 +629,7 @@
                 if infos["type"] != ANY:
                     DefaultElementClass.__setattr__(value, "tag", element_name)
                 return value
-        return [initial_value() for dummy in xrange(infos["minOccurs"])]
+        return [initial_value() for dummy in range(infos["minOccurs"])]
     else:
         return []
 
@@ -687,13 +686,13 @@
             choices_dict[choice_name] = infos
     prefix = ("%s:" % factory.TargetNamespace
               if factory.TargetNamespace is not None else "")
-    choices_xpath = "|".join(map(lambda x: prefix + x, choices_dict.keys()))
+    choices_xpath = "|".join([prefix + x for x in list(choices_dict.keys())])
 
     def GetContentInitial():
         content_name, infos = choices[0]
         if content_name == "sequence":
             content_value = []
-            for dummy in xrange(infos["minOccurs"]):
+            for dummy in range(infos["minOccurs"]):
                 for element_infos in infos["elements"]:
                     content_value.extend(GetElementInitialValue(factory, element_infos))
         else:
@@ -797,7 +796,7 @@
         if namespace is None:
             if name in self.Namespaces[self.SchemaNamespace]:
                 return self.Namespaces[self.SchemaNamespace][name]
-            for space, elements in self.Namespaces.iteritems():
+            for space, elements in self.Namespaces.items():
                 if space != self.SchemaNamespace and name in elements:
                     return elements[name]
             parts = name.split("_", 1)
@@ -839,7 +838,7 @@
         if namespace is None:
             if name in self.Namespaces[self.SchemaNamespace]:
                 return name, None
-            for space, elements in self.Namespaces.items():
+            for space, elements in list(self.Namespaces.items()):
                 if space != self.SchemaNamespace and name in elements:
                     return name, None
             parts = name.split("_", 1)
@@ -879,25 +878,27 @@
 
     def ExtractNodeAttrs(self, element_name, node, valid_attrs):
         attrs = {}
-        for qualified_name, attr in node._attrs.items():
-            namespace, name = DecomposeQualifiedName(qualified_name)
-            if name in valid_attrs:
-                infos = self.GetQualifiedNameInfos(name, namespace)
-                if infos["type"] != SYNTAXATTRIBUTE:
-                    raise ValueError("\"%s\" can't be a member attribute!" % name)
-                elif name in attrs:
-                    raise ValueError("\"%s\" attribute has been twice!" % name)
-                elif element_name in infos["extract"]:
-                    attrs[name] = infos["extract"][element_name](attr)
+        if node._attrs:
+            for qualified_name, attr in list(node._attrs.items()):
+                namespace, name = DecomposeQualifiedName(qualified_name)
+                if name in valid_attrs:
+                    infos = self.GetQualifiedNameInfos(name, namespace)
+                    if infos["type"] != SYNTAXATTRIBUTE:
+                        raise ValueError("\"%s\" can't be a member attribute!" % name)
+                    elif name in attrs:
+                        raise ValueError("\"%s\" attribute has been twice!" % name)
+                    elif element_name in infos["extract"]:
+                        attrs[name] = infos["extract"][element_name](attr)
+                    else:
+                        attrs[name] = infos["extract"]["default"](attr)
+                elif namespace == "xmlns":
+                    infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace)
+                    value = infos["extract"](attr)
+                    self.DefinedNamespaces[value] = name
+                    self.NSMAP[name] = value
                 else:
-                    attrs[name] = infos["extract"]["default"](attr)
-            elif namespace == "xmlns":
-                infos = self.GetQualifiedNameInfos("anyURI", self.SchemaNamespace)
-                value = infos["extract"](attr)
-                self.DefinedNamespaces[value] = name
-                self.NSMAP[name] = value
-            else:
-                raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" % (qualified_name, node.nodeName))
+                    raise ValueError("Invalid attribute \"%s\" for member \"%s\"!" %
+                                     (qualified_name, node.nodeName))
         for attr in valid_attrs:
             if attr not in attrs and \
                attr in self.Namespaces[self.SchemaNamespace] and \
@@ -964,7 +965,7 @@
 
     def AddToLookupClass(self, name, parent, typeinfos):
         lookup_name = self.etreeNamespaceFormat % name
-        if isinstance(typeinfos, string_types):
+        if isinstance(typeinfos, str):
             self.AddEquivalentClass(name, typeinfos)
             typeinfos = self.etreeNamespaceFormat % typeinfos
         lookup_classes = self.ComputedClassesLookUp.get(lookup_name)
@@ -982,7 +983,7 @@
             self.ComputedClassesLookUp[lookup_name] = lookup_classes
 
     def ExtractTypeInfos(self, name, parent, typeinfos):
-        if isinstance(typeinfos, string_types):
+        if isinstance(typeinfos, str):
             namespace, type_name = DecomposeQualifiedName(typeinfos)
             infos = self.GetQualifiedNameInfos(type_name, namespace)
             if name != "base":
@@ -993,13 +994,13 @@
             if infos["type"] == COMPLEXTYPE:
                 type_name, parent = self.SplitQualifiedName(type_name, namespace)
                 result = self.CreateClass(type_name, parent, infos)
-                if result is not None and not isinstance(result, string_types):
+                if result is not None and not isinstance(result, str):
                     self.Namespaces[self.TargetNamespace][result["name"]] = result
                 return result
             elif infos["type"] == ELEMENT and infos["elmt_type"]["type"] == COMPLEXTYPE:
                 type_name, parent = self.SplitQualifiedName(type_name, namespace)
                 result = self.CreateClass(type_name, parent, infos["elmt_type"])
-                if result is not None and not isinstance(result, string_types):
+                if result is not None and not isinstance(result, str):
                     self.Namespaces[self.TargetNamespace][result["name"]] = result
                 return result
             else:
@@ -1012,28 +1013,28 @@
     def GetEquivalentParents(self, parent):
         return reduce(lambda x, y: x + y,
                       [[p] + self.GetEquivalentParents(p)
-                       for p in self.EquivalentClassesParent.get(parent, {}).keys()], [])
+                       for p in list(self.EquivalentClassesParent.get(parent, {}).keys())], [])
 
     def CreateClasses(self):
         """
         Method that generates the classes
         """
         self.ParseSchema()
-        for name, infos in self.Namespaces[self.TargetNamespace].items():
+        for name, infos in list(self.Namespaces[self.TargetNamespace].items()):
             if infos["type"] == ELEMENT:
-                if not isinstance(infos["elmt_type"], string_types) and \
+                if not isinstance(infos["elmt_type"], str) and \
                    infos["elmt_type"]["type"] == COMPLEXTYPE:
                     self.ComputeAfter.append((name, None, infos["elmt_type"], True))
                     while len(self.ComputeAfter) > 0:
                         result = self.CreateClass(*self.ComputeAfter.pop(0))
-                        if result is not None and not isinstance(result, string_types):
+                        if result is not None and not isinstance(result, str):
                             self.Namespaces[self.TargetNamespace][result["name"]] = result
             elif infos["type"] == COMPLEXTYPE:
                 self.ComputeAfter.append((name, None, infos))
                 while len(self.ComputeAfter) > 0:
                     result = self.CreateClass(*self.ComputeAfter.pop(0))
                     if result is not None and \
-                       not isinstance(result, string_types):
+                       not isinstance(result, str):
                         self.Namespaces[self.TargetNamespace][result["name"]] = result
             elif infos["type"] == ELEMENTSGROUP:
                 elements = []
@@ -1042,18 +1043,18 @@
                 elif "choices" in infos:
                     elements = infos["choices"]
                 for element in elements:
-                    if not isinstance(element["elmt_type"], string_types) and \
+                    if not isinstance(element["elmt_type"], str) and \
                        element["elmt_type"]["type"] == COMPLEXTYPE:
                         self.ComputeAfter.append((element["name"], infos["name"], element["elmt_type"]))
                         while len(self.ComputeAfter) > 0:
                             result = self.CreateClass(*self.ComputeAfter.pop(0))
                             if result is not None and \
-                               not isinstance(result, string_types):
+                               not isinstance(result, str):
                                 self.Namespaces[self.TargetNamespace][result["name"]] = result
 
-        for name, parents in self.ComputedClassesLookUp.iteritems():
+        for name, parents in self.ComputedClassesLookUp.items():
             if isinstance(parents, dict):
-                computed_classes = parents.items()
+                computed_classes = list(parents.items())
             elif parents[1] is not None:
                 computed_classes = [(self.etreeNamespaceFormat % parents[1], parents[0])]
             else:
@@ -1097,7 +1098,7 @@
                 if self.FileName is not None:
                     classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None)
                     if classinfos["base"] is None:
-                        for filename, classes in self.ComputedClasses.iteritems():
+                        for filename, classes in self.ComputedClasses.items():
                             if filename != self.FileName:
                                 classinfos["base"] = classes.get(result["name"], None)
                                 if classinfos["base"] is not None:
@@ -1193,12 +1194,12 @@
         """
         Method that print the classes generated
         """
-        items = self.ComputedClasses.items()
+        items = list(self.ComputedClasses.items())
         items.sort()
         if self.FileName is not None:
             for filename, classes in items:
                 print("File '%s':" % filename)
-                class_items = classes.items()
+                class_items = list(classes.items())
                 class_items.sort()
                 for classname, xmlclass in class_items:
                     print("%s: %s" % (classname, str(xmlclass)))
@@ -1207,7 +1208,7 @@
                 print("%s: %s" % (classname, str(xmlclass)))
 
     def PrintClassNames(self):
-        classnames = self.XMLClassDefinitions.keys()
+        classnames = list(self.XMLClassDefinitions.keys())
         classnames.sort()
         for classname in classnames:
             print(classname)
@@ -1315,9 +1316,7 @@
                 if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1:
                     values = self.findall(element_name)
                     if element_infos["elmt_type"]["type"] == SIMPLETYPE:
-                        return map(lambda value:
-                                   element_infos["elmt_type"]["extract"](value.text, extract=False),
-                                   values)
+                        return [element_infos["elmt_type"]["extract"](value.text, extract=False) for value in values]
                     return values
                 else:
                     value = self.find(element_name)
@@ -1371,13 +1370,11 @@
                     self.remove(element)
 
                 if value is not None:
-                    element_idx = elements.keys().index(name)
+                    element_idx = list(elements.keys()).index(name)
                     if element_idx > 0:
-                        previous_elements_xpath = "|".join(map(
-                            lambda x: prefix + x
+                        previous_elements_xpath = "|".join([prefix + x
                             if x != "content"
-                            else elements["content"]["elmt_type"]["choices_xpath"].path,
-                            elements.keys()[:element_idx]))
+                            else elements["content"]["elmt_type"]["choices_xpath"].path for x in list(elements.keys())[:element_idx]])
 
                         insertion_point = len(self.xpath(previous_elements_xpath, namespaces=factory.NSMAP))
                     else:
@@ -1450,14 +1447,14 @@
             parts = path.split(".", 1)
             if parts[0] in attributes:
                 if len(parts) != 1:
-                    raise ValueError("Wrong path!")
+                    raise ValueError("Wrong path: "+path)
                 attr_type = gettypeinfos(attributes[parts[0]]["attr_type"]["basename"],
                                          attributes[parts[0]]["attr_type"]["facets"])
                 value = getattr(self, parts[0], "")
             elif parts[0] in elements:
                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                     if len(parts) != 1:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     attr_type = gettypeinfos(elements[parts[0]]["elmt_type"]["basename"],
                                              elements[parts[0]]["elmt_type"]["facets"])
                     value = getattr(self, parts[0], "")
@@ -1466,7 +1463,7 @@
                 else:
                     attr = getattr(self, parts[0], None)
                     if attr is None:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     if len(parts) == 1:
                         return attr.getElementInfos(parts[0])
                     else:
@@ -1477,13 +1474,13 @@
             elif "base" in classinfos:
                 classinfos["base"].getElementInfos(name, path)
             else:
-                raise ValueError("Wrong path!")
+                raise ValueError("Wrong path: "+path)
         else:
             if not derived:
                 children.extend(self.getElementAttributes())
             if "base" in classinfos:
                 children.extend(classinfos["base"].getElementInfos(self, name, derived=True)["children"])
-            for element_name, element in elements.items():
+            for element_name, element in list(elements.items()):
                 if element["minOccurs"] == 0:
                     use = "optional"
                 if element_name == "content" and element["type"] == CHOICE:
@@ -1519,7 +1516,7 @@
             parts = path.split(".", 1)
             if parts[0] in attributes:
                 if len(parts) != 1:
-                    raise ValueError("Wrong path!")
+                    raise ValueError("Wrong path: "+path)
                 if attributes[parts[0]]["attr_type"]["basename"] == "boolean":
                     setattr(self, parts[0], value)
                 elif attributes[parts[0]]["use"] == "optional" and value == None:
@@ -1534,7 +1531,7 @@
             elif parts[0] in elements:
                 if elements[parts[0]]["elmt_type"]["type"] == SIMPLETYPE:
                     if len(parts) != 1:
-                        raise ValueError("Wrong path!")
+                        raise ValueError("Wrong path: "+path)
                     if elements[parts[0]]["elmt_type"]["basename"] == "boolean":
                         setattr(self, parts[0], value)
                     elif attributes[parts[0]]["minOccurs"] == 0 and value == "":
@@ -1583,7 +1580,7 @@
             if element["type"] != CHOICE:
                 initial = GetElementInitialValue(factory, element)
                 if initial is not None:
-                    map(self.append, initial)
+                    list(map(self.append, initial))
     return initMethod
 
 
@@ -1736,8 +1733,10 @@
         return etree.QName(self.tag).localname
 
     def tostring(self):
-        return NAMESPACE_PATTERN.sub("", etree.tostring(self, pretty_print=True, encoding='utf-8')).decode('utf-8')
-
+        return NAMESPACE_PATTERN.sub("", etree.tostring(self, encoding='unicode'))
+
+    def getElementInfos(self, name, path=None, derived=False):
+        return {"name": name, "type": TAG, "value": None, "use": None, "children": []}
 
 class XMLElementClassLookUp(etree.PythonElementClassLookup):
 
@@ -1750,12 +1749,12 @@
     def GetElementClass(self, element_tag, parent_tag=None, default=DefaultElementClass):
         element_class = self.LookUpClasses.get(element_tag, (default, None))
         if not isinstance(element_class, dict):
-            if isinstance(element_class[0], string_types):
+            if isinstance(element_class[0], str):
                 return self.GetElementClass(element_class[0], default=default)
             return element_class[0]
 
         element_with_parent_class = element_class.get(parent_tag, default)
-        if isinstance(element_with_parent_class, string_types):
+        if isinstance(element_with_parent_class, str):
             return self.GetElementClass(element_with_parent_class, default=default)
         return element_with_parent_class
 
@@ -1817,7 +1816,7 @@
                 "%s " % etree.QName(child.tag).localname
                 for child in element])
             for possible_class in element_class:
-                if isinstance(possible_class, string_types):
+                if isinstance(possible_class, str):
                     possible_class = self.GetElementClass(possible_class)
                 if possible_class.StructurePattern.match(children) is not None:
                     return possible_class
@@ -1836,7 +1835,7 @@
         if targetNamespace is not None:
             self.RootNSMAP = {
                 name if targetNamespace != uri else None: uri
-                for name, uri in namespaces.iteritems()}
+                for name, uri in namespaces.items()}
         else:
             self.RootNSMAP = namespaces
         self.BaseClass = base_class
@@ -1847,7 +1846,7 @@
         self.ClassLookup = class_lookup
 
     def LoadXMLString(self, xml_string):
-        tree = etree.fromstring(xml_string, self)
+        tree = etree.fromstring(xml_string.encode(), self)
         if not self.XSDSchema.validate(tree):
             error = self.XSDSchema.error_log.last_error
             return tree, (error.line, error.message)
@@ -1939,13 +1938,13 @@
     ComputedClasses = factory.CreateClasses()
     if factory.FileName is not None:
         ComputedClasses = ComputedClasses[factory.FileName]
-    BaseClass = [(name, XSDclass) for name, XSDclass in ComputedClasses.items() if XSDclass.IsBaseClass]
+    BaseClass = [(name, XSDclass) for name, XSDclass in list(ComputedClasses.items()) if XSDclass.IsBaseClass]
 
     parser.initMembers(
         factory.NSMAP,
         factory.etreeNamespaceFormat,
         BaseClass[0] if len(BaseClass) == 1 else None,
-        etree.XMLSchema(etree.fromstring(xsdstring)))
+        etree.XMLSchema(etree.fromstring(xsdstring.encode())))
 
     class_lookup = XMLElementClassLookUp(factory.ComputedClassesLookUp)
     parser.set_element_class_lookup(class_lookup)
--- a/xmlclass/xsdschema.py	Fri Aug 18 11:38:20 2023 +0200
+++ b/xmlclass/xsdschema.py	Wed Oct 04 16:24:00 2023 +0200
@@ -23,16 +23,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 
-from __future__ import absolute_import
-from __future__ import print_function
 import os
 import re
 import datetime
 from types import FunctionType
 from xml.dom import minidom
-from future.builtins import round
-from six import string_types
-from past.builtins import long
 
 from xmlclass.xmlclass import *
 
@@ -77,9 +72,9 @@
 
 
 DEFAULT_FACETS = GenerateDictFacets(["pattern", "whiteSpace", "enumeration"])
-NUMBER_FACETS = GenerateDictFacets(DEFAULT_FACETS.keys() + ["maxInclusive", "maxExclusive", "minInclusive", "minExclusive"])
-DECIMAL_FACETS = GenerateDictFacets(NUMBER_FACETS.keys() + ["totalDigits", "fractionDigits"])
-STRING_FACETS = GenerateDictFacets(DEFAULT_FACETS.keys() + ["length", "minLength", "maxLength"])
+NUMBER_FACETS = GenerateDictFacets(list(DEFAULT_FACETS.keys()) + ["maxInclusive", "maxExclusive", "minInclusive", "minExclusive"])
+DECIMAL_FACETS = GenerateDictFacets(list(NUMBER_FACETS.keys()) + ["totalDigits", "fractionDigits"])
+STRING_FACETS = GenerateDictFacets(list(DEFAULT_FACETS.keys()) + ["length", "minLength", "maxLength"])
 
 ALL_FACETS = ["pattern", "whiteSpace", "enumeration", "maxInclusive",
               "maxExclusive", "minInclusive", "minExclusive", "totalDigits",
@@ -170,7 +165,7 @@
 
     if typeinfos["type"] in ["restriction", "extension"]:
         # Search for base type definition
-        if isinstance(typeinfos["base"], string_types):
+        if isinstance(typeinfos["base"], str):
             basetypeinfos = factory.FindSchemaElement(typeinfos["base"], SIMPLETYPE)
             if basetypeinfos is None:
                 raise "\"%s\" isn't defined!" % typeinfos["base"]
@@ -204,7 +199,7 @@
                 if len(facets) == 0:
                     facets[facettype] = ([value], False)
                     continue
-                elif facets.keys() == [facettype]:
+                elif list(facets.keys()) == [facettype]:
                     facets[facettype][0].append(value)
                     continue
                 else:
@@ -302,14 +297,14 @@
             facets[facettype] = (value, facet.get("fixed", False))
 
         # Report not redefined facet from base type to new created type
-        for facettype, facetvalue in basetypeinfos["facets"].items():
+        for facettype, facetvalue in list(basetypeinfos["facets"].items()):
             if facettype not in facets:
                 facets[facettype] = facetvalue
 
         # Generate extract value for new created type
         def ExtractSimpleTypeValue(attr, extract=True):
             value = basetypeinfos["extract"](attr, extract)
-            for facetname, (facetvalue, _facetfixed) in facets.items():
+            for facetname, (facetvalue, _facetfixed) in list(facets.items()):
                 if facetvalue is not None:
                     if facetname == "enumeration" and value not in facetvalue:
                         raise ValueError("\"%s\" not in enumerated values" % value)
@@ -328,7 +323,7 @@
                     elif facetname == "maxExclusive" and value >= facetvalue:
                         raise ValueError("value must be lesser than %s" % str(facetvalue))
                     elif facetname == "pattern":
-                        model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue)))
+                        model = re.compile("(?:%s)?$" % "|".join(["(?:%s)" % x for x in facetvalue]))
                         result = model.match(value)
                         if result is None:
                             if len(facetvalue) > 1:
@@ -343,7 +338,7 @@
             return value
 
         def CheckSimpleTypeValue(value):
-            for facetname, (facetvalue, _facetfixed) in facets.items():
+            for facetname, (facetvalue, _facetfixed) in list(facets.items()):
                 if facetvalue is not None:
                     if facetname == "enumeration" and value not in facetvalue:
                         return False
@@ -362,7 +357,7 @@
                     elif facetname == "maxExclusive" and value >= facetvalue:
                         return False
                     elif facetname == "pattern":
-                        model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue)))
+                        model = re.compile("(?:%s)?$" % "|".join(["(?:%s)" % x for x in facetvalue]))
                         result = model.match(value)
                         if result is None:
                             if len(facetvalue) > 1:
@@ -372,7 +367,7 @@
             return True
 
         def SimpleTypeInitialValue():
-            for facetname, (facetvalue, _facetfixed) in facets.items():
+            for facetname, (facetvalue, _facetfixed) in list(facets.items()):
                 if facetvalue is not None:
                     if facetname == "enumeration":
                         return facetvalue[0]
@@ -394,7 +389,7 @@
 
     elif typeinfos["type"] == "list":
         # Search for item type definition
-        if isinstance(typeinfos["itemType"], string_types):
+        if isinstance(typeinfos["itemType"], str):
             itemtypeinfos = factory.FindSchemaElement(typeinfos["itemType"], SIMPLETYPE)
             if itemtypeinfos is None:
                 raise "\"%s\" isn't defined!" % typeinfos["itemType"]
@@ -440,7 +435,7 @@
         # Search for member types definition
         membertypesinfos = []
         for membertype in typeinfos["memberTypes"]:
-            if isinstance(membertype, string_types):
+            if isinstance(membertype, str):
                 infos = factory.FindSchemaElement(membertype, SIMPLETYPE)
                 if infos is None:
                     raise ValueError("\"%s\" isn't defined!" % membertype)
@@ -514,8 +509,8 @@
     attrnames = {}
     if base is not None:
         basetypeinfos = factory.FindSchemaElement(base)
-        if not isinstance(basetypeinfos, string_types) and basetypeinfos["type"] == COMPLEXTYPE:
-            attrnames = dict(map(lambda x: (x["name"], True), basetypeinfos["attributes"]))
+        if not isinstance(basetypeinfos, str) and basetypeinfos["type"] == COMPLEXTYPE:
+            attrnames = dict([(x["name"], True) for x in basetypeinfos["attributes"]])
 
     for element in elements:
         if element["type"] == ATTRIBUTE:
@@ -815,7 +810,7 @@
                 raise ValueError("Only group composed of \"choice\" can be referenced in \"choice\" element!")
             choices_tmp = []
             for choice in elmtgroup["choices"]:
-                if not isinstance(choice["elmt_type"], string_types) and choice["elmt_type"]["type"] == COMPLEXTYPE:
+                if not isinstance(choice["elmt_type"], str) and choice["elmt_type"]["type"] == COMPLEXTYPE:
                     elmt_type = "%s_%s" % (elmtgroup["name"], choice["name"])
                     if factory.TargetNamespace is not None:
                         elmt_type = "%s:%s" % (factory.TargetNamespace, elmt_type)
@@ -849,7 +844,7 @@
                 raise ValueError("Only group composed of \"sequence\" can be referenced in \"sequence\" element!")
             elements_tmp = []
             for element in elmtgroup["elements"]:
-                if not isinstance(element["elmt_type"], string_types) and element["elmt_type"]["type"] == COMPLEXTYPE:
+                if not isinstance(element["elmt_type"], str) and element["elmt_type"]["type"] == COMPLEXTYPE:
                     elmt_type = "%s_%s" % (elmtgroup["name"], element["name"])
                     if factory.TargetNamespace is not None:
                         elmt_type = "%s:%s" % (factory.TargetNamespace, elmt_type)
@@ -996,7 +991,7 @@
     elif isinstance(schema, dict):
         if not isinstance(reference, dict) or len(schema) != len(reference):
             return False
-        for name, value in schema.items():
+        for name, value in list(schema.items()):
             ref_value = reference.get(name, None)
             if ref_value is None and value is not None:
                 return False
@@ -1061,7 +1056,7 @@
             if child.nodeType == self.Document.ELEMENT_NODE:
                 schema = child
                 break
-        for qualified_name, attr in schema._attrs.items():
+        for qualified_name, attr in list(schema._attrs.items()):
             namespace, name = DecomposeQualifiedName(qualified_name)
             if namespace == "xmlns":
                 value = GetAttributeValue(attr)
@@ -2215,7 +2210,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)},
+        "check": lambda x: isinstance(x, str)},
 
     "normalizedString": {
         "type": SIMPLETYPE,
@@ -2224,7 +2219,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "token": {
@@ -2234,7 +2229,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "base64Binary": {
@@ -2244,7 +2239,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: 0,
-        "check": lambda x: isinstance(x, (int, long))
+        "check": lambda x: isinstance(x, int)
     },
 
     "hexBinary": {
@@ -2254,7 +2249,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: ("%."+str(int(round(len("%X" % x)/2.)*2))+"X") % x),
         "initial": lambda: 0,
-        "check": lambda x: isinstance(x, (int, long))
+        "check": lambda x: isinstance(x, int)
     },
 
     "integer": {
@@ -2434,7 +2429,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "dateTime": {
@@ -2474,7 +2469,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "gYearMonth": {
@@ -2484,7 +2479,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "gMonth": {
@@ -2494,7 +2489,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "gMonthDay": {
@@ -2504,7 +2499,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "gDay": {
@@ -2514,7 +2509,7 @@
         "facets": NUMBER_FACETS,
         "generate": GenerateSimpleTypeXMLText(str),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "Name": {
@@ -2524,7 +2519,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "QName": {
@@ -2534,7 +2529,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "NCName": {
@@ -2544,7 +2539,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "anyURI": {
@@ -2554,7 +2549,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "language": {
@@ -2564,7 +2559,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "en",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "ID": {
@@ -2574,7 +2569,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "IDREF": {
@@ -2584,7 +2579,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "IDREFS": {
@@ -2594,7 +2589,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "ENTITY": {
@@ -2604,7 +2599,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "ENTITIES": {
@@ -2614,7 +2609,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "NOTATION": {
@@ -2624,7 +2619,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "NMTOKEN": {
@@ -2634,7 +2629,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     "NMTOKENS": {
@@ -2644,7 +2639,7 @@
         "facets": STRING_FACETS,
         "generate": GenerateSimpleTypeXMLText(lambda x: x),
         "initial": lambda: "",
-        "check": lambda x: isinstance(x, string_types)
+        "check": lambda x: isinstance(x, str)
     },
 
     # Complex Types