merged Andrey's default
authorEdouard Tisserant
Wed, 08 Aug 2018 13:46:19 +0200
changeset 2259 53f3eb5c47f7
parent 2251 62eba810212e (diff)
parent 2258 c9915bc620cd (current diff)
child 2260 74205edac761
merged Andrey's default
--- a/Beremiz_service.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/Beremiz_service.py	Wed Aug 08 13:46:19 2018 +0200
@@ -30,7 +30,7 @@
 import sys
 import getopt
 import threading
-from threading import Thread, currentThread, Semaphore, Lock
+from threading import Thread, Semaphore, Lock
 import traceback
 import __builtin__
 import Pyro
@@ -45,17 +45,17 @@
     print("""
 Usage of Beremiz PLC execution service :\n
 %s {[-n servicename] [-i IP] [-p port] [-x enabletaskbar] [-a autostart]|-h|--help} working_dir
-           -n        - zeroconf service name (default:disabled)
-           -i        - IP address of interface to bind to (default:localhost)
-           -p        - port number default:3000
-           -h        - print this help text and quit
-           -a        - autostart PLC (0:disable 1:enable) (default:0)
-           -x        - enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
-           -t        - enable/disable Twisted web interface (0:disable 1:enable) (default:1)
-           -w        - web server port or "off" to disable web server (default:8009)
-           -c        - WAMP client default config file (default:wampconf.json)
-           -s        - WAMP client secret, given as a file
-           -e        - python extension (absolute path .py)
+  -n  zeroconf service name (default:disabled)
+  -i  IP address of interface to bind to (default:localhost)
+  -p  port number default:3000
+  -h  print this help text and quit
+  -a  autostart PLC (0:disable 1:enable) (default:0)
+  -x  enable/disable wxTaskbarIcon (0:disable 1:enable) (default:1)
+  -t  enable/disable Twisted web interface (0:disable 1:enable) (default:1)
+  -w  web server port or "off" to disable web server (default:8009)
+  -c  WAMP client config file (can be overriden by wampconf.json in project)
+  -s  WAMP client secret, given as a file (can be overriden by wamp.secret in project)
+  -e  python extension (absolute path .py)
 
            working_dir - directory where are stored PLC files
 """ % sys.argv[0])
@@ -499,6 +499,7 @@
 
 if havewx:
     wx_eval_lock = Semaphore(0)
+    # FIXME : beware wx mainloop is _not_ running in main thread
     # main_thread = currentThread()
 
     def statuschangeTskBar(status):
@@ -512,6 +513,7 @@
         wx_eval_lock.release()
 
     def evaluator(tocall, *args, **kwargs):
+        # FIXME : should implement anti-deadlock
         # if main_thread == currentThread():
         #     # avoid dead lock if called from the wx mainloop
         #     return default_evaluator(tocall, *args, **kwargs)
@@ -571,29 +573,23 @@
 
 
 installThreadExcepthook()
+havewamp = False
 
 if havetwisted:
     if webport is not None:
         try:
             import runtime.NevowServer as NS  # pylint: disable=ungrouped-imports
-        except Exception, e:
-            print(_("Nevow/Athena import failed :"), e)
+        except Exception:
+            LogMessageAndException(_("Nevow/Athena import failed :"))
             webport = None
         NS.WorkingDir = WorkingDir
 
-    # Find pre-existing project WAMP config file
-    _wampconf = os.path.join(WorkingDir, "wampconf.json")
-
-    # If project's WAMP config file exits, override default (-c)
-    if os.path.exists(_wampconf):
-        wampconf = _wampconf
-
-    if wampconf is not None:
-        try:
-            import runtime.WampClient as WC  # pylint: disable=ungrouped-imports
-        except Exception, e:
-            print(_("WAMP import failed :"), e)
-            wampconf = None
+    try:
+        import runtime.WampClient as WC  # pylint: disable=ungrouped-imports
+        WC.WorkingDir = WorkingDir
+        havewamp = True
+    except Exception:
+        LogMessageAndException(_("WAMP import failed :"))
 
 # Load extensions
 for extention_file, extension_folder in extensions:
@@ -605,22 +601,16 @@
         try:
             website = NS.RegisterWebsite(webport)
             pyruntimevars["website"] = website
+            NS.SetServer(pyroserver)
             statuschange.append(NS.website_statuslistener_factory(website))
         except Exception:
             LogMessageAndException(_("Nevow Web service failed. "))
 
-    if wampconf is not None:
+    if havewamp:
         try:
-            _wampconf = WC.LoadWampClientConf(wampconf)
-            if _wampconf:
-                if _wampconf["url"]:  # TODO : test more ?
-                    WC.RegisterWampClient(wampconf, wampsecret)
-                    pyruntimevars["wampsession"] = WC.GetSession
-                    WC.SetServer(pyroserver)
-                else:
-                    raise Exception(_("WAMP config is incomplete."))
-            else:
-                raise Exception(_("WAMP config is missing."))
+            WC.SetServer(pyroserver)
+            WC.RegisterWampClient(wampconf, wampsecret)
+            WC.RegisterWebSettings(NS)
         except Exception:
             LogMessageAndException(_("WAMP client startup failed. "))
 
--- a/ProjectController.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/ProjectController.py	Wed Aug 08 13:46:19 2018 +0200
@@ -65,7 +65,8 @@
 
 base_folder = paths.AbsParentDir(__file__)
 
-MATIEC_ERROR_MODEL = re.compile(".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
+MATIEC_ERROR_MODEL = re.compile(
+    ".*\.st:(\d+)-(\d+)\.\.(\d+)-(\d+): (?:error)|(?:warning) : (.*)$")
 
 
 def ExtractChildrenTypesFromCatalog(catalog):
@@ -94,6 +95,7 @@
 
 
 class Iec2CSettings(object):
+
     def __init__(self):
         self.iec2c = None
         self.iec2c_buildopts = None
@@ -109,11 +111,12 @@
         return path
 
     def findCmd(self):
-        cmd = "iec2c"+(".exe" if wx.Platform == '__WXMSW__' else "")
+        cmd = "iec2c" + (".exe" if wx.Platform == '__WXMSW__' else "")
         paths = [
             os.path.join(base_folder, "matiec")
         ]
-        path = self.findObject(paths, lambda p: os.path.isfile(os.path.join(p, cmd)))
+        path = self.findObject(
+            paths, lambda p: os.path.isfile(os.path.join(p, cmd)))
 
         # otherwise use iec2c from PATH
         if path is not None:
@@ -126,7 +129,8 @@
             os.path.join(base_folder, "matiec", "lib"),
             "/usr/lib/matiec"
         ]
-        path = self.findObject(paths, lambda p: os.path.isfile(os.path.join(p, "ieclib.txt")))
+        path = self.findObject(
+            paths, lambda p: os.path.isfile(os.path.join(p, "ieclib.txt")))
         return path
 
     def findLibCPath(self):
@@ -146,7 +150,8 @@
 
         buildopt = ""
         try:
-            # Invoke compiler. Output files are listed to stdout, errors to stderr
+            # Invoke compiler.
+            # Output files are listed to stdout, errors to stderr
             _status, result, _err_result = ProcessLogger(None, buildcmd,
                                                          no_stdout=True,
                                                          no_stderr=True).spin()
@@ -186,16 +191,17 @@
             <xsd:element name="TargetType">
               <xsd:complexType>
                 <xsd:choice minOccurs="0">
-                """+targets.GetTargetChoices()+"""
+                """ + targets.GetTargetChoices() + """
                 </xsd:choice>
               </xsd:complexType>
-            </xsd:element>"""+(("""
+            </xsd:element>""" + (("""
             <xsd:element name="Libraries" minOccurs="0">
               <xsd:complexType>
-              """+"\n".join(['<xsd:attribute name=' +
-                             '"Enable_' + libname + '_Library" ' +
-                             'type="xsd:boolean" use="optional" default="false"/>'
-                             for libname, _lib in features.libraries])+"""
+              """ + "\n".join(['<xsd:attribute name=' +
+                               '"Enable_' + libname + '_Library" ' +
+                               'type="xsd:boolean" use="optional" default="' +
+                               ('true' if default else 'false') + '"/>'
+                               for libname, _lib, default in features.libraries]) + """
               </xsd:complexType>
             </xsd:element>""") if len(features.libraries) > 0 else '') + """
           </xsd:sequence>
@@ -209,6 +215,7 @@
 
 
 class ProjectController(ConfigTreeNode, PLCControler):
+
     """
     This class define Root object of the confnode tree.
     It is responsible of :
@@ -219,7 +226,8 @@
     - ...
 
     """
-    # For root object, available Children Types are modules of the confnode packages.
+    # For root object, available Children Types are modules of the confnode
+    # packages.
     CTNChildrenTypes = ExtractChildrenTypesFromCatalog(features.catalog)
     XSD = GetProjectControllerXSD()
     EditorType = ProjectNodeEditor
@@ -270,8 +278,10 @@
     def LoadLibraries(self):
         self.Libraries = []
         TypeStack = []
-        for libname, clsname in features.libraries:
-            if self.BeremizRoot.Libraries is not None and getattr(self.BeremizRoot.Libraries, "Enable_"+libname+"_Library"):
+        for libname, clsname, _default in features.libraries:
+            if self.BeremizRoot.Libraries is not None and \
+               getattr(self.BeremizRoot.Libraries,
+                       "Enable_" + libname + "_Library"):
                 Lib = GetClassImporter(clsname)()(self, libname, TypeStack)
                 TypeStack.append(Lib.GetTypes())
                 self.Libraries.append(Lib)
@@ -361,7 +371,8 @@
             target = self.Parser.CreateElement("TargetType", "BeremizRoot")
             temp_root.setTargetType(target)
             target_name = self.GetDefaultTargetName()
-            target.setcontent(self.Parser.CreateElement(target_name, "TargetType"))
+            target.setcontent(
+                self.Parser.CreateElement(target_name, "TargetType"))
         return target
 
     def GetParamsAttributes(self, path=None):
@@ -369,7 +380,8 @@
         if params[0]["name"] == "BeremizRoot":
             for child in params[0]["children"]:
                 if child["name"] == "TargetType" and child["value"] == '':
-                    child.update(self.GetTarget().getElementInfos("TargetType"))
+                    child.update(
+                        self.GetTarget().getElementInfos("TargetType"))
         return params
 
     def SetParamsAttribute(self, path, value):
@@ -403,7 +415,8 @@
     def _getProjectFilesPath(self, project_path=None):
         if project_path is not None:
             return os.path.join(project_path, "project_files")
-        projectfiles_path = os.path.join(self.GetProjectPath(), "project_files")
+        projectfiles_path = os.path.join(
+            self.GetProjectPath(), "project_files")
         if not os.path.exists(projectfiles_path):
             os.mkdir(projectfiles_path)
         return projectfiles_path
@@ -414,7 +427,8 @@
 
     def SetProjectDefaultConfiguration(self):
         # Sets default task and instance for new project
-        config = self.Project.getconfiguration(self.GetProjectMainConfigurationName())
+        config = self.Project.getconfiguration(
+            self.GetProjectMainConfigurationName())
         resource = config.getresource()[0].getname()
         config = config.getname()
         resource_tagname = ComputeConfigurationResourceName(config, resource)
@@ -471,7 +485,8 @@
         if error is not None:
             if self.Project is not None:
                 (fname_err, lnum, src) = (("PLC",) + error)
-                self.logger.write_warning(XSDSchemaErrorMessage.format(a1=fname_err, a2=lnum, a3=src))
+                self.logger.write_warning(
+                    XSDSchemaErrorMessage.format(a1=fname_err, a2=lnum, a3=src))
             else:
                 return error, False
         if len(self.GetProjectConfigNames()) == 0:
@@ -528,14 +543,17 @@
     def CheckNewProjectPath(self, old_project_path, new_project_path):
         if old_project_path == new_project_path:
             message = (_("Save path is the same as path of a project! \n"))
-            dialog = wx.MessageDialog(self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR)
+            dialog = wx.MessageDialog(
+                self.AppFrame, message, _("Error"), wx.OK | wx.ICON_ERROR)
             dialog.ShowModal()
             return False
         else:
             plc_file = os.path.join(new_project_path, "plc.xml")
             if os.path.isfile(plc_file):
-                message = (_("Selected directory already contains another project. Overwrite? \n"))
-                dialog = wx.MessageDialog(self.AppFrame, message, _("Error"), wx.YES_NO | wx.ICON_ERROR)
+                message = (
+                    _("Selected directory already contains another project. Overwrite? \n"))
+                dialog = wx.MessageDialog(
+                    self.AppFrame, message, _("Error"), wx.YES_NO | wx.ICON_ERROR)
                 answer = dialog.ShowModal()
                 return answer == wx.ID_YES
         return True
@@ -543,7 +561,8 @@
     def SaveProject(self, from_project_path=None):
         if self.CheckProjectPathPerm(False):
             if from_project_path is not None:
-                old_projectfiles_path = self._getProjectFilesPath(from_project_path)
+                old_projectfiles_path = self._getProjectFilesPath(
+                    from_project_path)
                 if os.path.isdir(old_projectfiles_path):
                     shutil.copytree(old_projectfiles_path,
                                     self._getProjectFilesPath(self.ProjectPath))
@@ -558,7 +577,8 @@
             path = os.getenv("USERPROFILE")
         else:
             path = os.getenv("HOME")
-        dirdialog = wx.DirDialog(self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
+        dirdialog = wx.DirDialog(
+            self.AppFrame, _("Choose a directory to save project"), path, wx.DD_NEW_DIR_BUTTON)
         answer = dirdialog.ShowModal()
         dirdialog.Destroy()
         if answer == wx.ID_OK:
@@ -582,7 +602,8 @@
         if len(self.Libraries) == 0:
             return [], [], ()
         self.GetIECProgramsAndVariables()
-        LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath(self.GetIECLibPath())
+        LibIECCflags = '"-I%s" -Wno-unused-function' % os.path.abspath(
+            self.GetIECLibPath())
         LocatedCCodeAndFlags = []
         Extras = []
         for lib in self.Libraries:
@@ -590,7 +611,7 @@
             LocatedCCodeAndFlags.append(res[:2])
             if len(res) > 2:
                 Extras.extend(res[2:])
-        return map(list, zip(*LocatedCCodeAndFlags))+[tuple(Extras)]
+        return map(list, zip(*LocatedCCodeAndFlags)) + [tuple(Extras)]
 
     # Update PLCOpenEditor ConfNode Block types from loaded confnodes
     def RefreshConfNodesBlockLists(self):
@@ -656,7 +677,8 @@
             self.DefaultBuildPath = os.path.join(self.ProjectPath, "build")
         # Create a build path in temp folder
         else:
-            self.DefaultBuildPath = os.path.join(tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build")
+            self.DefaultBuildPath = os.path.join(
+                tempfile.mkdtemp(), os.path.basename(self.ProjectPath), "build")
 
         if not os.path.exists(self.DefaultBuildPath):
             os.makedirs(self.DefaultBuildPath)
@@ -681,19 +703,23 @@
         locations = []
         filepath = os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h")
         if os.path.isfile(filepath):
-            # IEC2C compiler generate a list of located variables : LOCATED_VARIABLES.h
-            location_file = open(os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h"))
+            # IEC2C compiler generate a list of located variables :
+            # LOCATED_VARIABLES.h
+            location_file = open(
+                os.path.join(self._getBuildPath(), "LOCATED_VARIABLES.h"))
             # each line of LOCATED_VARIABLES.h declares a located variable
             lines = [line.strip() for line in location_file.readlines()]
             # This regular expression parses the lines genereated by IEC2C
-            LOCATED_MODEL = re.compile("__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWDL]))?,(?P<LOC>[,0-9]*)\)")
+            LOCATED_MODEL = re.compile(
+                "__LOCATED_VAR\((?P<IEC_TYPE>[A-Z]*),(?P<NAME>[_A-Za-z0-9]*),(?P<DIR>[QMI])(?:,(?P<SIZE>[XBWDL]))?,(?P<LOC>[,0-9]*)\)")
             for line in lines:
                 # If line match RE,
                 result = LOCATED_MODEL.match(line)
                 if result:
                     # Get the resulting dict
                     resdict = result.groupdict()
-                    # rewrite string for variadic location as a tuple of integers
+                    # rewrite string for variadic location as a tuple of
+                    # integers
                     resdict['LOC'] = tuple(map(int, resdict['LOC'].split(',')))
                     # set located size to 'X' if not given
                     if not resdict['SIZE']:
@@ -719,16 +745,20 @@
         # Update PLCOpenEditor ConfNode Block types before generate ST code
         self.RefreshConfNodesBlockLists()
 
-        self.logger.write(_("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
+        self.logger.write(
+            _("Generating SoftPLC IEC-61131 ST/IL/SFC code...\n"))
         # ask PLCOpenEditor controller to write ST/IL/SFC code file
-        _program, errors, warnings = self.GenerateProgram(self._getIECgeneratedcodepath())
+        _program, errors, warnings = self.GenerateProgram(
+            self._getIECgeneratedcodepath())
         if len(warnings) > 0:
-            self.logger.write_warning(_("Warnings in ST/IL/SFC code generator :\n"))
+            self.logger.write_warning(
+                _("Warnings in ST/IL/SFC code generator :\n"))
             for warning in warnings:
                 self.logger.write_warning("%s\n" % warning)
         if len(errors) > 0:
             # Failed !
-            self.logger.write_error(_("Error in ST/IL/SFC code generator :\n%s\n") % errors[0])
+            self.logger.write_error(
+                _("Error in ST/IL/SFC code generator :\n%s\n") % errors[0])
             return False
         plc_file = open(self._getIECcodepath(), "w")
         # Add ST Library from confnodes
@@ -763,9 +793,11 @@
             self._getIECcodepath())
 
         try:
-            # Invoke compiler. Output files are listed to stdout, errors to stderr
+            # Invoke compiler.
+            # Output files are listed to stdout, errors to stderr
             status, result, err_result = ProcessLogger(self.logger, buildcmd,
-                                                       no_stdout=True, no_stderr=True).spin()
+                                                       no_stdout=True,
+                                                       no_stderr=True).spin()
         except Exception, e:
             self.logger.write_error(buildcmd + "\n")
             self.logger.write_error(repr(e) + "\n")
@@ -794,29 +826,36 @@
 
                         if first_line <= i <= last_line:
                             if last_section is not None:
-                                self.logger.write_warning("In section: " + last_section)
+                                self.logger.write_warning(
+                                    "In section: " + last_section)
                                 last_section = None  # only write section once
                             self.logger.write_warning("%04d: %s" % (i, line))
 
                     f.close()
 
-            self.logger.write_error(_("Error : IEC to C compiler returned %d\n") % status)
+            self.logger.write_error(
+                _("Error : IEC to C compiler returned %d\n") % status)
             return False
 
         # Now extract C files of stdout
-        C_files = [fname for fname in result.splitlines() if fname[-2:] == ".c" or fname[-2:] == ".C"]
+        C_files = [fname for fname in result.splitlines() if fname[
+            -2:] == ".c" or fname[-2:] == ".C"]
         # remove those that are not to be compiled because included by others
         C_files.remove("POUS.c")
         if not C_files:
-            self.logger.write_error(_("Error : At least one configuration and one resource must be declared in PLC !\n"))
+            self.logger.write_error(
+                _("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 = map(
+            lambda filename: os.path.join(buildpath, filename), 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 = [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 = map(
+            lambda filename: os.path.join(buildpath, filename), H_files)
         for H_file in H_files:
             with file(H_file, 'r') as original:
                 data = original.read()
@@ -824,7 +863,8 @@
                 modified.write('#include "beremiz.h"\n' + data)
 
         self.logger.write(_("Extracting Located Variables...\n"))
-        # Keep track of generated located variables for later use by self._Generate_C
+        # Keep track of generated located variables for later use by
+        # self._Generate_C
         self.PLCGeneratedLocatedVars = self.GetLocations()
         # Keep track of generated C files for later use by self.CTNGenerate_C
         self.PLCGeneratedCFiles = C_files
@@ -859,11 +899,11 @@
         else:
             return None
 
-    #######################################################################
+    #
     #
     #                C CODE GENERATION METHODS
     #
-    #######################################################################
+    #
 
     def CTNGenerate_C(self, buildpath, locations):
         """
@@ -902,7 +942,8 @@
                 csvfile = os.path.join(self._getBuildPath(), "VARIABLES.csv")
                 # describes CSV columns
                 ProgramsListAttributeName = ["num", "C_path", "type"]
-                VariablesListAttributeName = ["num", "vartype", "IEC_path", "C_path", "type"]
+                VariablesListAttributeName = [
+                    "num", "vartype", "IEC_path", "C_path", "type"]
                 self._ProgramList = []
                 self._VariablesList = []
                 self._DbgVariablesList = []
@@ -922,9 +963,11 @@
                 # first section contains programs
                 for line in ListGroup[0]:
                     # Split and Maps each field to dictionnary entries
-                    attrs = dict(zip(ProgramsListAttributeName, line.strip().split(';')))
+                    attrs = dict(
+                        zip(ProgramsListAttributeName, line.strip().split(';')))
                     # Truncate "C_path" to remove conf an resources names
-                    attrs["C_path"] = '__'.join(attrs["C_path"].split(".", 2)[1:])
+                    attrs["C_path"] = '__'.join(
+                        attrs["C_path"].split(".", 2)[1:])
                     # Push this dictionnary into result.
                     self._ProgramList.append(attrs)
 
@@ -933,7 +976,8 @@
                 Idx = 0
                 for line in ListGroup[1]:
                     # Split and Maps each field to dictionnary entries
-                    attrs = dict(zip(VariablesListAttributeName, line.strip().split(';')))
+                    attrs = dict(
+                        zip(VariablesListAttributeName, line.strip().split(';')))
                     # Truncate "C_path" to remove conf an resources names
                     parts = attrs["C_path"].split(".", 2)
                     if len(parts) > 2:
@@ -964,7 +1008,8 @@
                     self._Ticktime = int(ListGroup[2][0])
 
             except Exception:
-                self.logger.write_error(_("Cannot open/parse VARIABLES.csv!\n"))
+                self.logger.write_error(
+                    _("Cannot open/parse VARIABLES.csv!\n"))
                 self.logger.write_error(traceback.format_exc())
                 self.ResetIECProgramsAndVariables()
                 return False
@@ -1034,16 +1079,16 @@
                 "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 xrange(len(locstrs), 0, -1)]),
                 "init_calls": "\n    ".join([
-                    "init_level=%d; " % (i+1) +
+                    "init_level=%d; " % (i + 1) +
                     "if((res = __init_%s(argc,argv))){" % locstr +
                     # "printf(\"%s\"); "%locstr + #for debug
                     "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 xrange(len(locstrs), 0, -1)])
+            }
         else:
             plc_main_code = targets.GetCode("plc_main_head.c") % {
                 "calls_prototypes": "\n",
@@ -1052,7 +1097,8 @@
                 "init_calls":       "\n",
                 "cleanup_calls":    "\n"
             }
-        plc_main_code += targets.GetTargetCode(self.GetTarget().getcontent().getLocalTag())
+        plc_main_code += targets.GetTargetCode(
+            self.GetTarget().getcontent().getLocalTag())
         plc_main_code += targets.GetCode("plc_main_tail.c")
         return plc_main_code
 
@@ -1124,21 +1170,25 @@
                 buildpath,
                 self.PLCGeneratedLocatedVars)
         except Exception:
-            self.logger.write_error(_("Runtime IO extensions C code generation failed !\n"))
+            self.logger.write_error(
+                _("Runtime IO extensions C code generation failed !\n"))
             self.logger.write_error(traceback.format_exc())
             self.ResetBuildMD5()
             return False
 
         # Generate C code and compilation params from liraries
         try:
-            LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(buildpath)
+            LibCFilesAndCFLAGS, LibLDFLAGS, LibExtraFiles = self.GetLibrariesCCode(
+                buildpath)
         except Exception:
-            self.logger.write_error(_("Runtime library extensions C code generation failed !\n"))
+            self.logger.write_error(
+                _("Runtime library extensions C code generation failed !\n"))
             self.logger.write_error(traceback.format_exc())
             self.ResetBuildMD5()
             return False
 
-        self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + CTNLocationCFilesAndCFLAGS
+        self.LocationCFilesAndCFLAGS = LibCFilesAndCFLAGS + \
+            CTNLocationCFilesAndCFLAGS
         self.LDFLAGS = CTNLDFLAGS + LibLDFLAGS
         ExtraFiles = CTNExtraFiles + LibExtraFiles
 
@@ -1157,7 +1207,8 @@
         del ExtraFiles
 
         # Header file for extensions
-        open(os.path.join(buildpath, "beremiz.h"), "w").write(targets.GetHeader())
+        open(os.path.join(buildpath, "beremiz.h"), "w").write(
+            targets.GetHeader())
 
         # Template based part of C code generation
         # files are stacked at the beginning, as files of confnode tree root
@@ -1176,10 +1227,12 @@
                     raise Exception
                 code_path = os.path.join(buildpath, filename)
                 open(code_path, "w").write(code)
-                # Insert this file as first file to be compiled at root confnode
-                self.LocationCFilesAndCFLAGS[0][1].insert(0, (code_path, self.plcCFLAGS))
+                # Insert this file as first file to be compiled at root
+                # confnode
+                self.LocationCFilesAndCFLAGS[0][1].insert(
+                    0, (code_path, self.plcCFLAGS))
             except Exception:
-                self.logger.write_error(name+_(" generation failed !\n"))
+                self.logger.write_error(name + _(" generation failed !\n"))
                 self.logger.write_error(traceback.format_exc())
                 self.ResetBuildMD5()
                 return False
@@ -1189,12 +1242,16 @@
     def ShowError(self, logger, from_location, to_location):
         chunk_infos = self.GetChunkInfos(from_location, to_location)
         for infos, (start_row, start_col) in chunk_infos:
-            row = 1 if from_location[0] < start_row else (from_location[0] - start_row)
-            col = 1 if (start_row != from_location[0]) else (from_location[1] - start_col)
+            row = 1 if from_location[0] < start_row else (
+                from_location[0] - start_row)
+            col = 1 if (start_row != from_location[0]) else (
+                from_location[1] - start_col)
             start = (row, col)
 
-            row = 1 if to_location[0] < start_row else (to_location[0] - start_row)
-            col = 1 if (start_row != to_location[0]) else (to_location[1] - start_col)
+            row = 1 if to_location[0] < start_row else (
+                to_location[0] - start_row)
+            col = 1 if (start_row != to_location[0]) else (
+                to_location[1] - start_col)
             end = (row, col)
 
             if self.AppFrame is not None:
@@ -1225,7 +1282,8 @@
             if self._IECCodeView is None:
                 plc_file = self._getIECcodepath()
 
-                self._IECCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
+                self._IECCodeView = IECCodeViewer(
+                    self.AppFrame.TabsOpened, "", self.AppFrame, None, instancepath=name)
                 self._IECCodeView.SetTextSyntax("ALL")
                 self._IECCodeView.SetKeywords(IEC_KEYWORDS)
                 try:
@@ -1246,7 +1304,8 @@
             if self._IECRawCodeView is None:
                 controler = MiniTextControler(self._getIECrawcodepath(), self)
 
-                self._IECRawCodeView = IECCodeViewer(self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name)
+                self._IECRawCodeView = IECCodeViewer(
+                    self.AppFrame.TabsOpened, "", self.AppFrame, controler, instancepath=name)
                 self._IECRawCodeView.SetTextSyntax("ALL")
                 self._IECRawCodeView.SetKeywords(IEC_KEYWORDS)
                 self._IECRawCodeView.RefreshView()
@@ -1260,7 +1319,8 @@
 
         elif name == "Project Files":
             if self._ProjectFilesView is None:
-                self._ProjectFilesView = FileManagementPanel(self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
+                self._ProjectFilesView = FileManagementPanel(
+                    self.AppFrame.TabsOpened, self, name, self._getProjectFilesPath(), True)
 
                 extensions = []
                 for extension, name, editor in features.file_editors:
@@ -1302,7 +1362,8 @@
                         name = "::".join([filepath, editor_name])
 
                         editor = editors[editor_name]()
-                        self._FileEditors[filepath] = editor(self.AppFrame.TabsOpened, self, name, self.AppFrame)
+                        self._FileEditors[filepath] = editor(
+                            self.AppFrame.TabsOpened, self, name, self.AppFrame)
                         self._FileEditors[filepath].SetIcon(GetBitmap("FILE"))
                         if isinstance(self._FileEditors[filepath], DebugViewer):
                             self._FileEditors[filepath].SetDataProducer(self)
@@ -1352,6 +1413,31 @@
             if self.AppFrame is not None:
                 self.AppFrame.LogViewer.SetLogCounters(log_count)
 
+    DefaultMethods = {
+        "_Run": False,
+        "_Stop": False,
+        "_Transfer": False,
+        "_Connect": True,
+        "_Disconnect": False
+    }
+
+    MethodsFromStatus = {
+        "Started":      {"_Stop": True,
+                         "_Transfer": True,
+                         "_Connect": False,
+                         "_Disconnect": True},
+        "Stopped":      {"_Run": True,
+                         "_Transfer": True,
+                         "_Connect": False,
+                         "_Disconnect": True},
+        "Empty":        {"_Transfer": True,
+                         "_Connect": False,
+                         "_Disconnect": True},
+        "Broken":       {"_Connect": False,
+                         "_Disconnect": True},
+        "Disconnected": {},
+    }
+
     def UpdateMethodsFromPLCStatus(self):
         updated = False
         status = None
@@ -1364,32 +1450,24 @@
             self._SetConnector(None, False)
             status = "Disconnected"
         if self.previous_plcstate != status:
-            for args in {
-                    "Started":      [("_Run", False),
-                                     ("_Stop", True)],
-                    "Stopped":      [("_Run", True),
-                                     ("_Stop", False)],
-                    "Empty":        [("_Run", False),
-                                     ("_Stop", False)],
-                    "Broken":       [],
-                    "Disconnected": [("_Run", False),
-                                     ("_Stop", False),
-                                     ("_Transfer", False),
-                                     ("_Connect", True),
-                                     ("_Disconnect", False)],
-            }.get(status, []):
-                self.ShowMethod(*args)
+            allmethods = self.DefaultMethods.copy()
+            allmethods.update(
+                self.MethodsFromStatus.get(status, {}))
+            for method, active in allmethods.items():
+                self.ShowMethod(method, active)
             self.previous_plcstate = status
             if self.AppFrame is not None:
                 updated = True
                 self.AppFrame.RefreshStatusToolBar()
                 if status == "Disconnected":
-                    self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 1)
+                    self.AppFrame.ConnectionStatusBar.SetStatusText(
+                        self.GetTextStatus(status), 1)
                     self.AppFrame.ConnectionStatusBar.SetStatusText('', 2)
                 else:
                     self.AppFrame.ConnectionStatusBar.SetStatusText(
                         _("Connected to URI: %s") % self.BeremizRoot.getURI_location().strip(), 1)
-                    self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 2)
+                    self.AppFrame.ConnectionStatusBar.SetStatusText(
+                        self.GetTextStatus(status), 2)
         return updated
 
     def GetTextStatus(self, status):
@@ -1399,12 +1477,13 @@
             "Empty":        _("Empty"),
             "Broken":       _("Broken"),
             "Disconnected": _("Disconnected")
-            }
+        }
         return msgs.get(status, status)
 
     def ShowPLCProgress(self, status="", progress=0):
         self.AppFrame.ProgressStatusBar.Show()
-        self.AppFrame.ConnectionStatusBar.SetStatusText(self.GetTextStatus(status), 1)
+        self.AppFrame.ConnectionStatusBar.SetStatusText(
+            self.GetTextStatus(status), 1)
         self.AppFrame.ProgressStatusBar.SetValue(progress)
 
     def HidePLCProgress(self):
@@ -1420,19 +1499,23 @@
     def SnapshotAndResetDebugValuesBuffers(self):
         if self._connector is not None:
             plc_status, Traces = self._connector.GetTraceVariables()
-            # print [dict.keys() for IECPath, (dict, log, status, fvalue) in self.IECdebug_datas.items()]
+            # print [dict.keys() for IECPath, (dict, log, status, fvalue) in
+            # self.IECdebug_datas.items()]
             if plc_status == "Started":
                 if len(Traces) > 0:
                     for debug_tick, debug_buff in Traces:
-                        debug_vars = UnpackDebugBuffer(debug_buff, self.TracedIECTypes)
+                        debug_vars = UnpackDebugBuffer(
+                            debug_buff, self.TracedIECTypes)
                         if debug_vars is not None and len(debug_vars) == len(self.TracedIECPath):
                             for IECPath, values_buffer, value in izip(
                                     self.TracedIECPath,
                                     self.DebugValuesBuffers,
                                     debug_vars):
-                                IECdebug_data = self.IECdebug_datas.get(IECPath, None)
+                                IECdebug_data = self.IECdebug_datas.get(
+                                    IECPath, None)
                                 if IECdebug_data is not None and value is not None:
-                                    forced = IECdebug_data[2:4] == ["Forced", value]
+                                    forced = IECdebug_data[2:4] == [
+                                        "Forced", value]
                                     if not IECdebug_data[4] and len(values_buffer) > 0:
                                         values_buffer[-1] = (value, forced)
                                     else:
@@ -1461,14 +1544,17 @@
                     IECPathsToPop.append(IECPath)
                 elif IECPath != "__tick__":
                     # Convert
-                    Idx, IEC_Type = self._IECPathToIdx.get(IECPath, (None, None))
+                    Idx, IEC_Type = self._IECPathToIdx.get(
+                        IECPath, (None, None))
                     if Idx is not None:
                         if IEC_Type in DebugTypesSize:
                             Idxs.append((Idx, IEC_Type, fvalue, IECPath))
                         else:
-                            self.logger.write_warning(_("Debug: Unsupported type to debug '%s'\n") % IEC_Type)
+                            self.logger.write_warning(
+                                _("Debug: Unsupported type to debug '%s'\n") % IEC_Type)
                     else:
-                        self.logger.write_warning(_("Debug: Unknown variable '%s'\n") % IECPath)
+                        self.logger.write_warning(
+                            _("Debug: Unknown variable '%s'\n") % IECPath)
             for IECPathToPop in IECPathsToPop:
                 self.IECdebug_datas.pop(IECPathToPop)
 
@@ -1495,8 +1581,10 @@
         # Links between PLC located variables and real variables are not ready
         if self.IsPLCStarted():
             # Timer to prevent rapid-fire when registering many variables
-            # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead
-            self.DebugTimer = Timer(0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector])
+            # use wx.CallAfter use keep using same thread. TODO : use wx.Timer
+            # instead
+            self.DebugTimer = Timer(
+                0.5, wx.CallAfter, args=[self.RegisterDebugVarToConnector])
             # Rearm anti-rapid-fire timer
             self.DebugTimer.start()
 
@@ -1600,9 +1688,11 @@
         if len(self.TracedIECPath) == len(buffers):
             for IECPath, values in izip(self.TracedIECPath, buffers):
                 if len(values) > 0:
-                    self.CallWeakcallables(IECPath, "NewValues", debug_ticks, values)
+                    self.CallWeakcallables(
+                        IECPath, "NewValues", debug_ticks, values)
             if len(debug_ticks) > 0:
-                self.CallWeakcallables("__tick__", "NewDataAvailable", debug_ticks)
+                self.CallWeakcallables(
+                    "__tick__", "NewDataAvailable", debug_ticks)
 
         delay = time.time() - start_time
         next_refresh = max(REFRESH_PERIOD - delay, 0.2 * delay)
@@ -1667,7 +1757,8 @@
     def _Connect(self):
         # don't accept re-connetion if already connected
         if self._connector is not None:
-            self.logger.write_error(_("Already connected. Please disconnect\n"))
+            self.logger.write_error(
+                _("Already connected. Please disconnect\n"))
             return
 
         # Get connector uri
@@ -1705,7 +1796,8 @@
         try:
             self._SetConnector(connectors.ConnectorFactory(uri, self))
         except Exception:
-            self.logger.write_error(_("Exception while connecting %s!\n") % uri)
+            self.logger.write_error(
+                _("Exception while connecting %s!\n") % uri)
             self.logger.write_error(traceback.format_exc())
 
         # Did connection success ?
@@ -1713,10 +1805,6 @@
             # Oups.
             self.logger.write_error(_("Connection failed to %s!\n") % uri)
         else:
-            self.ShowMethod("_Connect", False)
-            self.ShowMethod("_Disconnect", True)
-            self.ShowMethod("_Transfer", True)
-
             self.CompareLocalAndRemotePLC()
 
             # Init with actual PLC status and print it
@@ -1726,7 +1814,8 @@
                     self.logger.write(_("Debugger ready\n"))
                     self._connect_debug()
                 else:
-                    self.logger.write_warning(_("Debug does not match PLC - stop/transfert/start to re-enable\n"))
+                    self.logger.write_warning(
+                        _("Debug does not match PLC - stop/transfert/start to re-enable\n"))
 
     def CompareLocalAndRemotePLC(self):
         if self._connector is None:
@@ -1737,7 +1826,8 @@
         if MD5 is not None:
             if not self._connector.MatchMD5(MD5):
                 # self.logger.write_warning(
-                #     _("Latest build does not match with target, please transfer.\n"))
+                # _("Latest build does not match with target, please
+                # transfer.\n"))
                 self.EnableMethod("_Transfer", True)
             else:
                 # self.logger.write(
@@ -1770,7 +1860,8 @@
 
         # Check if md5 file is empty : ask user to build PLC
         if MD5 is None:
-            self.logger.write_error(_("Failed : Must build before transfer.\n"))
+            self.logger.write_error(
+                _("Failed : Must build before transfer.\n"))
             return False
 
         # Compare PLC project with PLC on target
@@ -1805,7 +1896,8 @@
                     self.logger.write_error(_("Transfer failed\n"))
                 self.HidePLCProgress()
             else:
-                self.logger.write_error(_("No PLC to transfer (did build succeed ?)\n"))
+                self.logger.write_error(
+                    _("No PLC to transfer (did build succeed ?)\n"))
 
         wx.CallAfter(self.UpdateMethodsFromPLCStatus)
 
--- a/bacnet/BacnetSlaveEditor.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/bacnet/BacnetSlaveEditor.py	Wed Aug 08 13:46:19 2018 +0200
@@ -23,329 +23,327 @@
 # 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 collections import Counter
+
 import wx
-from collections        import Counter
-from pickle             import dump
+
+# Import some libraries on Beremiz code
 from util.BitmapLibrary import GetBitmap
-
-
-
-# Import some libraries on Beremiz code...
-from controls.CustomGrid        import CustomGrid
-from controls.CustomTable       import CustomTable
+from controls.CustomGrid import CustomGrid
+from controls.CustomTable import CustomTable
 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor
-from graphics.GraphicCommons    import ERROR_HIGHLIGHT
-
+from graphics.GraphicCommons import ERROR_HIGHLIGHT
 
 
 # BACnet Engineering units taken from: ASHRAE 135-2016, clause/chapter 21
-BACnetEngineeringUnits = [ 
-    ('(Acceleration) meters-per-second-per-second (166)',              166   ),
-    ('(Area) square-meters (0)',                                       0     ),
-    ('(Area) square-centimeters (116)',                                116   ),
-    ('(Area) square-feet (1)',                                         1     ),
-    ('(Area) square-inches (115)',                                     115   ),
-    ('(Currency) currency1 (105)',                                     105   ),
-    ('(Currency) currency2 (106)',                                     106   ),
-    ('(Currency) currency3 (107)',                                     107   ),
-    ('(Currency) currency4 (108)',                                     108   ),
-    ('(Currency) currency5 (109)',                                     109   ),
-    ('(Currency) currency6 (110)',                                     110   ),
-    ('(Currency) currency7 (111)',                                     111   ),
-    ('(Currency) currency8 (112)',                                     112   ),
-    ('(Currency) currency9 (113)',                                     113   ),
-    ('(Currency) currency10 (114)',                                    114   ),
-    ('(Electrical) milliamperes (2)',                                  2     ),
-    ('(Electrical) amperes (3)',                                       3     ),
-    ('(Electrical) amperes-per-meter (167)',                           167   ),
-    ('(Electrical) amperes-per-square-meter (168)',                    168   ),
-    ('(Electrical) ampere-square-meters (169)',                        169   ),
-    ('(Electrical) decibels (199)',                                    199   ),
-    ('(Electrical) decibels-millivolt (200)',                          200   ),
-    ('(Electrical) decibels-volt (201)',                               201   ),
-    ('(Electrical) farads (170)',                                      170   ),
-    ('(Electrical) henrys (171)',                                      171   ),
-    ('(Electrical) ohms (4)',                                          4     ),
-    ('(Electrical) ohm-meter-squared-per-meter (237)',                 237   ),
-    ('(Electrical) ohm-meters (172)',                                  172   ),
-    ('(Electrical) milliohms (145)',                                   145   ),
-    ('(Electrical) kilohms (122)',                                     122   ),
-    ('(Electrical) megohms (123)',                                     123   ),
-    ('(Electrical) microsiemens (190)',                                190   ),
-    ('(Electrical) millisiemens (202)',                                202   ),
-    ('(Electrical) siemens (173)',                                     173   ),
-    ('(Electrical) siemens-per-meter (174)',                           174   ),
-    ('(Electrical) teslas (175)',                                      175   ),
-    ('(Electrical) volts (5)',                                         5     ),
-    ('(Electrical) millivolts (124)',                                  124   ),
-    ('(Electrical) kilovolts (6)',                                     6     ),
-    ('(Electrical) megavolts (7)',                                     7     ),
-    ('(Electrical) volt-amperes (8)',                                  8     ),
-    ('(Electrical) kilovolt-amperes (9)',                              9     ),
-    ('(Electrical) megavolt-amperes (10)',                             10    ),
-    ('(Electrical) volt-amperes-reactive (11)',                        11    ),
-    ('(Electrical) kilovolt-amperes-reactive (12)',                    12    ),
-    ('(Electrical) megavolt-amperes-reactive (13)',                    13    ),
-    ('(Electrical) volts-per-degree-kelvin (176)',                     176   ),
-    ('(Electrical) volts-per-meter (177)',                             177   ),
-    ('(Electrical) degrees-phase (14)',                                14    ),
-    ('(Electrical) power-factor (15)',                                 15    ),
-    ('(Electrical) webers (178)',                                      178   ),
-    ('(Energy) ampere-seconds (238)',                                  238   ),
-    ('(Energy) volt-ampere-hours (239)',                               239   ),
-    ('(Energy) kilovolt-ampere-hours (240)',                           240   ),
-    ('(Energy) megavolt-ampere-hours (241)',                           241   ),
-    ('(Energy) volt-ampere-hours-reactive (242)',                      242   ),
-    ('(Energy) kilovolt-ampere-hours-reactive (243)',                  243   ),
-    ('(Energy) megavolt-ampere-hours-reactive (244)',                  244   ),
-    ('(Energy) volt-square-hours (245)',                               245   ),
-    ('(Energy) ampere-square-hours (246)',                             246   ),
-    ('(Energy) joules (16)',                                           16    ),
-    ('(Energy) kilojoules (17)',                                       17    ),
-    ('(Energy) kilojoules-per-kilogram (125)',                         125   ),
-    ('(Energy) megajoules (126)',                                      126   ),
-    ('(Energy) watt-hours (18)',                                       18    ),
-    ('(Energy) kilowatt-hours (19)',                                   19    ),
-    ('(Energy) megawatt-hours (146)',                                  146   ),
-    ('(Energy) watt-hours-reactive (203)',                             203   ),
-    ('(Energy) kilowatt-hours-reactive (204)',                         204   ),
-    ('(Energy) megawatt-hours-reactive (205)',                         205   ),
-    ('(Energy) btus (20)',                                             20    ),
-    ('(Energy) kilo-btus (147)',                                       147   ),
-    ('(Energy) mega-btus (148)',                                       148   ),
-    ('(Energy) therms (21)',                                           21    ),
-    ('(Energy) ton-hours (22)',                                        22    ),
-    ('(Enthalpy) joules-per-kilogram-dry-air (23)',                    23    ),
-    ('(Enthalpy) kilojoules-per-kilogram-dry-air (149)',               149   ),
-    ('(Enthalpy) megajoules-per-kilogram-dry-air (150)',               150   ),
-    ('(Enthalpy) btus-per-pound-dry-air (24)',                         24    ),
-    ('(Enthalpy) btus-per-pound (117)',                                117   ),
-    ('(Entropy) joules-per-degree-kelvin (127)',                       127   ),
-    ('(Entropy) kilojoules-per-degree-kelvin (151)',                   151   ),
-    ('(Entropy) megajoules-per-degree-kelvin (152)',                   152   ),
-    ('(Entropy) joules-per-kilogram-degree-kelvin (128)',              128   ),
-    ('(Force) newton (153)',                                           153   ),
-    ('(Frequency) cycles-per-hour (25)',                               25    ),
-    ('(Frequency) cycles-per-minute (26)',                             26    ),
-    ('(Frequency) hertz (27)',                                         27    ),
-    ('(Frequency) kilohertz (129)',                                    129   ),
-    ('(Frequency) megahertz (130)',                                    130   ),
-    ('(Frequency) per-hour (131)',                                     131   ),
-    ('(Humidity) grams-of-water-per-kilogram-dry-air (28)',            28    ),
-    ('(Humidity) percent-relative-humidity (29)',                      29    ),
-    ('(Length) micrometers (194)',                                     194   ),
-    ('(Length) millimeters (30)',                                      30    ),
-    ('(Length) centimeters (118)',                                     118   ),
-    ('(Length) kilometers (193)',                                      193   ),
-    ('(Length) meters (31)',                                           31    ),
-    ('(Length) inches (32)',                                           32    ),
-    ('(Length) feet (33)',                                             33    ),
-    ('(Light) candelas (179)',                                         179   ),
-    ('(Light) candelas-per-square-meter (180)',                        180   ),
-    ('(Light) watts-per-square-foot (34)',                             34    ),
-    ('(Light) watts-per-square-meter (35)',                            35    ),
-    ('(Light) lumens (36)',                                            36    ),
-    ('(Light) luxes (37)',                                             37    ),
-    ('(Light) foot-candles (38)',                                      38    ),
-    ('(Mass) milligrams (196)',                                        196   ),
-    ('(Mass) grams (195)',                                             195   ),
-    ('(Mass) kilograms (39)',                                          39    ),
-    ('(Mass) pounds-mass (40)',                                        40    ),
-    ('(Mass) tons (41)',                                               41    ),
-    ('(Mass Flow) grams-per-second (154)',                             154   ),
-    ('(Mass Flow) grams-per-minute (155)',                             155   ),
-    ('(Mass Flow) kilograms-per-second (42)',                          42    ),
-    ('(Mass Flow) kilograms-per-minute (43)',                          43    ),
-    ('(Mass Flow) kilograms-per-hour (44)',                            44    ),
-    ('(Mass Flow) pounds-mass-per-second (119)',                       119   ),
-    ('(Mass Flow) pounds-mass-per-minute (45)',                        45    ),
-    ('(Mass Flow) pounds-mass-per-hour (46)',                          46    ),
-    ('(Mass Flow) tons-per-hour (156)',                                156   ),
-    ('(Power) milliwatts (132)',                                       132   ),
-    ('(Power) watts (47)',                                             47    ),
-    ('(Power) kilowatts (48)',                                         48    ),
-    ('(Power) megawatts (49)',                                         49    ),
-    ('(Power) btus-per-hour (50)',                                     50    ),
-    ('(Power) kilo-btus-per-hour (157)',                               157   ),
-    ('(Power) joule-per-hours (247)',                                  247   ),
-    ('(Power) horsepower (51)',                                        51    ),
-    ('(Power) tons-refrigeration (52)',                                52    ),
-    ('(Pressure) pascals (53)',                                        53    ),
-    ('(Pressure) hectopascals (133)',                                  133   ),
-    ('(Pressure) kilopascals (54)',                                    54    ),
-    ('(Pressure) millibars (134)',                                     134   ),
-    ('(Pressure) bars (55)',                                           55    ),
-    ('(Pressure) pounds-force-per-square-inch (56)',                   56    ),
-    ('(Pressure) millimeters-of-water (206)',                          206   ),
-    ('(Pressure) centimeters-of-water (57)',                           57    ),
-    ('(Pressure) inches-of-water (58)',                                58    ),
-    ('(Pressure) millimeters-of-mercury (59)',                         59    ),
-    ('(Pressure) centimeters-of-mercury (60)',                         60    ),
-    ('(Pressure) inches-of-mercury (61)',                              61    ),
-    ('(Temperature) degrees-celsius (62)',                             62    ),
-    ('(Temperature) degrees-kelvin (63)',                              63    ),
-    ('(Temperature) degrees-kelvin-per-hour (181)',                    181   ),
-    ('(Temperature) degrees-kelvin-per-minute (182)',                  182   ),
-    ('(Temperature) degrees-fahrenheit (64)',                          64    ),
-    ('(Temperature) degree-days-celsius (65)',                         65    ),
-    ('(Temperature) degree-days-fahrenheit (66)',                      66    ),
-    ('(Temperature) delta-degrees-fahrenheit (120)',                   120   ),
-    ('(Temperature) delta-degrees-kelvin (121)',                       121   ),
-    ('(Time) years (67)',                                              67    ),
-    ('(Time) months (68)',                                             68    ),
-    ('(Time) weeks (69)',                                              69    ),
-    ('(Time) days (70)',                                               70    ),
-    ('(Time) hours (71)',                                              71    ),
-    ('(Time) minutes (72)',                                            72    ),
-    ('(Time) seconds (73)',                                            73    ),
-    ('(Time) hundredths-seconds (158)',                                158   ),
-    ('(Time) milliseconds (159)',                                      159   ),
-    ('(Torque) newton-meters (160)',                                   160   ),
-    ('(Velocity) millimeters-per-second (161)',                        161   ),
-    ('(Velocity) millimeters-per-minute (162)',                        162   ),
-    ('(Velocity) meters-per-second (74)',                              74    ),
-    ('(Velocity) meters-per-minute (163)',                             163   ),
-    ('(Velocity) meters-per-hour (164)',                               164   ), 
-    ('(Velocity) kilometers-per-hour (75)',                            75    ), 
-    ('(Velocity) feet-per-second (76)',                                76    ), 
-    ('(Velocity) feet-per-minute (77)',                                77    ), 
-    ('(Velocity) miles-per-hour (78)',                                 78    ), 
-    ('(Volume) cubic-feet (79)',                                       79    ), 
-    ('(Volume) cubic-meters (80)',                                     80    ), 
-    ('(Volume) imperial-gallons (81)',                                 81    ), 
-    ('(Volume) milliliters (197)',                                     197   ), 
-    ('(Volume) liters (82)',                                           82    ), 
-    ('(Volume) us-gallons (83)',                                       83    ), 
-    ('(Volumetric Flow) cubic-feet-per-second (142)',                  142   ), 
-    ('(Volumetric Flow) cubic-feet-per-minute (84)',                   84    ), 
-    ('(Volumetric Flow) million-standard-cubic-feet-per-minute (254)', 254   ), 
-    ('(Volumetric Flow) cubic-feet-per-hour (191)',                    191   ), 
-    ('(Volumetric Flow) cubic-feet-per-day (248)',                     248   ), 
-    ('(Volumetric Flow) standard-cubic-feet-per-day (47808)',          47808 ), 
-    ('(Volumetric Flow) million-standard-cubic-feet-per-day (47809)',  47809 ), 
-    ('(Volumetric Flow) thousand-cubic-feet-per-day (47810)',          47810 ), 
-    ('(Volumetric Flow) thousand-standard-cubic-feet-per-day (47811)', 47811 ), 
-    ('(Volumetric Flow) pounds-mass-per-day (47812)',                  47812 ), 
-    ('(Volumetric Flow) cubic-meters-per-second (85)',                 85    ), 
-    ('(Volumetric Flow) cubic-meters-per-minute (165)',                165   ), 
-    ('(Volumetric Flow) cubic-meters-per-hour (135)',                  135   ), 
-    ('(Volumetric Flow) cubic-meters-per-day (249)',                   249   ), 
-    ('(Volumetric Flow) imperial-gallons-per-minute (86)',             86    ), 
-    ('(Volumetric Flow) milliliters-per-second (198)',                 198   ), 
-    ('(Volumetric Flow) liters-per-second (87)',                       87    ), 
-    ('(Volumetric Flow) liters-per-minute (88)',                       88    ), 
-    ('(Volumetric Flow) liters-per-hour (136)',                        136   ), 
-    ('(Volumetric Flow) us-gallons-per-minute (89)',                   89    ), 
-    ('(Volumetric Flow) us-gallons-per-hour (192)',                    192   ), 
-    ('(Other) degrees-angular (90)',                                   90    ), 
-    ('(Other) degrees-celsius-per-hour (91)',                          91    ), 
-    ('(Other) degrees-celsius-per-minute (92)',                        92    ), 
-    ('(Other) degrees-fahrenheit-per-hour (93)',                       93    ), 
-    ('(Other) degrees-fahrenheit-per-minute (94)',                     94    ), 
-    ('(Other) joule-seconds (183)',                                    183   ), 
-    ('(Other) kilograms-per-cubic-meter (186)',                        186   ), 
-    ('(Other) kilowatt-hours-per-square-meter (137)',                  137   ), 
-    ('(Other) kilowatt-hours-per-square-foot (138)',                   138   ), 
-    ('(Other) watt-hours-per-cubic-meter (250)',                       250   ), 
-    ('(Other) joules-per-cubic-meter (251)',                           251   ), 
-    ('(Other) megajoules-per-square-meter (139)',                      139   ), 
-    ('(Other) megajoules-per-square-foot (140)',                       140   ), 
-    ('(Other) mole-percent (252)',                                     252   ), 
-    ('(Other) no-units (95)',                                          95    ), 
-    ('(Other) newton-seconds (187)',                                   187   ), 
-    ('(Other) newtons-per-meter (188)',                                188   ), 
-    ('(Other) parts-per-million (96)',                                 96    ), 
-    ('(Other) parts-per-billion (97)',                                 97    ), 
-    ('(Other) pascal-seconds (253)',                                   253   ), 
-    ('(Other) percent (98)',                                           98    ), 
-    ('(Other) percent-obscuration-per-foot (143)',                     143   ), 
-    ('(Other) percent-obscuration-per-meter (144)',                    144   ), 
-    ('(Other) percent-per-second (99)',                                99    ), 
-    ('(Other) per-minute (100)',                                       100   ), 
-    ('(Other) per-second (101)',                                       101   ), 
-    ('(Other) psi-per-degree-fahrenheit (102)',                        102   ), 
-    ('(Other) radians (103)',                                          103   ), 
-    ('(Other) radians-per-second (184)',                               184   ), 
-    ('(Other) revolutions-per-minute (104)',                           104   ), 
-    ('(Other) square-meters-per-newton (185)',                         185   ), 
-    ('(Other) watts-per-meter-per-degree-kelvin (189)',                189   ), 
-    ('(Other) watts-per-square-meter-degree-kelvin (141)',             141   ), 
-    ('(Other) per-mille (207)',                                        207   ), 
-    ('(Other) grams-per-gram (208)',                                   208   ), 
-    ('(Other) kilograms-per-kilogram (209)',                           209   ), 
-    ('(Other) grams-per-kilogram (210)',                               210   ), 
-    ('(Other) milligrams-per-gram (211)',                              211   ), 
-    ('(Other) milligrams-per-kilogram (212)',                          212   ), 
-    ('(Other) grams-per-milliliter (213)',                             213   ), 
-    ('(Other) grams-per-liter (214)',                                  214   ), 
-    ('(Other) milligrams-per-liter (215)',                             215   ), 
-    ('(Other) micrograms-per-liter (216)',                             216   ), 
-    ('(Other) grams-per-cubic-meter (217)',                            217   ), 
-    ('(Other) milligrams-per-cubic-meter (218)',                       218   ), 
-    ('(Other) micrograms-per-cubic-meter (219)',                       219   ), 
-    ('(Other) nanograms-per-cubic-meter (220)',                        220   ), 
-    ('(Other) grams-per-cubic-centimeter (221)',                       221   ), 
-    ('(Other) becquerels (222)',                                       222   ), 
-    ('(Other) kilobecquerels (223)',                                   223   ), 
-    ('(Other) megabecquerels (224)',                                   224   ), 
-    ('(Other) gray (225)',                                             225   ), 
-    ('(Other) milligray (226)',                                        226   ), 
-    ('(Other) microgray (227)',                                        227   ), 
-    ('(Other) sieverts (228)',                                         228   ), 
-    ('(Other) millisieverts (229)',                                    229   ), 
-    ('(Other) microsieverts (230)',                                    230   ), 
-    ('(Other) microsieverts-per-hour (231)',                           231   ), 
-    ('(Other) millirems (47814)',                                      47814 ), 
-    ('(Other) millirems-per-hour (47815)',                             47815 ), 
-    ('(Other) decibels-a (232)',                                       232   ), 
-    ('(Other) nephelometric-turbidity-unit (233)',                     233   ), 
-    ('(Other) pH (234)',                                               234   ), 
-    ('(Other) grams-per-square-meter (235)',                           235   ), 
-    ('(Other) minutes-per-degree-kelvin (236)',                        236   )  
-    ] # BACnetEngineeringUnits 
-   
-   
-   
+BACnetEngineeringUnits = [
+    ('(Acceleration) meters-per-second-per-second (166)',              166),
+    ('(Area) square-meters (0)',                                       0),
+    ('(Area) square-centimeters (116)',                                116),
+    ('(Area) square-feet (1)',                                         1),
+    ('(Area) square-inches (115)',                                     115),
+    ('(Currency) currency1 (105)',                                     105),
+    ('(Currency) currency2 (106)',                                     106),
+    ('(Currency) currency3 (107)',                                     107),
+    ('(Currency) currency4 (108)',                                     108),
+    ('(Currency) currency5 (109)',                                     109),
+    ('(Currency) currency6 (110)',                                     110),
+    ('(Currency) currency7 (111)',                                     111),
+    ('(Currency) currency8 (112)',                                     112),
+    ('(Currency) currency9 (113)',                                     113),
+    ('(Currency) currency10 (114)',                                    114),
+    ('(Electrical) milliamperes (2)',                                  2),
+    ('(Electrical) amperes (3)',                                       3),
+    ('(Electrical) amperes-per-meter (167)',                           167),
+    ('(Electrical) amperes-per-square-meter (168)',                    168),
+    ('(Electrical) ampere-square-meters (169)',                        169),
+    ('(Electrical) decibels (199)',                                    199),
+    ('(Electrical) decibels-millivolt (200)',                          200),
+    ('(Electrical) decibels-volt (201)',                               201),
+    ('(Electrical) farads (170)',                                      170),
+    ('(Electrical) henrys (171)',                                      171),
+    ('(Electrical) ohms (4)',                                          4),
+    ('(Electrical) ohm-meter-squared-per-meter (237)',                 237),
+    ('(Electrical) ohm-meters (172)',                                  172),
+    ('(Electrical) milliohms (145)',                                   145),
+    ('(Electrical) kilohms (122)',                                     122),
+    ('(Electrical) megohms (123)',                                     123),
+    ('(Electrical) microsiemens (190)',                                190),
+    ('(Electrical) millisiemens (202)',                                202),
+    ('(Electrical) siemens (173)',                                     173),
+    ('(Electrical) siemens-per-meter (174)',                           174),
+    ('(Electrical) teslas (175)',                                      175),
+    ('(Electrical) volts (5)',                                         5),
+    ('(Electrical) millivolts (124)',                                  124),
+    ('(Electrical) kilovolts (6)',                                     6),
+    ('(Electrical) megavolts (7)',                                     7),
+    ('(Electrical) volt-amperes (8)',                                  8),
+    ('(Electrical) kilovolt-amperes (9)',                              9),
+    ('(Electrical) megavolt-amperes (10)',                             10),
+    ('(Electrical) volt-amperes-reactive (11)',                        11),
+    ('(Electrical) kilovolt-amperes-reactive (12)',                    12),
+    ('(Electrical) megavolt-amperes-reactive (13)',                    13),
+    ('(Electrical) volts-per-degree-kelvin (176)',                     176),
+    ('(Electrical) volts-per-meter (177)',                             177),
+    ('(Electrical) degrees-phase (14)',                                14),
+    ('(Electrical) power-factor (15)',                                 15),
+    ('(Electrical) webers (178)',                                      178),
+    ('(Energy) ampere-seconds (238)',                                  238),
+    ('(Energy) volt-ampere-hours (239)',                               239),
+    ('(Energy) kilovolt-ampere-hours (240)',                           240),
+    ('(Energy) megavolt-ampere-hours (241)',                           241),
+    ('(Energy) volt-ampere-hours-reactive (242)',                      242),
+    ('(Energy) kilovolt-ampere-hours-reactive (243)',                  243),
+    ('(Energy) megavolt-ampere-hours-reactive (244)',                  244),
+    ('(Energy) volt-square-hours (245)',                               245),
+    ('(Energy) ampere-square-hours (246)',                             246),
+    ('(Energy) joules (16)',                                           16),
+    ('(Energy) kilojoules (17)',                                       17),
+    ('(Energy) kilojoules-per-kilogram (125)',                         125),
+    ('(Energy) megajoules (126)',                                      126),
+    ('(Energy) watt-hours (18)',                                       18),
+    ('(Energy) kilowatt-hours (19)',                                   19),
+    ('(Energy) megawatt-hours (146)',                                  146),
+    ('(Energy) watt-hours-reactive (203)',                             203),
+    ('(Energy) kilowatt-hours-reactive (204)',                         204),
+    ('(Energy) megawatt-hours-reactive (205)',                         205),
+    ('(Energy) btus (20)',                                             20),
+    ('(Energy) kilo-btus (147)',                                       147),
+    ('(Energy) mega-btus (148)',                                       148),
+    ('(Energy) therms (21)',                                           21),
+    ('(Energy) ton-hours (22)',                                        22),
+    ('(Enthalpy) joules-per-kilogram-dry-air (23)',                    23),
+    ('(Enthalpy) kilojoules-per-kilogram-dry-air (149)',               149),
+    ('(Enthalpy) megajoules-per-kilogram-dry-air (150)',               150),
+    ('(Enthalpy) btus-per-pound-dry-air (24)',                         24),
+    ('(Enthalpy) btus-per-pound (117)',                                117),
+    ('(Entropy) joules-per-degree-kelvin (127)',                       127),
+    ('(Entropy) kilojoules-per-degree-kelvin (151)',                   151),
+    ('(Entropy) megajoules-per-degree-kelvin (152)',                   152),
+    ('(Entropy) joules-per-kilogram-degree-kelvin (128)',              128),
+    ('(Force) newton (153)',                                           153),
+    ('(Frequency) cycles-per-hour (25)',                               25),
+    ('(Frequency) cycles-per-minute (26)',                             26),
+    ('(Frequency) hertz (27)',                                         27),
+    ('(Frequency) kilohertz (129)',                                    129),
+    ('(Frequency) megahertz (130)',                                    130),
+    ('(Frequency) per-hour (131)',                                     131),
+    ('(Humidity) grams-of-water-per-kilogram-dry-air (28)',            28),
+    ('(Humidity) percent-relative-humidity (29)',                      29),
+    ('(Length) micrometers (194)',                                     194),
+    ('(Length) millimeters (30)',                                      30),
+    ('(Length) centimeters (118)',                                     118),
+    ('(Length) kilometers (193)',                                      193),
+    ('(Length) meters (31)',                                           31),
+    ('(Length) inches (32)',                                           32),
+    ('(Length) feet (33)',                                             33),
+    ('(Light) candelas (179)',                                         179),
+    ('(Light) candelas-per-square-meter (180)',                        180),
+    ('(Light) watts-per-square-foot (34)',                             34),
+    ('(Light) watts-per-square-meter (35)',                            35),
+    ('(Light) lumens (36)',                                            36),
+    ('(Light) luxes (37)',                                             37),
+    ('(Light) foot-candles (38)',                                      38),
+    ('(Mass) milligrams (196)',                                        196),
+    ('(Mass) grams (195)',                                             195),
+    ('(Mass) kilograms (39)',                                          39),
+    ('(Mass) pounds-mass (40)',                                        40),
+    ('(Mass) tons (41)',                                               41),
+    ('(Mass Flow) grams-per-second (154)',                             154),
+    ('(Mass Flow) grams-per-minute (155)',                             155),
+    ('(Mass Flow) kilograms-per-second (42)',                          42),
+    ('(Mass Flow) kilograms-per-minute (43)',                          43),
+    ('(Mass Flow) kilograms-per-hour (44)',                            44),
+    ('(Mass Flow) pounds-mass-per-second (119)',                       119),
+    ('(Mass Flow) pounds-mass-per-minute (45)',                        45),
+    ('(Mass Flow) pounds-mass-per-hour (46)',                          46),
+    ('(Mass Flow) tons-per-hour (156)',                                156),
+    ('(Power) milliwatts (132)',                                       132),
+    ('(Power) watts (47)',                                             47),
+    ('(Power) kilowatts (48)',                                         48),
+    ('(Power) megawatts (49)',                                         49),
+    ('(Power) btus-per-hour (50)',                                     50),
+    ('(Power) kilo-btus-per-hour (157)',                               157),
+    ('(Power) joule-per-hours (247)',                                  247),
+    ('(Power) horsepower (51)',                                        51),
+    ('(Power) tons-refrigeration (52)',                                52),
+    ('(Pressure) pascals (53)',                                        53),
+    ('(Pressure) hectopascals (133)',                                  133),
+    ('(Pressure) kilopascals (54)',                                    54),
+    ('(Pressure) millibars (134)',                                     134),
+    ('(Pressure) bars (55)',                                           55),
+    ('(Pressure) pounds-force-per-square-inch (56)',                   56),
+    ('(Pressure) millimeters-of-water (206)',                          206),
+    ('(Pressure) centimeters-of-water (57)',                           57),
+    ('(Pressure) inches-of-water (58)',                                58),
+    ('(Pressure) millimeters-of-mercury (59)',                         59),
+    ('(Pressure) centimeters-of-mercury (60)',                         60),
+    ('(Pressure) inches-of-mercury (61)',                              61),
+    ('(Temperature) degrees-celsius (62)',                             62),
+    ('(Temperature) degrees-kelvin (63)',                              63),
+    ('(Temperature) degrees-kelvin-per-hour (181)',                    181),
+    ('(Temperature) degrees-kelvin-per-minute (182)',                  182),
+    ('(Temperature) degrees-fahrenheit (64)',                          64),
+    ('(Temperature) degree-days-celsius (65)',                         65),
+    ('(Temperature) degree-days-fahrenheit (66)',                      66),
+    ('(Temperature) delta-degrees-fahrenheit (120)',                   120),
+    ('(Temperature) delta-degrees-kelvin (121)',                       121),
+    ('(Time) years (67)',                                              67),
+    ('(Time) months (68)',                                             68),
+    ('(Time) weeks (69)',                                              69),
+    ('(Time) days (70)',                                               70),
+    ('(Time) hours (71)',                                              71),
+    ('(Time) minutes (72)',                                            72),
+    ('(Time) seconds (73)',                                            73),
+    ('(Time) hundredths-seconds (158)',                                158),
+    ('(Time) milliseconds (159)',                                      159),
+    ('(Torque) newton-meters (160)',                                   160),
+    ('(Velocity) millimeters-per-second (161)',                        161),
+    ('(Velocity) millimeters-per-minute (162)',                        162),
+    ('(Velocity) meters-per-second (74)',                              74),
+    ('(Velocity) meters-per-minute (163)',                             163),
+    ('(Velocity) meters-per-hour (164)',                               164),
+    ('(Velocity) kilometers-per-hour (75)',                            75),
+    ('(Velocity) feet-per-second (76)',                                76),
+    ('(Velocity) feet-per-minute (77)',                                77),
+    ('(Velocity) miles-per-hour (78)',                                 78),
+    ('(Volume) cubic-feet (79)',                                       79),
+    ('(Volume) cubic-meters (80)',                                     80),
+    ('(Volume) imperial-gallons (81)',                                 81),
+    ('(Volume) milliliters (197)',                                     197),
+    ('(Volume) liters (82)',                                           82),
+    ('(Volume) us-gallons (83)',                                       83),
+    ('(Volumetric Flow) cubic-feet-per-second (142)',                  142),
+    ('(Volumetric Flow) cubic-feet-per-minute (84)',                   84),
+    ('(Volumetric Flow) million-standard-cubic-feet-per-minute (254)', 254),
+    ('(Volumetric Flow) cubic-feet-per-hour (191)',                    191),
+    ('(Volumetric Flow) cubic-feet-per-day (248)',                     248),
+    ('(Volumetric Flow) standard-cubic-feet-per-day (47808)',          47808),
+    ('(Volumetric Flow) million-standard-cubic-feet-per-day (47809)',  47809),
+    ('(Volumetric Flow) thousand-cubic-feet-per-day (47810)',          47810),
+    ('(Volumetric Flow) thousand-standard-cubic-feet-per-day (47811)', 47811),
+    ('(Volumetric Flow) pounds-mass-per-day (47812)',                  47812),
+    ('(Volumetric Flow) cubic-meters-per-second (85)',                 85),
+    ('(Volumetric Flow) cubic-meters-per-minute (165)',                165),
+    ('(Volumetric Flow) cubic-meters-per-hour (135)',                  135),
+    ('(Volumetric Flow) cubic-meters-per-day (249)',                   249),
+    ('(Volumetric Flow) imperial-gallons-per-minute (86)',             86),
+    ('(Volumetric Flow) milliliters-per-second (198)',                 198),
+    ('(Volumetric Flow) liters-per-second (87)',                       87),
+    ('(Volumetric Flow) liters-per-minute (88)',                       88),
+    ('(Volumetric Flow) liters-per-hour (136)',                        136),
+    ('(Volumetric Flow) us-gallons-per-minute (89)',                   89),
+    ('(Volumetric Flow) us-gallons-per-hour (192)',                    192),
+    ('(Other) degrees-angular (90)',                                   90),
+    ('(Other) degrees-celsius-per-hour (91)',                          91),
+    ('(Other) degrees-celsius-per-minute (92)',                        92),
+    ('(Other) degrees-fahrenheit-per-hour (93)',                       93),
+    ('(Other) degrees-fahrenheit-per-minute (94)',                     94),
+    ('(Other) joule-seconds (183)',                                    183),
+    ('(Other) kilograms-per-cubic-meter (186)',                        186),
+    ('(Other) kilowatt-hours-per-square-meter (137)',                  137),
+    ('(Other) kilowatt-hours-per-square-foot (138)',                   138),
+    ('(Other) watt-hours-per-cubic-meter (250)',                       250),
+    ('(Other) joules-per-cubic-meter (251)',                           251),
+    ('(Other) megajoules-per-square-meter (139)',                      139),
+    ('(Other) megajoules-per-square-foot (140)',                       140),
+    ('(Other) mole-percent (252)',                                     252),
+    ('(Other) no-units (95)',                                          95),
+    ('(Other) newton-seconds (187)',                                   187),
+    ('(Other) newtons-per-meter (188)',                                188),
+    ('(Other) parts-per-million (96)',                                 96),
+    ('(Other) parts-per-billion (97)',                                 97),
+    ('(Other) pascal-seconds (253)',                                   253),
+    ('(Other) percent (98)',                                           98),
+    ('(Other) percent-obscuration-per-foot (143)',                     143),
+    ('(Other) percent-obscuration-per-meter (144)',                    144),
+    ('(Other) percent-per-second (99)',                                99),
+    ('(Other) per-minute (100)',                                       100),
+    ('(Other) per-second (101)',                                       101),
+    ('(Other) psi-per-degree-fahrenheit (102)',                        102),
+    ('(Other) radians (103)',                                          103),
+    ('(Other) radians-per-second (184)',                               184),
+    ('(Other) revolutions-per-minute (104)',                           104),
+    ('(Other) square-meters-per-newton (185)',                         185),
+    ('(Other) watts-per-meter-per-degree-kelvin (189)',                189),
+    ('(Other) watts-per-square-meter-degree-kelvin (141)',             141),
+    ('(Other) per-mille (207)',                                        207),
+    ('(Other) grams-per-gram (208)',                                   208),
+    ('(Other) kilograms-per-kilogram (209)',                           209),
+    ('(Other) grams-per-kilogram (210)',                               210),
+    ('(Other) milligrams-per-gram (211)',                              211),
+    ('(Other) milligrams-per-kilogram (212)',                          212),
+    ('(Other) grams-per-milliliter (213)',                             213),
+    ('(Other) grams-per-liter (214)',                                  214),
+    ('(Other) milligrams-per-liter (215)',                             215),
+    ('(Other) micrograms-per-liter (216)',                             216),
+    ('(Other) grams-per-cubic-meter (217)',                            217),
+    ('(Other) milligrams-per-cubic-meter (218)',                       218),
+    ('(Other) micrograms-per-cubic-meter (219)',                       219),
+    ('(Other) nanograms-per-cubic-meter (220)',                        220),
+    ('(Other) grams-per-cubic-centimeter (221)',                       221),
+    ('(Other) becquerels (222)',                                       222),
+    ('(Other) kilobecquerels (223)',                                   223),
+    ('(Other) megabecquerels (224)',                                   224),
+    ('(Other) gray (225)',                                             225),
+    ('(Other) milligray (226)',                                        226),
+    ('(Other) microgray (227)',                                        227),
+    ('(Other) sieverts (228)',                                         228),
+    ('(Other) millisieverts (229)',                                    229),
+    ('(Other) microsieverts (230)',                                    230),
+    ('(Other) microsieverts-per-hour (231)',                           231),
+    ('(Other) millirems (47814)',                                      47814),
+    ('(Other) millirems-per-hour (47815)',                             47815),
+    ('(Other) decibels-a (232)',                                       232),
+    ('(Other) nephelometric-turbidity-unit (233)',                     233),
+    ('(Other) pH (234)',                                               234),
+    ('(Other) grams-per-square-meter (235)',                           235),
+    ('(Other) minutes-per-degree-kelvin (236)',                        236)
+]  # BACnetEngineeringUnits
+
+
 # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 = 4194303
 #  However, ObjectID 4194303 is not allowed!
-#  4194303 is used as a special value when object Id reference is referencing an undefined object 
+#  4194303 is used as a special value when object Id reference is referencing an undefined object
 #  (similar to NULL in C)
 BACnetObjectID_MAX = 4194302
 BACnetObjectID_NUL = 4194303
 
 
-
 # A base class
 # what would be a purely virtual class in C++
-class ObjectProperties:
+class ObjectProperties(object):
     # this __init_() function is currently not beeing used!
+
     def __init__(self):
-        #nothing to do 
+        # nothing to do
         return
 
 
 class BinaryObject(ObjectProperties):
-    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
-    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
-    #          Be sure to use these exact names for these BACnet object properties!
-    PropertyNames    = ["Object Identifier", "Object Name", "Description"]
-    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT]
-    ColumnSizes      = [        40         ,      80      ,      80      ]
-    PropertyConfig   = {"Object Identifier": {"GridCellEditor"  : wx.grid.GridCellNumberEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
-                                              "GridCellEditorParam": "0,4194302" 
-                                                                     # syntax for GridCellNumberEditor -> "min,max" 
-                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
-                                             },
-                        "Object Name"      : {"GridCellEditor"  : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer": wx.grid.GridCellStringRenderer},  
-                        "Description"      : {"GridCellEditor"  : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer": wx.grid.GridCellStringRenderer}
-                       }
-    
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
+    # Be sure to use these exact names for these BACnet object properties!
+    PropertyNames = ["Object Identifier", "Object Name", "Description"]
+    ColumnAlignments = [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+    ColumnSizes = [40, 80, 80]
+    PropertyConfig = {
+        "Object Identifier": {"GridCellEditor": wx.grid.GridCellNumberEditor,
+                              "GridCellRenderer": wx.grid.GridCellNumberRenderer,
+                              # syntax for GridCellNumberEditor -> "min,max"
+                              # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1
+                              "GridCellEditorParam": "0,4194302"},
+        "Object Name": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer},
+        "Description": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer}
+    }
+
+
 class AnalogObject(ObjectProperties):
-    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
-    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
     #          Be sure to use these exact names for these BACnet object properties!
     #
     # NOTE: Although it is not listed here (so it does not show up in the GUI, this object will also
@@ -353,200 +351,194 @@
     #       will store the ID corresponding to the "Engineering Units" currently chosen.
     #       This virtual property is kept synchronised to the "Engineering Units" property
     #       by the function PropertyChanged() which should be called by the OnCellChange event handler.
-    PropertyNames    = ["Object Identifier", "Object Name", "Description", "Engineering Units"] #'Unit ID'
-    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT,    wx.ALIGN_LEFT   ]
-    ColumnSizes      = [        40         ,      80      ,      80      ,         200        ]
-    PropertyConfig   = {"Object Identifier": {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
-                                              "GridCellEditorParam": "0,4194302"
-                                                                     # syntax for GridCellNumberEditor -> "min,max" 
-                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
-                                             },
-                        "Object Name"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
-                        "Description"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
-                        "Engineering Units": {"GridCellEditor"     : wx.grid.GridCellChoiceEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer,  
-                                                                     # use string renderer with choice editor!
-                                              "GridCellEditorParam": ','.join([x[0] for x in BACnetEngineeringUnits]) 
-                                                                     # syntax for GridCellChoiceEditor -> comma separated values 
-                                              }  
-                       }
-
-    # obj_properties should be a dictionary, with keys "Object Identifier", "Object Name", "Description", ...
+    PropertyNames = ["Object Identifier", "Object Name",
+                     "Description", "Engineering Units"]  # 'Unit ID'
+    ColumnAlignments = [
+        wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
+    ColumnSizes = [40, 80, 80, 200]
+    PropertyConfig = {
+        "Object Identifier": {"GridCellEditor": wx.grid.GridCellNumberEditor,
+                              "GridCellRenderer": wx.grid.GridCellNumberRenderer,
+                              "GridCellEditorParam": "0,4194302"},
+        "Object Name": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer},
+        "Description": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer},
+        "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])}
+    }
+
+    # obj_properties should be a dictionary, with keys "Object Identifier",
+    # "Object Name", "Description", ...
     def UpdateVirtualProperties(self, obj_properties):
-        obj_properties["Unit ID"] = [x[1] for x in BACnetEngineeringUnits if x[0] == obj_properties["Engineering Units"]][0]
-
-         
+        obj_properties["Unit ID"] = [x[1]
+                                     for x in BACnetEngineeringUnits if x[0] == obj_properties["Engineering Units"]][0]
+
 
 class MultiSObject(ObjectProperties):
-    # 'PropertyNames' will be used as the header for each column of the Object Properties grid! 
-    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name" 
-    #          Be sure to use these exact names for these BACnet object properties!
-    PropertyNames    = ["Object Identifier", "Object Name", "Description", "Number of States"]
-    ColumnAlignments = [   wx.ALIGN_RIGHT  , wx.ALIGN_LEFT, wx.ALIGN_LEFT,   wx.ALIGN_CENTER ]
-    ColumnSizes      = [        40         ,      80      ,      80      ,         120       ]
-    PropertyConfig   = {"Object Identifier": {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
-                                              "GridCellEditorParam": "0,4194302"
-                                                                     # syntax for GridCellNumberEditor -> "min,max" 
-                                                                     # ObjectID (22 bits ID + 10 bits type) => max = 2^22-1 
-                                             },
-                        "Object Name"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
-                        "Description"      : {"GridCellEditor"     : wx.grid.GridCellTextEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellStringRenderer},  
-                        "Number of States" : {"GridCellEditor"     : wx.grid.GridCellNumberEditor,
-                                              "GridCellRenderer"   : wx.grid.GridCellNumberRenderer,
-                                              "GridCellEditorParam": "1,255" # syntax for GridCellNumberEditor -> "min,max" 
-                                                                             # MultiState Values are encoded in unsigned integer
-                                                                             # (in BACnet => uint8_t), and can not be 0.
-                                                                             # See ASHRAE 135-2016, section 12.20.4
-                                             }
-                       }
-
+    # 'PropertyNames' will be used as the header for each column of the Object Properties grid!
+    # Warning: The rest of the code depends on the existance of an "Object Identifier" and "Object Name"
+    # Be sure to use these exact names for these BACnet object properties!
+    PropertyNames = [
+        "Object Identifier", "Object Name", "Description", "Number of States"]
+    ColumnAlignments = [
+        wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_CENTER]
+    ColumnSizes = [40, 80, 80, 120]
+    PropertyConfig = {
+        "Object Identifier": {"GridCellEditor": wx.grid.GridCellNumberEditor,
+                              "GridCellRenderer": wx.grid.GridCellNumberRenderer,
+                              "GridCellEditorParam": "0,4194302"},
+        "Object Name": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer},
+        "Description": {"GridCellEditor": wx.grid.GridCellTextEditor,
+                        "GridCellRenderer": wx.grid.GridCellStringRenderer},
+        # MultiState Values are encoded in unsigned integer
+        # (in BACnet => uint8_t), and can not be 0.
+        # See ASHRAE 135-2016, section 12.20.4
+        "Number of States": {"GridCellEditor": wx.grid.GridCellNumberEditor,
+                             "GridCellRenderer": wx.grid.GridCellNumberRenderer,
+                             # syntax for GridCellNumberEditor -> "min,max"
+                             "GridCellEditorParam": "1,255"}
+    }
 
 
 # The default values to use for each BACnet object type
 #
 # Note that the 'internal plugin parameters' get stored in the data table, but
-# are not visible in the GUI. They are used to generate the 
+# are not visible in the GUI. They are used to generate the
 # EDE files as well as the C code
 class BVObject(BinaryObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Binary Value", 
-                        "Description"      :"",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :5,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Binary Value",
+                     "Description": "",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 5,
+                     "Ctype": "uint8_t",
+                     "Settable": "Y"}
+
 
 class BOObject(BinaryObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Binary Output", 
-                        "Description"      :"",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :4,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Binary Output",
+                     "Description": "",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 4,
+                     "Ctype": "uint8_t",
+                     "Settable": "Y"}
+
 
 class BIObject(BinaryObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Binary Input", 
-                        "Description"      :"",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :3,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"N"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Binary Input",
+                     "Description": "",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 3,
+                     "Ctype": "uint8_t",
+                     "Settable": "N"}
+
 
 class AVObject(AnalogObject):
-    DefaultValues    = {"Object Identifier":"", 
-                        "Object Name"      :"Analog Value",
-                        "Description"      :"", 
-                        "Engineering Units":'(Other) no-units (95)',
-                        # internal plugin parameters...
-                        "Unit ID"          :95,   # the ID of the engineering unit
-                                                  # will get updated by UpdateVirtualProperties()
-                        "BACnetObjTypeID"  :2,
-                        "Ctype"            :"float",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Analog Value",
+                     "Description": "",
+                     "Engineering Units": '(Other) no-units (95)',
+                     # internal plugin parameters...
+                     "Unit ID": 95,   # the ID of the engineering unit
+                     # will get updated by
+                     # UpdateVirtualProperties()
+                     "BACnetObjTypeID": 2,
+                     "Ctype": "float",
+                     "Settable": "Y"}
+
 
 class AOObject(AnalogObject):
-    DefaultValues    = {"Object Identifier":"", 
-                        "Object Name"      :"Analog Output",
-                        "Description"      :"", 
-                        "Engineering Units":'(Other) no-units (95)',
-                        # internal plugin parameters...
-                        "Unit ID"          :95,   # the ID of the engineering unit
-                                                  # will get updated by UpdateVirtualProperties()
-                        "BACnetObjTypeID"  :1,
-                        "Ctype"            :"float",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Analog Output",
+                     "Description": "",
+                     "Engineering Units": '(Other) no-units (95)',
+                     # internal plugin parameters...
+                     "Unit ID": 95,   # the ID of the engineering unit
+                     # will get updated by
+                     # UpdateVirtualProperties()
+                     "BACnetObjTypeID": 1,
+                     "Ctype": "float",
+                     "Settable": "Y"}
+
 
 class AIObject(AnalogObject):
-    DefaultValues    = {"Object Identifier":"", 
-                        "Object Name"      :"Analog Input",
-                        "Description"      :"", 
-                        "Engineering Units":'(Other) no-units (95)',
-                        # internal plugin parameters...
-                        "Unit ID"          :95,   # the ID of the engineering unit
-                                                  # will get updated by UpdateVirtualProperties()
-                        "BACnetObjTypeID"  :0,
-                        "Ctype"            :"float",
-                        "Settable"         :"N"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Analog Input",
+                     "Description": "",
+                     "Engineering Units": '(Other) no-units (95)',
+                     # internal plugin parameters...
+                     "Unit ID": 95,   # the ID of the engineering unit
+                     # will get updated by
+                     # UpdateVirtualProperties()
+                     "BACnetObjTypeID": 0,
+                     "Ctype": "float",
+                     "Settable": "N"}
+
 
 class MSVObject(MultiSObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Multi-state Value",
-                        "Description"      :"",
-                        "Number of States" :"255",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :19,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Multi-state Value",
+                     "Description": "",
+                     "Number of States": "255",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 19,
+                     "Ctype": "uint8_t",
+                     "Settable": "Y"}
+
 
 class MSOObject(MultiSObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Multi-state Output",
-                        "Description"      :"",
-                        "Number of States" :"255",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :14,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"Y"
-                        }
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Multi-state Output",
+                     "Description": "",
+                     "Number of States": "255",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 14,
+                     "Ctype": "uint8_t",
+                     "Settable": "Y"}
+
 
 class MSIObject(MultiSObject):
-    DefaultValues    = {"Object Identifier":"",
-                        "Object Name"      :"Multi-state Input",
-                        "Description"      :"",
-                        "Number of States" :"255",
-                        # internal plugin parameters...
-                        "BACnetObjTypeID"  :13,
-                        "Ctype"            :"uint8_t",
-                        "Settable"         :"N"
-                        }
-
-
-
-
-
-
-
-
+    DefaultValues = {"Object Identifier": "",
+                     "Object Name": "Multi-state Input",
+                     "Description": "",
+                     "Number of States": "255",
+                     # internal plugin parameters...
+                     "BACnetObjTypeID": 13,
+                     "Ctype": "uint8_t",
+                     "Settable": "N"}
 
 
 class ObjectTable(CustomTable):
     #  A custom wx.grid.PyGridTableBase using user supplied data
-    #  
-    #  This will basically store a list of BACnet objects that the slave will support/implement. 
-    #  There will be one instance of this ObjectTable class for each BACnet object type 
+    #
+    #  This will basically store a list of BACnet objects that the slave will support/implement.
+    #  There will be one instance of this ObjectTable class for each BACnet object type
     #  (e.g. Binary Value, Analog Input, Multi State Output, ...)
-    #  
+    #
     #  The list of BACnet objects will actually be stored within the self.data variable
     #  (declared in CustomTable). Self.data will be a list of dictionaries (one entry per BACnet
     #  object). All of these dictionaries in the self.data list will have entries whose keys actually
-    #  depend on the BACnet type object being handled. The keys in the dictionary will be 
+    #  depend on the BACnet type object being handled. The keys in the dictionary will be
     #  the entries in the PropertyNames list of one of the following classes:
     #  (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
     #
-    #  For example, when handling Binary Value BACnet objects, 
+    #  For example, when handling Binary Value BACnet objects,
     #   self.data will be a list of dictionaries (one entry per row)
     #     self.data[n] will be a dictionary, with keys "Object Identifier", "Object Name", "Description"
-    #     for example: self.data[n] = {"Object Identifier":33, "Object Name":"room1", "Description":"xx"} 
-    #  
+    #     for example: self.data[n] = {"Object Identifier":33, "Object Name":"room1", "Description":"xx"}
+    #
     #  Note that this ObjectTable class merely stores the configuration data.
-    #  It does not control the display nor the editing of this data. 
+    #  It does not control the display nor the editing of this data.
     #  This data is typically displayed within a grid, that is controlled by the ObjectGrid class.
     #
+
     def __init__(self, parent, data, BACnetObjectType):
         #   parent          : the _BacnetSlavePlug object that is instantiating this ObjectTable
         #   data            : a list with the data to be shown on the grid
@@ -560,35 +552,37 @@
         #                     (in particular, the UpdateVirtualProperties() method)
         #
         # The base class must be initialized *first*
-        CustomTable.__init__(self, parent, data, BACnetObjectType.PropertyNames)
+        CustomTable.__init__(
+            self, parent, data, BACnetObjectType.PropertyNames)
         self.BACnetObjectType = BACnetObjectType()
-        self.ChangesToSave    = False
-
-    #def _GetRowEdit(self, row):
-        #row_edit = self.GetValueByName(row, "Edit")
-        #var_type = self.Parent.GetTagName()
-        #bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type)
-        #if bodytype in ["ST", "IL"]:
-            #row_edit = True;
-        #return row_edit
+        self.ChangesToSave = False
+
+    # def _GetRowEdit(self, row):
+        # row_edit = self.GetValueByName(row, "Edit")
+        # var_type = self.Parent.GetTagName()
+        # bodytype = self.Parent.Controler.GetEditedElementBodyType(var_type)
+        # if bodytype in ["ST", "IL"]:
+        #     row_edit = True;
+        # return row_edit
 
     def _updateColAttrs(self, grid):
         #  wx.grid.Grid -> update the column attributes to add the
         #  appropriate renderer given the column name.
-        #  
+        #
         #  Otherwise default to the default renderer.
-        #print "ObjectTable._updateColAttrs() called!!!"
+        # print "ObjectTable._updateColAttrs() called!!!"
         for row in range(self.GetNumberRows()):
             for col in range(self.GetNumberCols()):
-                PropertyName   = self.BACnetObjectType.PropertyNames[col]
+                PropertyName = self.BACnetObjectType.PropertyNames[col]
                 PropertyConfig = self.BACnetObjectType.PropertyConfig[PropertyName]
-                grid.SetReadOnly            (row, col, False)
-                grid.SetCellEditor          (row, col, PropertyConfig["GridCellEditor"]  ())
-                grid.SetCellRenderer        (row, col, PropertyConfig["GridCellRenderer"]())
+                grid.SetReadOnly(row, col, False)
+                grid.SetCellEditor(row, col, PropertyConfig["GridCellEditor"]())
+                grid.SetCellRenderer(row, col, PropertyConfig["GridCellRenderer"]())
                 grid.SetCellBackgroundColour(row, col, wx.WHITE)
-                grid.SetCellTextColour      (row, col, wx.BLACK)
+                grid.SetCellTextColour(row, col, wx.BLACK)
                 if "GridCellEditorParam" in PropertyConfig:
-                    grid.GetCellEditor(row, col).SetParameters(PropertyConfig["GridCellEditorParam"])
+                    grid.GetCellEditor(row, col).SetParameters(
+                        PropertyConfig["GridCellEditorParam"])
             self.ResizeRow(grid, row)
 
     def FindValueByName(self, PropertyName, PropertyValue):
@@ -600,8 +594,8 @@
             if int(self.GetValueByName(row, PropertyName)) == PropertyValue:
                 return row
         return None
-      
-    # Return a list containing all the values under the column named 'colname'  
+
+    # Return a list containing all the values under the column named 'colname'
     def GetAllValuesByName(self, colname):
         values = []
         for row in range(self.GetNumberRows()):
@@ -610,13 +604,15 @@
 
     # Returns a dictionary with:
     #      keys: IDs    of BACnet objects
-    #     value: number of BACnet objects using this same Id 
+    #     value: number of BACnet objects using this same Id
     #            (values larger than 1 indicates an error as BACnet requires unique
     #             object IDs for objects of the same type)
     def GetObjectIDCount(self):
-        # The dictionary is built by first creating a list containing the IDs of all BACnet objects
+        # The dictionary is built by first creating a list containing the IDs
+        # of all BACnet objects
         ObjectIDs = self.GetAllValuesByName("Object Identifier")
-        ObjectIDs_as_int = [int(x) for x in ObjectIDs] # list of integers instead of strings...
+        # list of integers instead of strings...
+        ObjectIDs_as_int = [int(x) for x in ObjectIDs]
         # This list is then transformed into a collections.Counter class
         # Which is then transformed into a dictionary using dict()
         return dict(Counter(ObjectIDs_as_int))
@@ -639,11 +635,10 @@
                 self.BACnetObjectType.UpdateVirtualProperties(ObjProp)
 
 
-
 class ObjectGrid(CustomGrid):
     # A custom wx.grid.Grid (CustomGrid derives from wx.grid.Grid)
-    # 
-    # Needed mostly to customise the initial values of newly added rows, and to 
+    #
+    # Needed mostly to customise the initial values of newly added rows, and to
     # validate if the inserted data follows BACnet rules.
     #
     #
@@ -656,82 +651,87 @@
     #   The grid uses one line/row per BACnet object, and one column for each property of the BACnet
     #   object. The column titles change depending on the specific type of BACnet object being edited
     #   (BVObject, BOObject, BIObject, AVObject, AOObject, AIObject, MSVObject, MSOObject, MSIObject).
-    #   The editor to use for each column is also obtained from that class (e.g. TextEditor, 
+    #   The editor to use for each column is also obtained from that class (e.g. TextEditor,
     #   NumberEditor, ...)
     #
     #   This class does NOT store the data in the grid. It merely controls its display and editing.
     #   The data in the grid is stored within an object of class ObjectTable
     #
+
     def __init__(self, *args, **kwargs):
         CustomGrid.__init__(self, *args, **kwargs)
 
     # Called when a new row is added by clicking Add button
-    #    call graph: CustomGrid.OnAddButton() --> CustomGrid.AddRow() --> ObjectGrid._AddRow()
+    # call graph: CustomGrid.OnAddButton() --> CustomGrid.AddRow() -->
+    # ObjectGrid._AddRow()
     def _AddRow(self, new_row):
         if new_row > 0:
             self.Table.InsertRow(new_row, self.Table.GetRow(new_row - 1).copy())
         else:
             self.Table.InsertRow(new_row, self.DefaultValue.copy())
-        self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL) # start off with invalid object ID
+        # start off with invalid object ID
+        self.Table.SetValueByName(new_row, "Object Identifier", BACnetObjectID_NUL)
         # Find an apropriate BACnet object ID for the new object.
         # We choose a first attempt (based on object ID of previous line + 1)
         new_object_id = 0
         if new_row > 0:
-            new_object_id = int(self.Table.GetValueByName(new_row - 1, "Object Identifier"))
+            new_object_id = int(
+                self.Table.GetValueByName(new_row - 1, "Object Identifier"))
             new_object_id += 1
-        # Check whether the chosen object ID is not already in use. 
+        # Check whether the chosen object ID is not already in use.
         # If in use, add 1 to the attempted object ID and recheck...
         while self.Table.FindValueByName("Object Identifier", new_object_id) is not None:
             new_object_id += 1
-            # if reached end of object IDs, cycle back to 0 
+            # if reached end of object IDs, cycle back to 0
             # (remember, we may have started at any inital object ID > 0, so it makes sense to cyclce back to 0)
-            # warning: We risk entering an inifinite loop if all object IDs are already used. 
+            # warning: We risk entering an inifinite loop if all object IDs are already used.
             #          The likelyhood of this happening is extremely low, (we would need 2^22 elements in the table!)
             #          so don't worry about it for now.
             if new_object_id > BACnetObjectID_MAX:
                 new_object_id = 0
         # Set the object ID of the new object to the apropriate value
         # ... and append the ID to the default object name (so the name becomes unique)
-        new_object_name = self.DefaultValue.get("Object Name") + " " + str(new_object_id)
-        self.Table.SetValueByName(new_row, "Object Name"      , new_object_name)        
+        new_object_name = self.DefaultValue.get(
+            "Object Name") + " " + str(new_object_id)
+        self.Table.SetValueByName(
+            new_row, "Object Name", new_object_name)
         self.Table.SetValueByName(new_row, "Object Identifier", new_object_id)
         self.Table.ResetView(self)
         return new_row
 
-
     # Called when a object ID is changed
     #    call graph: ObjectEditor.OnVariablesGridCellChange() --> this method
     # Will check whether there is a duplicate object ID, and highlight it if so.
     def HighlightDuplicateObjectIDs(self):
         if self.Table.GetNumberRows() < 2:
-            # Less than 2 rows. No duplicates are possible! 
+            # Less than 2 rows. No duplicates are possible!
             return
         IDsCount = self.Table.GetObjectIDCount()
         # check ALL object IDs for duplicates...
         for row in range(self.Table.GetNumberRows()):
             obj_id1 = int(self.Table.GetValueByName(row, "Object Identifier"))
             if IDsCount[obj_id1] > 1:
-                # More than 1 BACnet object using this ID! Let us Highlight this row with errors...            
+                # More than 1 BACnet object using this ID! Let us Highlight this row with errors...
                 # TODO: change the hardcoded column number '0' to a number obtained at runtime
                 #       that is guaranteed to match the column titled "Object Identifier"
                 self.SetCellBackgroundColour(row, 0, ERROR_HIGHLIGHT[0])
-                self.SetCellTextColour      (row, 0, ERROR_HIGHLIGHT[1])
-            else: 
+                self.SetCellTextColour(row, 0, ERROR_HIGHLIGHT[1])
+            else:
                 self.SetCellBackgroundColour(row, 0, wx.WHITE)
-                self.SetCellTextColour      (row, 0, wx.BLACK)
-        # Refresh the graphical display to take into account any changes we may have made
+                self.SetCellTextColour(row, 0, wx.BLACK)
+        # Refresh the graphical display to take into account any changes we may
+        # have made
         self.ForceRefresh()
-        return None        
-
+        return None
 
     # Called when the user changes the name of BACnet object (using the GUI grid)
-    #    call graph: ObjectEditor.OnVariablesGridCellChange() --> 
+    #    call graph: ObjectEditor.OnVariablesGridCellChange() -->
     #                --> BacnetSlaveEditorPlug.HighlightAllDuplicateObjectNames() -->
     #                --> ObjectEditor.HighlightDuplicateObjectNames() -->
     #                -->   (this method)
     # Will check whether there is a duplicate BACnet object name, and highlight it if so.
     #
-    # Since the names of BACnet objects must be unique within the whole bacnet server (and 
+    # Since the names of BACnet objects must be unique within the whole bacnet server (and
     # not just among the BACnet objects of the same class (e.g. Analog Value, Binary Input, ...)
     # to work properly this method must be passed a list of the names of all BACnet objects
     # currently configured.
@@ -743,18 +743,17 @@
             # TODO: change the hardcoded column number '1' to a number obtained at runtime
             #       that is guaranteed to match the column titled "Object Name"
             if AllObjectNamesFreq[self.Table.GetValueByName(row, "Object Name")] > 1:
-                # This is an error! Highlight it...            
+                # This is an error! Highlight it...
                 self.SetCellBackgroundColour(row, 1, ERROR_HIGHLIGHT[0])
-                self.SetCellTextColour      (row, 1, ERROR_HIGHLIGHT[1])
-            else: 
+                self.SetCellTextColour(row, 1, ERROR_HIGHLIGHT[1])
+            else:
                 self.SetCellBackgroundColour(row, 1, wx.WHITE)
-                self.SetCellTextColour      (row, 1, wx.BLACK)
-        # Refresh the graphical display to take into account any changes we may have made
+                self.SetCellTextColour(row, 1, wx.BLACK)
+        # Refresh the graphical display to take into account any changes we may
+        # have made
         self.ForceRefresh()
-        return None        
-      
-      
-      
+        return None
+
 
 class ObjectEditor(wx.Panel):
     # This ObjectEditor class:
@@ -763,14 +762,15 @@
     #   This 'window' is currenty displayed within one tab of the bacnet plugin, but this
     #   may change in the future!
     #
-    #   It includes a grid to display all the BACnet objects of its type , as well as the buttons 
+    #   It includes a grid to display all the BACnet objects of its type , as well as the buttons
     #   to insert, delete and move (up/down) a BACnet object in the grid.
     #   It also includes the sizers and spacers required to lay out the grid and buttons
     #   in the wndow.
     #
+
     def __init__(self, parent, window, controller, ObjTable):
         # window:  the window in which the editor will open.
-        # controller: The ConfigTreeNode object that controlls the data presented by 
+        # controller: The ConfigTreeNode object that controlls the data presented by
         #             this 'config tree node editor'
         #
         # parent:  wx._controls.Notebook
@@ -778,57 +778,62 @@
         # controller: controller will be an object of class
         #            FinalCTNClass         (i.e. beremiz.ConfigTreeNode.FinalCTNClass )
         #            (FinalCTNClass inherits from: ConfigTreeNode and _BacnetSlavePlug)
-        #            (For the BACnet plugin, it is easier to think of controller as a _BacnetSlavePlug, 
+        #            (For the BACnet plugin, it is easier to think of controller as a _BacnetSlavePlug,
         #             as the other classes are generic to all plugins!!)
         #
         # ObjTable: The object of class ObjectTable that stores the data displayed in the grid.
         #           This object is instantiated and managed by the _BacnetSlavePlug class.
         #
-        self.window   = window
-        self.controller  = controller
+        self.window = window
+        self.controller = controller
         self.ObjTable = ObjTable
-      
+
         wx.Panel.__init__(self, parent)
-        
+
         # The main sizer, 2 rows: top row for buttons, bottom row for 2D grid
         self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
         self.MainSizer.AddGrowableCol(0)
         self.MainSizer.AddGrowableRow(1)
 
-        # sizer placed on top row of main sizer: 
+        # sizer placed on top row of main sizer:
         #   1 row; 6 columns: 1 static text, one stretchable spacer, 4 buttons
         controls_sizer = wx.FlexGridSizer(cols=6, hgap=4, rows=1, vgap=5)
         controls_sizer.AddGrowableCol(0)
         controls_sizer.AddGrowableRow(0)
-        self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW|wx.ALL)
+        self.MainSizer.Add(controls_sizer, border=5, flag=wx.GROW | wx.ALL)
 
         # the buttons that populate the controls sizer (itself in top row of the main sizer)
         # NOTE: the _("string") function will translate the "string" to the local language
-        controls_sizer.Add(wx.StaticText(self, label=_('Object Properties:')), flag=wx.ALIGN_BOTTOM)         
+        controls_sizer.Add(
+            wx.StaticText(self, label=_('Object Properties:')), flag=wx.ALIGN_BOTTOM)
         controls_sizer.AddStretchSpacer()
         for name, bitmap, help in [
-                ("AddButton"   , "add_element"   , _("Add variable"      )),
-                ("DeleteButton", "remove_element", _("Remove variable"   )),
-                ("UpButton"    , "up"            , _("Move variable up"  )),
-                ("DownButton"  , "down"          , _("Move variable down"))]:
-            button = wx.lib.buttons.GenBitmapButton(self, bitmap=GetBitmap(bitmap),
-                                                    size=wx.Size(28, 28), 
-                                                    style=wx.NO_BORDER)
+                ("AddButton", "add_element", _("Add variable")),
+                ("DeleteButton", "remove_element", _("Remove variable")),
+                ("UpButton", "up", _("Move variable up")),
+                ("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)
             setattr(self, name, button)
-            controls_sizer.Add(button)         
-
-        # the variable grid that will populate the bottom row of the main sizer 
+            controls_sizer.Add(button)
+
+        # the variable grid that will populate the bottom row of the main sizer
         panel = self
         self.VariablesGrid = ObjectGrid(panel, style=wx.VSCROLL)
-        #self.VariablesGrid.SetDropTarget(VariableDropTarget(self)) # use only to enable drag'n'drop
-        self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE,     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)
+        # use only to enable drag'n'drop
+        # self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
+        self.VariablesGrid.Bind(
+            wx.grid.EVT_GRID_CELL_CHANGE,     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)
- 
+
         # Configure the Variables Grid...
-        self.VariablesGrid.SetRowLabelSize(0)  # do not include a leftmost column containing the 'row label'
+        # do not include a leftmost column containing the 'row label'
+        self.VariablesGrid.SetRowLabelSize(0)
         self.VariablesGrid.SetButtons({"Add":    self.AddButton,
                                        "Delete": self.DeleteButton,
                                        "Up":     self.UpButton,
@@ -837,10 +842,11 @@
         # NOTE: ObjTable.BACnetObjectType will contain the class name of one of the following classes
         # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
         # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
-        self.VariablesGrid.SetDefaultValue(self.ObjTable.BACnetObjectType.DefaultValues)
+        self.VariablesGrid.SetDefaultValue(
+            self.ObjTable.BACnetObjectType.DefaultValues)
 
         # self.ObjTable: The table that contains the data displayed in the grid
-        #  This table was instantiated/created in the initializer for class _BacnetSlavePlug
+        # This table was instantiated/created in the initializer for class _BacnetSlavePlug
         self.VariablesGrid.SetTable(self.ObjTable)
         self.VariablesGrid.SetEditable(True)
         # set the column attributes (width, alignment)
@@ -848,19 +854,19 @@
         # (BVObject, BIObject, BOObject, AVObject, AIObject, AOObject, MSVObject, MSIObject, MSOObject)
         # which inherit from one of (BinaryObject, AnalogObject, MultiSObject)
         ColumnAlignments = self.ObjTable.BACnetObjectType.ColumnAlignments
-        ColumnSizes      = self.ObjTable.BACnetObjectType.ColumnSizes
-        for col in range(  self.ObjTable.GetNumberCols()):
+        ColumnSizes = self.ObjTable.BACnetObjectType.ColumnSizes
+        for col in range(self.ObjTable.GetNumberCols()):
             attr = wx.grid.GridCellAttr()
             attr.SetAlignment(ColumnAlignments[col], wx.ALIGN_CENTRE)
             self.VariablesGrid.SetColAttr(col, attr)
             self.VariablesGrid.SetColMinimalWidth(col, ColumnSizes[col])
             self.VariablesGrid.AutoSizeColumn(col, False)
- 
+
         # layout the items in all sizers, and show them too.
-        self.SetSizer(self.MainSizer)    # Have the wondow 'own' the sizer...   
-        #self.MainSizer.ShowItems(True)  # not needed once the window 'owns' the sizer (SetSizer())
-        #self.MainSizer.Layout()         # not needed once the window 'owns' the sizer (SetSizer())
-        
+        self.SetSizer(self.MainSizer)    # Have the wondow 'own' the sizer...
+        # self.MainSizer.ShowItems(True)  # not needed once the window 'owns' the sizer (SetSizer())
+        # self.MainSizer.Layout()         # not needed once the window 'owns' the sizer (SetSizer())
+
         # Refresh the view of the grid...
         #   We ask the table to do that, who in turn will configure the grid for us!!
         #   It will configure the CellRenderers and CellEditors taking into account the type of
@@ -872,62 +878,57 @@
         #   (in order to maintain GUI consistency), so we have to adopt their way of doing things.
         #
         # NOTE: ObjectTable.ResetView() (remember, ObjTable is of class ObjectTable)
-        #         calls ObjectTable._updateColAttrs(), who will do the configuring. 
+        #         calls ObjectTable._updateColAttrs(), who will do the configuring.
         self.ObjTable.ResetView(self.VariablesGrid)
 
-
     def RefreshView(self):
-        #print "ObjectEditor.RefreshView() called!!!"
+        # print "ObjectEditor.RefreshView() called!!!"
         # Check for Duplicate Object IDs is only done within same BACnet object type (ID is unique by type).
         # The VariablesGrid class can handle it by itself.
         self.VariablesGrid.HighlightDuplicateObjectIDs()
         # Check for Duplicate Object Names must be done globally (Object Name is unique within bacnet server)
         # Only the BacnetSlaveEditorPlug can and will handle this.
-        #self.window.HighlightAllDuplicateObjectNames()
-        pass      
-
-    #########################################
+        # self.window.HighlightAllDuplicateObjectNames()
+
+    #
     # Event handlers for the Variables Grid #
-    #########################################
+    #
     def OnVariablesGridCellChange(self, event):
-        row, col = event.GetRow(), event.GetCol()
-        #print "ObjectEditor.OnVariablesGridCellChange(row=%s, col=%s) called!!!" % (row, col)
+        col = event.GetCol()
+        # print "ObjectEditor.OnVariablesGridCellChange(row=%s, col=%s)
+        # called!!!" % (row, col)
         self.ObjTable.ChangesToSave = True
         if self.ObjTable.GetColLabelValue(col) == "Object Identifier":
-            # an Object ID was changed => must check duplicate object IDs. 
+            # an Object ID was changed => must check duplicate object IDs.
             self.VariablesGrid.HighlightDuplicateObjectIDs()
         if self.ObjTable.GetColLabelValue(col) == "Object Name":
-            # an Object Name was changed => must check duplicate object names. 
+            # an Object Name was changed => must check duplicate object names.
             # Note that this must be done to _all_ BACnet objects, and not just the objects
             # of the same BACnet class (Binary Value, Analog Input, ...)
             # So we have the BacnetSlaveEditorPlug class do it...
             self.window.HighlightAllDuplicateObjectNames()
-        # There are changes to save => 
-        #        udate the enabled/disabled state of the 'save' option in the 'file' menu
+        # There are changes to save =>
+        # udate the enabled/disabled state of the 'save' option in the 'file' menu
         self.window.RefreshBeremizWindow()
         event.Skip()
 
     def OnVariablesGridCellLeftClick(self, event):
-        row = event.GetRow()
+        pass
 
     def OnVariablesGridEditorShown(self, event):
-        row, col = event.GetRow(), event.GetCol()
+        pass
 
     def HighlightDuplicateObjectNames(self, AllObjectNamesFreq):
         return self.VariablesGrid.HighlightDuplicateObjectNames(AllObjectNamesFreq)
 
 
-
-
-
-
 class BacnetSlaveEditorPlug(ConfTreeNodeEditor):
     # inheritance tree
     #  wx.SplitterWindow-->EditorPanel-->ConfTreeNodeEditor-->BacnetSlaveEditorPlug
     #
     # self.Controller -> The object that controls the data displayed in this editor
     #                    In our case, the object of class _BacnetSlavePlug
-    
+
     CONFNODEEDITOR_TABS = [
         (_("Analog Value Objects"),       "_create_AV_ObjectEditor"),
         (_("Analog Output Objects"),      "_create_AO_ObjectEditor"),
@@ -938,53 +939,62 @@
         (_("Multi-State Value Objects"),  "_create_MSV_ObjectEditor"),
         (_("Multi-State Output Objects"), "_create_MSO_ObjectEditor"),
         (_("Multi-State Input Objects"), "_create_MSI_ObjectEditor")]
-    
+
     def _create_AV_ObjectEditor(self, parent):
-        self.AV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AV_Obj"])
+        self.AV_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["AV_Obj"])
         return self.AV_ObjectEditor
-        
+
     def _create_AO_ObjectEditor(self, parent):
-        self.AO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AO_Obj"])
+        self.AO_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["AO_Obj"])
         return self.AO_ObjectEditor
-        
+
     def _create_AI_ObjectEditor(self, parent):
-        self.AI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["AI_Obj"])
+        self.AI_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["AI_Obj"])
         return self.AI_ObjectEditor
-        
+
     def _create_BV_ObjectEditor(self, parent):
-        self.BV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BV_Obj"])
+        self.BV_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["BV_Obj"])
         return self.BV_ObjectEditor
-        
+
     def _create_BO_ObjectEditor(self, parent):
-        self.BO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BO_Obj"])
+        self.BO_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["BO_Obj"])
         return self.BO_ObjectEditor
-        
+
     def _create_BI_ObjectEditor(self, parent):
-        self.BI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["BI_Obj"])
+        self.BI_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["BI_Obj"])
         return self.BI_ObjectEditor
-        
+
     def _create_MSV_ObjectEditor(self, parent):
-        self.MSV_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSV_Obj"])
+        self.MSV_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["MSV_Obj"])
         return self.MSV_ObjectEditor
-        
+
     def _create_MSO_ObjectEditor(self, parent):
-        self.MSO_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSO_Obj"])
+        self.MSO_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["MSO_Obj"])
         return self.MSO_ObjectEditor
-        
+
     def _create_MSI_ObjectEditor(self, parent):
-        self.MSI_ObjectEditor = ObjectEditor(parent, self, self.Controler, self.Controler.ObjTables["MSI_Obj"])
+        self.MSI_ObjectEditor = ObjectEditor(
+            parent, self, self.Controler, self.Controler.ObjTables["MSI_Obj"])
         return self.MSI_ObjectEditor
-        
+
     def __init__(self, parent, controler, window, editable=True):
         self.Editable = editable
         ConfTreeNodeEditor.__init__(self, parent, controler, window)
-    
+
     def __del__(self):
         self.Controler.OnCloseEditor(self)
-    
+
     def GetConfNodeMenuItems(self):
         return []
-    
+
     def RefreshConfNodeMenu(self, confnode_menu):
         return
 
@@ -1013,17 +1023,21 @@
         self.MSO_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
         self.MSI_ObjectEditor.HighlightDuplicateObjectNames(ObjectNamesCount)
         return None
-        
-        
+
     def RefreshBeremizWindow(self):
-        # self.ParentWindow is the top level Beremiz class (object) that 
+        # self.ParentWindow is the top level Beremiz class (object) that
         #                   handles the beremiz window and layout
-        self.ParentWindow.RefreshTitle()        # Refresh the title of the Beremiz window
-                                                #  (it changes depending on whether there are
-                                                #   changes to save!! )
-        self.ParentWindow.RefreshFileMenu()     # Refresh the enabled/disabled state of the
-                                                #  entries in the main 'file' menu. 
-                                                #  ('Save' sub-menu should become enabled
-                                                #   if there are changes to save! )
-        ###self.ParentWindow.RefreshEditMenu()
-        ###self.ParentWindow.RefreshPageTitles()
+
+        # Refresh the title of the Beremiz window
+        #  (it changes depending on whether there are
+        #   changes to save!! )
+        self.ParentWindow.RefreshTitle()
+
+        # Refresh the enabled/disabled state of the
+        #  entries in the main 'file' menu.
+        #  ('Save' sub-menu should become enabled
+        #   if there are changes to save! )
+        self.ParentWindow.RefreshFileMenu()
+
+        # self.ParentWindow.RefreshEditMenu()
+        # self.ParentWindow.RefreshPageTitles()
--- a/bacnet/__init__.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/bacnet/__init__.py	Wed Aug 08 13:46:19 2018 +0200
@@ -18,9 +18,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#    
+#
 # 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 bacnet import *
+from __future__ import absolute_import
+
+from bacnet.bacnet import *
--- a/bacnet/bacnet.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/bacnet/bacnet.py	Wed Aug 08 13:46:19 2018 +0200
@@ -19,51 +19,53 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#    
+#
 # This code is made available on the understanding that it will not be
 # used in safety-critical situations without a full and competent review.
 
-
-
-import os, sys
+from __future__ import absolute_import
+
+import os
 from collections import Counter
-from datetime    import datetime
-
-base_folder           = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
-base_folder           = os.path.join(base_folder, "..")
-BacnetPath            = os.path.join(base_folder, "BACnet")
-BacnetLibraryPath     = os.path.join(BacnetPath, "lib")
-BacnetIncludePath     = os.path.join(BacnetPath, "include")
+from datetime import datetime
+import pickle
+
+import wx
+
+from bacnet.BacnetSlaveEditor import *
+from bacnet.BacnetSlaveEditor import ObjectProperties
+from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
+
+base_folder = os.path.split(
+    os.path.dirname(os.path.realpath(__file__)))[0]
+base_folder = os.path.join(base_folder, "..")
+BacnetPath = os.path.join(base_folder, "BACnet")
+BacnetLibraryPath = os.path.join(BacnetPath, "lib")
+BacnetIncludePath = os.path.join(BacnetPath, "include")
 BacnetIncludePortPath = os.path.join(BacnetPath, "ports")
 BacnetIncludePortPath = os.path.join(BacnetIncludePortPath, "linux")
 
-import wx
-import pickle
-
-from BacnetSlaveEditor import *
-from BacnetSlaveEditor import ObjectProperties
-from ConfigTreeNode    import ConfigTreeNode
-from PLCControler      import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
-
 # Parameters to be monkey patched in beremiz customizations
-BACNET_VENDOR_ID = 9999 
+BACNET_VENDOR_ID = 9999
 BACNET_VENDOR_NAME = "Beremiz.org"
 BACNET_DEVICE_MODEL_NAME = "Beremiz PLC"
 
-###################################################
-###################################################
-#                                                 #
-#           S L A V E    D E V I C E              # 
-#                                                 #
-###################################################
-###################################################
+#
+#
+#
+# S L A V E    D E V I C E              #
+#
+#
+#
 
 # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
 #       The objects are instead instantiated from class FinalCTNClass
 #       FinalCTNClass inherits from: - ConfigTreeNode
 #                                    - The tree node plug (in our case _BacnetSlavePlug)
-#class _BacnetSlavePlug:
-class RootClass:
+# class _BacnetSlavePlug:
+
+
+class RootClass(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       <xsd:element name="BACnetServerNode">
@@ -77,7 +79,7 @@
                 </xsd:restriction>
             </xsd:simpleType>
           </xsd:attribute>
-          <xsd:attribute name="BACnet_Communication_Control_Password"     
+          <xsd:attribute name="BACnet_Communication_Control_Password"
                                                        type="xsd:string"  use="optional" default="Malba Tahan"/>
           <xsd:attribute name="BACnet_Device_ID"                          use="optional" default="0">
             <xsd:simpleType>
@@ -99,8 +101,7 @@
     #       so the Device instance ID is limited from 0 to 22^2-1 = 4194303
     #       However, 4194303 is reserved for special use (similar to NULL pointer), so last
     #       valid ID becomes 4194302
-    
-    
+
     # The class/object that will render the graphical interface to edit the
     #    BacnetSlavePlug's configuration parameters. The object of class BacnetSlaveEditorPlug
     #    will be instantiated by the ConfigTreeNode class.
@@ -117,17 +118,17 @@
     #       of classes ConfigTreeNode as well as FinalCTNClass (since they are always instantiated
     #       as a FinalCTNClass)
     EditorType = BacnetSlaveEditorPlug
-    
+
     # The following classes follow the model/viewer design pattern
     #
     # _BacnetSlavePlug       - contains the model (i.e. configuration parameters)
-    # BacnetSlaveEditorPlug  - contains the viewer (and editor, so it includes the 'controller' part of the 
+    # BacnetSlaveEditorPlug  - contains the viewer (and editor, so it includes the 'controller' part of the
     #                                    design pattern which in this case is not separated from the viewer)
     #
-    # The _BacnetSlavePlug      object is 'permanent', i.e. it exists as long as the beremiz project is open 
+    # The _BacnetSlavePlug      object is 'permanent', i.e. it exists as long as the beremiz project is open
     # The BacnetSlaveEditorPlug object is 'transient', i.e. it exists only while the editor is visible/open
     #                                                         in the editing panel. It is destoryed whenever
-    #                                                         the user closes the corresponding tab in the 
+    #                                                         the user closes the corresponding tab in the
     #                                                         editing panel, and a new object is created when
     #                                                         the editor is re-opened.
     #
@@ -136,9 +137,9 @@
     #                              and are therefore stored to a file)
     #
     # _BacnetSlavePlug contains:  AV_VarEditor, ...
-    #                             (these are the objects that implement a grid table to edit/view the 
+    #                             (these are the objects that implement a grid table to edit/view the
     #                              corresponding mode parameters)
-    #  
+    #
     #  Logic:
     #    - The xx_VarEditor classes inherit from wx.grid.Grid
     #    - The xx_ObjTable  classes inherit from wx.grid.PyGridTableBase
@@ -149,102 +150,117 @@
     #  Note that wx.grid.Grid is prepared to work with wx.grid.PyGridTableBase as the container of
     #  data that is displayed and edited in the Grid.
 
-    
     ConfNodeMethods = [
-        {"bitmap"  : "ExportSlave",
-         "name"    : _("Export slave"), 
-         "tooltip" : _("Export BACnet slave to EDE file"),
-         "method"  : "_ExportBacnetSlave"},
+        {"bitmap": "ExportSlave",
+         "name": _("Export slave"),
+         "tooltip": _("Export BACnet slave to EDE file"),
+         "method": "_ExportBacnetSlave"},
     ]
-    
+
     def __init__(self):
-        # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables 
+        # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables
         #   in this BACnet server.
         self.ObjTablesData = {}
-        self.ObjTablesData[ "AV_Obj"] = [] # Each list will contain an entry for each row in the xxxxVar grid!!
-        self.ObjTablesData[ "AO_Obj"] = [] #   Each entry/row will be a dictionary
-        self.ObjTablesData[ "AI_Obj"] = [] #     Each dictionary will contain all entries/data 
-        self.ObjTablesData[ "BV_Obj"] = [] #     for one row in the grid.
-        self.ObjTablesData[ "BO_Obj"] = [] # Same structure as explained above...
-        self.ObjTablesData[ "BI_Obj"] = [] # Same structure as explained above...
-        self.ObjTablesData["MSV_Obj"] = [] # Same structure as explained above...
-        self.ObjTablesData["MSO_Obj"] = [] # Same structure as explained above...
-        self.ObjTablesData["MSI_Obj"] = [] # Same structure as explained above...
-        
-        self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version":1} 
-                                                # EDE files inlcude extra parameters (ex. file version)
-                                                # We would like to save the parameters the user configures
-                                                # so they are available the next time the user opens the project.
-                                                # Since this plugin is only storing the ObjTablesData[] dict
-                                                # to file, we add that info to this dictionary too.
-                                                # Yes, I know this is kind of a hack.
-        
+
+        # Each list will contain an entry for each row in the xxxxVar grid!!
+        #   Each entry/row will be a dictionary
+        #     Each dictionary will contain all entries/data
+        # for one row in the grid.
+
+        self.ObjTablesData["AV_Obj"] = []
+        self.ObjTablesData["AO_Obj"] = []
+        self.ObjTablesData["AI_Obj"] = []
+        self.ObjTablesData["BV_Obj"] = []
+        self.ObjTablesData["BO_Obj"] = []
+        self.ObjTablesData["BI_Obj"] = []
+        self.ObjTablesData["MSV_Obj"] = []
+        self.ObjTablesData["MSO_Obj"] = []
+        self.ObjTablesData["MSI_Obj"] = []
+
+        self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version": 1}
+
+        # EDE files inlcude extra parameters (ex. file version)
+        # We would like to save the parameters the user configures
+        # so they are available the next time the user opens the project.
+        # Since this plugin is only storing the ObjTablesData[] dict
+        # to file, we add that info to this dictionary too.
+        # Yes, I know this is kind of a
+        # hack.
+
         filepath = self.GetFileName()
-        if(os.path.isfile(filepath)):
+        if os.path.isfile(filepath):
             self.LoadFromFile(filepath)
 
         self.ObjTables = {}
-        self.ObjTables[ "AV_Obj"] = ObjectTable(self, self.ObjTablesData[ "AV_Obj"],  AVObject)
-        self.ObjTables[ "AO_Obj"] = ObjectTable(self, self.ObjTablesData[ "AO_Obj"],  AOObject)
-        self.ObjTables[ "AI_Obj"] = ObjectTable(self, self.ObjTablesData[ "AI_Obj"],  AIObject)
-        self.ObjTables[ "BV_Obj"] = ObjectTable(self, self.ObjTablesData[ "BV_Obj"],  BVObject)
-        self.ObjTables[ "BO_Obj"] = ObjectTable(self, self.ObjTablesData[ "BO_Obj"],  BOObject)
-        self.ObjTables[ "BI_Obj"] = ObjectTable(self, self.ObjTablesData[ "BI_Obj"],  BIObject)
-        self.ObjTables["MSV_Obj"] = ObjectTable(self, self.ObjTablesData["MSV_Obj"], MSVObject)
-        self.ObjTables["MSO_Obj"] = ObjectTable(self, self.ObjTablesData["MSO_Obj"], MSOObject)
-        self.ObjTables["MSI_Obj"] = ObjectTable(self, self.ObjTablesData["MSI_Obj"], MSIObject)
-        #   list containing the data in the table <--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
-
-
-    ######################################
+        self.ObjTables["AV_Obj"] = ObjectTable(
+            self, self.ObjTablesData["AV_Obj"],  AVObject)
+        self.ObjTables["AO_Obj"] = ObjectTable(
+            self, self.ObjTablesData["AO_Obj"],  AOObject)
+        self.ObjTables["AI_Obj"] = ObjectTable(
+            self, self.ObjTablesData["AI_Obj"],  AIObject)
+        self.ObjTables["BV_Obj"] = ObjectTable(
+            self, self.ObjTablesData["BV_Obj"],  BVObject)
+        self.ObjTables["BO_Obj"] = ObjectTable(
+            self, self.ObjTablesData["BO_Obj"],  BOObject)
+        self.ObjTables["BI_Obj"] = ObjectTable(
+            self, self.ObjTablesData["BI_Obj"],  BIObject)
+        self.ObjTables["MSV_Obj"] = ObjectTable(
+            self, self.ObjTablesData["MSV_Obj"], MSVObject)
+        self.ObjTables["MSO_Obj"] = ObjectTable(
+            self, self.ObjTablesData["MSO_Obj"], MSOObject)
+        self.ObjTables["MSI_Obj"] = ObjectTable(
+            self, self.ObjTablesData["MSI_Obj"], MSIObject)
+
+    #
     # Functions to be called by CTNClass #
-    ######################################
+    #
     # The following functions would be somewhat equvalent to virtual functions/methods in C++ classes
-    #  They will be called by the base class (CTNClass) from which this _BacnetSlavePlug class derives.
-    
+    # They will be called by the base class (CTNClass) from which this
+    # _BacnetSlavePlug class derives.
+
     def GetCurrentNodeName(self):
         return self.CTNName()
 
     def GetFileName(self):
         return os.path.join(self.CTNPath(), 'bacnet_slave')
-    
+
     def OnCTNSave(self, from_project_path=None):
-       return self.SaveToFile(self.GetFileName())
-
+        return self.SaveToFile(self.GetFileName())
 
     def CTNTestModified(self):
         # self.ChangesToSave: Check whether any of the parameters, defined in the XSD above, were changed.
         #                     This is handled by the ConfigTreeNode class
-        #                     (Remember that no objects are ever instantiated from _BacnetSlavePlug. 
+        #                     (Remember that no objects are ever instantiated from _BacnetSlavePlug.
         #                      Objects are instead created from FinalCTNClass, which derives from
         #                      _BacnetSlavePlug and ConfigTreeNode. This means that we can exceptionally
-        #                      consider that all objects of type _BacnetSlavePlug will also be a ConfigTreeNode).
-        result = self.ChangesToSave or self.ObjTables[ "AV_Obj"].ChangesToSave \
-                                    or self.ObjTables[ "AO_Obj"].ChangesToSave \
-                                    or self.ObjTables[ "AI_Obj"].ChangesToSave \
-                                    or self.ObjTables[ "BV_Obj"].ChangesToSave \
-                                    or self.ObjTables[ "BO_Obj"].ChangesToSave \
-                                    or self.ObjTables[ "BI_Obj"].ChangesToSave \
-                                    or self.ObjTables["MSV_Obj"].ChangesToSave \
-                                    or self.ObjTables["MSO_Obj"].ChangesToSave \
-                                    or self.ObjTables["MSI_Obj"].ChangesToSave
+        # consider that all objects of type _BacnetSlavePlug will also be a
+        # ConfigTreeNode).
+        result = self.ChangesToSave \
+            or self.ObjTables["AV_Obj"].ChangesToSave \
+            or self.ObjTables["AO_Obj"].ChangesToSave \
+            or self.ObjTables["AI_Obj"].ChangesToSave \
+            or self.ObjTables["BV_Obj"].ChangesToSave \
+            or self.ObjTables["BO_Obj"].ChangesToSave \
+            or self.ObjTables["BI_Obj"].ChangesToSave \
+            or self.ObjTables["MSV_Obj"].ChangesToSave \
+            or self.ObjTables["MSO_Obj"].ChangesToSave \
+            or self.ObjTables["MSI_Obj"].ChangesToSave
         return result
 
-    ### Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
-    ##def _OpenView(self, name=None, onlyopened=False):
-        ##print "_BacnetSlavePlug._OpenView() Called!!!"
-        ##ConfigTreeNode._OpenView(self, name, onlyopened)
-        ###print self._View
-        #####if self._View is not None:
-            #####self._View.SetBusId(self.GetCurrentLocation())
-        ##return self._View
-
+    # Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
+    # def _OpenView(self, name=None, onlyopened=False):
+        # print "_BacnetSlavePlug._OpenView() Called!!!"
+        # ConfigTreeNode._OpenView(self, name, onlyopened)
+        # print self._View
+        # if self._View is not None:
+        #     self._View.SetBusId(self.GetCurrentLocation())
+        # return self._View
 
     def GetVariableLocationTree(self):
         current_location = self.GetCurrentLocation()
         # see comment in CTNGenerate_C regarding identical line of code!
-        locstr = ".".join(map(str,current_location))
-        
+        locstr = ".".join(map(str, current_location))
+
         # IDs used by BACnet to identify object types/class.
         #     OBJECT_ANALOG_INPUT       =  0,
         #     OBJECT_ANALOG_OUTPUT      =  1,
@@ -270,78 +286,84 @@
         #   Value objects will be mapped onto %M
         #   Input objects will be mapped onto %I
         #  Output objects will be mapped onto %Q
-                
+
         BACnetEntries = []
         BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "AV_Obj"], 32, 'REAL', 'D', locstr+ '.2', 'Analog Values'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "AO_Obj"], 32, 'REAL', 'D', locstr+ '.1', 'Analog Outputs'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "AI_Obj"], 32, 'REAL', 'D', locstr+ '.0', 'Analog Inputs'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "BV_Obj"],  1, 'BOOL', 'X', locstr+ '.5', 'Binary Values'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "BO_Obj"],  1, 'BOOL', 'X', locstr+ '.4', 'Binary Outputs'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData[ "BI_Obj"],  1, 'BOOL', 'X', locstr+ '.3', 'Binary Inputs'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData["MSV_Obj"],  8, 'BYTE', 'B', locstr+'.19', 'Multi State Values'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData["MSO_Obj"],  8, 'BYTE', 'B', locstr+'.14', 'Multi State Outputs'))
-        BACnetEntries.append(self.GetSlaveLocationTree(
-                      self.ObjTablesData["MSI_Obj"],  8, 'BYTE', 'B', locstr+'.13', 'Multi State Inputs'))
-
-        return  {"name": self.BaseParams.getName(),
-                 "type": LOCATION_CONFNODE,
-                 "location": locstr + ".x",
-                 "children": BACnetEntries}
-
-
-    ############################
+            self.ObjTablesData["AV_Obj"], 32, 'REAL', 'D', locstr + '.2', 'Analog Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["AO_Obj"], 32, 'REAL', 'D', locstr + '.1', 'Analog Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["AI_Obj"], 32, 'REAL', 'D', locstr + '.0', 'Analog Inputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["BV_Obj"],  1, 'BOOL', 'X', locstr + '.5', 'Binary Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["BO_Obj"],  1, 'BOOL', 'X', locstr + '.4', 'Binary Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["BI_Obj"],  1, 'BOOL', 'X', locstr + '.3', 'Binary Inputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["MSV_Obj"],  8, 'BYTE', 'B', locstr + '.19', 'Multi State Values'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["MSO_Obj"],  8, 'BYTE', 'B', locstr + '.14', 'Multi State Outputs'))
+        BACnetEntries.append(self.GetSlaveLocationTree(
+            self.ObjTablesData["MSI_Obj"],  8, 'BYTE', 'B', locstr + '.13', 'Multi State Inputs'))
+
+        return {"name": self.BaseParams.getName(),
+                "type": LOCATION_CONFNODE,
+                "location": locstr + ".x",
+                "children": BACnetEntries}
+
+    #
     # Helper functions/methods #
-    ############################
+    #
     # a helper function to GetVariableLocationTree()
     def GetSlaveLocationTree(self, ObjTablesData, size_in_bits, IECdatatype, location_size, location_str, name):
         BACnetObjectEntries = []
-        for  xx_ObjProp in ObjTablesData:
+        for xx_ObjProp in ObjTablesData:
             BACnetObjectEntries.append({
                 "name": str(xx_ObjProp["Object Identifier"]) + ': ' + xx_ObjProp["Object Name"],
-                "type": LOCATION_VAR_MEMORY, # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
-                "size": size_in_bits, # 1 or 16
-                "IEC_type": IECdatatype, # 'BOOL', 'WORD', ...
-                "var_name": "var_name", # seems to be ignored??
+                "type": LOCATION_VAR_MEMORY,  # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
+                "size": size_in_bits,  # 1 or 16
+                "IEC_type": IECdatatype,  # 'BOOL', 'WORD', ...
+                "var_name": "var_name",  # seems to be ignored??
                 "location": location_size + location_str + "." + str(xx_ObjProp["Object Identifier"]),
-                "description": "description", # seems to be ignored?
+                "description": "description",  # seems to be ignored?
                 "children": []})
-        
-        BACnetEntries = []        
-        return  {"name": name,
-                 "type": LOCATION_CONFNODE,
-                 "location": location_str + ".x",
-                 "children": BACnetObjectEntries}
-    
-    
+
+        return {"name": name,
+                "type": LOCATION_CONFNODE,
+                "location": location_str + ".x",
+                "children": BACnetObjectEntries}
+
     # Returns a dictionary with:
     #      keys: names  of BACnet objects
-    #     value: number of BACnet objects using this same name 
+    #     value: number of BACnet objects using this same name
     #            (values larger than 1 indicates an error as BACnet requires unique names)
     def GetObjectNamesCount(self):
-        # The dictionary is built by first creating a list containing the names of _ALL_ 
+        # The dictionary is built by first creating a list containing the names of _ALL_
         # BACnet objects currently configured by the user (using the GUI)
         ObjectNames = []
-        ObjectNames.extend(self.ObjTables[ "AV_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables[ "AO_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables[ "AI_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables[ "BV_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables[ "BO_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables[ "BI_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
-        ObjectNames.extend(self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["AV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["AO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["AI_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["BV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["BO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["BI_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
+        ObjectNames.extend(
+            self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
         # This list is then transformed into a collections.Counter class
         # Which is then transformed into a dictionary using dict()
         return dict(Counter(ObjectNames))
-      
+
     # Check whether the current configuration contains BACnet objects configured
     # with the same identical object name  (returns True or False)
     def HasDuplicateObjectNames(self):
@@ -354,26 +376,25 @@
     # Check whether any object ID is used more than once (not valid in BACnet)
     # (returns True or False)
     def HasDuplicateObjectIDs(self):
-        res =        self.ObjTables[ "AV_Obj"].HasDuplicateObjectIDs()
-        res = res or self.ObjTables[ "AO_Obj"].HasDuplicateObjectIDs()
-        res = res or self.ObjTables[ "AI_Obj"].HasDuplicateObjectIDs()
-        res = res or self.ObjTables[ "BV_Obj"].HasDuplicateObjectIDs()
-        res = res or self.ObjTables[ "BO_Obj"].HasDuplicateObjectIDs()
-        res = res or self.ObjTables[ "BI_Obj"].HasDuplicateObjectIDs()
+        res = self.ObjTables["AV_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["AO_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["AI_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["BV_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["BO_Obj"].HasDuplicateObjectIDs()
+        res = res or self.ObjTables["BI_Obj"].HasDuplicateObjectIDs()
         res = res or self.ObjTables["MSV_Obj"].HasDuplicateObjectIDs()
         res = res or self.ObjTables["MSO_Obj"].HasDuplicateObjectIDs()
         res = res or self.ObjTables["MSI_Obj"].HasDuplicateObjectIDs()
         return res
 
-    
-    #######################################################
+    #
     # Methods related to files (saving/loading/exporting) #
-    #######################################################
+    #
     def SaveToFile(self, filepath):
         # Save node data in file
         # The configuration data declared in the XSD string will be saved by the ConfigTreeNode class,
         # so we only need to save the data that is stored in ObjTablesData objects
-        # Note that we do not store the ObjTables objects. ObjTables is of a class that 
+        # Note that we do not store the ObjTables objects. ObjTables is of a class that
         # contains more stuff we do not need to store. Actually it is a bad idea to store
         # this extra stuff (as we would make the files we generate dependent on the actual
         # version of the wx library we are using!!! Remember that ObjTables evetually
@@ -383,19 +404,20 @@
             fd = open(filepath,   "w")
             pickle.dump(self.ObjTablesData, fd)
             fd.close()
-            # On successfull save, reset flags to indicate no more changes that need saving
-            self.ObjTables[ "AV_Obj"].ChangesToSave = False
-            self.ObjTables[ "AO_Obj"].ChangesToSave = False
-            self.ObjTables[ "AI_Obj"].ChangesToSave = False
-            self.ObjTables[ "BV_Obj"].ChangesToSave = False
-            self.ObjTables[ "BO_Obj"].ChangesToSave = False
-            self.ObjTables[ "BI_Obj"].ChangesToSave = False
+            # On successfull save, reset flags to indicate no more changes that
+            # need saving
+            self.ObjTables["AV_Obj"].ChangesToSave = False
+            self.ObjTables["AO_Obj"].ChangesToSave = False
+            self.ObjTables["AI_Obj"].ChangesToSave = False
+            self.ObjTables["BV_Obj"].ChangesToSave = False
+            self.ObjTables["BO_Obj"].ChangesToSave = False
+            self.ObjTables["BI_Obj"].ChangesToSave = False
             self.ObjTables["MSV_Obj"].ChangesToSave = False
             self.ObjTables["MSO_Obj"].ChangesToSave = False
             self.ObjTables["MSI_Obj"].ChangesToSave = False
             return True
         except:
-            return _("Unable to save to file \"%s\"!")%filepath
+            return _("Unable to save to file \"%s\"!") % filepath
 
     def LoadFromFile(self, filepath):
         # Load the data that is saved in SaveToFile()
@@ -405,157 +427,181 @@
             fd.close()
             return True
         except:
-            return _("Unable to load file \"%s\"!")%filepath
+            return _("Unable to load file \"%s\"!") % filepath
 
     def _ExportBacnetSlave(self):
-        dialog = wx.FileDialog(self.GetCTRoot().AppFrame, 
-                               _("Choose a file"), 
-                               os.path.expanduser("~"), 
-                               "%s_EDE.csv" % self.CTNName(),  
+        dialog = wx.FileDialog(self.GetCTRoot().AppFrame,
+                               _("Choose a file"),
+                               os.path.expanduser("~"),
+                               "%s_EDE.csv" % self.CTNName(),
                                _("EDE files (*_EDE.csv)|*_EDE.csv|All files|*.*"),
-                               wx.SAVE|wx.OVERWRITE_PROMPT)
+                               wx.SAVE | wx.OVERWRITE_PROMPT)
         if dialog.ShowModal() == wx.ID_OK:
             result = self.GenerateEDEFile(dialog.GetPath())
             result = False
             if result:
-                self.GetCTRoot().logger.write_error(_("Error: Export slave failed\n"))
-        dialog.Destroy()  
-
+                self.GetCTRoot().logger.write_error(
+                    _("Error: Export slave failed\n"))
+        dialog.Destroy()
 
     def GenerateEDEFile(self, filename):
-        template_file_dir     = os.path.join(os.path.split(__file__)[0],"ede_files")
-        
-        #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
-        # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
+        template_file_dir = os.path.join(
+            os.path.split(__file__)[0], "ede_files")
+
+        # The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+        # It will be an XML parser object created by
+        # GenerateParserFromXSDstring(self.XSD).CreateRoot()
         BACnet_Device_ID = self.BACnetServerNode.getBACnet_Device_ID()
-        
+
         # The EDE file contains a header that includes general project data (name, author, ...)
         # Instead of asking the user for this data, we get it from the configuration
         # of the Beremiz project itself.
         # We ask the root Config Tree Node for the data...
         ProjProp = {}
         FileProp = {}
-        CTN_Root      = self.GetCTRoot()   # this should be an object of class ProjectController
-        Project       = CTN_Root.Project   # this should be an object capable of parsing
-                                           # PLCopen XML files. The parser is created automatically
-                                           # (i.e. using GenerateParserFromXSD() from xmlclass module)
-                                           # using the PLCopen XSD file defining the format of the XML.
-                                           # See the file plcopen/plcopen.py
+
+        # this should be an object of class ProjectController
+        CTN_Root = self.GetCTRoot()
+
+        # this should be an object capable of parsing
+        # PLCopen XML files. The parser is created automatically
+        # (i.e. using GenerateParserFromXSD() from xmlclass module)
+        # using the PLCopen XSD file defining the format of the XML.
+        # See the file plcopen/plcopen.py
+        Project = CTN_Root.Project
         if Project is not None:
-          # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
-          # plcopn/plcopen.py    We cannot rely on their existance
-          if getattr(Project, "getcontentHeader", None) is not None:
-            ProjProp = Project.getcontentHeader()
-            # getcontentHeader() returns a dictionary. Available keys are:
-            # "projectName", "projectVersion", "modificationDateTime", 
-            # "organization", "authorName", "language", "pageSize", "scaling"
-          if getattr(Project, "getfileHeader", None) is not None:
-            FileProp = Project.getfileHeader()
-            # getfileHeader() returns a dictionary. Available keys are:
-            # "companyName", "companyURL", "productName", "productVersion",
-            # "productRelease", "creationDateTime", "contentDescription"
-
-        ProjName   = ""
+            # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
+            # plcopn/plcopen.py    We cannot rely on their existance
+            if getattr(Project, "getcontentHeader", None) is not None:
+                ProjProp = Project.getcontentHeader()
+                # getcontentHeader() returns a dictionary. Available keys are:
+                # "projectName", "projectVersion", "modificationDateTime",
+                # "organization", "authorName", "language", "pageSize", "scaling"
+            if getattr(Project, "getfileHeader", None) is not None:
+                FileProp = Project.getfileHeader()
+                # getfileHeader() returns a dictionary. Available keys are:
+                # "companyName", "companyURL", "productName", "productVersion",
+                # "productRelease", "creationDateTime", "contentDescription"
+
+        ProjName = ""
         if "projectName" in ProjProp:
-            ProjName    = ProjProp["projectName"]
+            ProjName = ProjProp["projectName"]
         ProjAuthor = ""
         if "companyName" in FileProp:
             ProjAuthor += "(" + FileProp["companyName"] + ")"
         if "authorName" in ProjProp:
-            ProjAuthor  = ProjProp["authorName"] + " " + ProjAuthor
-            
+            ProjAuthor = ProjProp["authorName"] + " " + ProjAuthor
+
         projdata_dict = {}
-        projdata_dict["Project Name"]     = ProjName
-        projdata_dict["Project Author"]   = ProjAuthor
-        projdata_dict["Current Time"]     = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-        projdata_dict["EDE file version"] = self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"]
+        projdata_dict["Project Name"] = ProjName
+        projdata_dict["Project Author"] = ProjAuthor
+        projdata_dict["Current Time"] = datetime.now().strftime(
+            '%Y-%m-%d %H:%M:%S')
+        projdata_dict["EDE file version"] = self.ObjTablesData[
+            "EDEfile_parm"]["next_EDE_file_version"]
 
         # Next time we generate an EDE file, use another version!
         self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"] += 1
 
-        AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
-
-        BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
-
-        MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
-        
+        AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
+            ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
+
+        BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
+            ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
+
+        MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
+            ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
+
         Objects_List = []
-        for  ObjType,     params_format   in [
-            ("AV" ,  AX_params_format ), ("AO" ,  AX_params_format ), ("AI" ,  AX_params_format ),
-            ("BV" ,  BX_params_format ), ("BO" ,  BX_params_format ), ("BI" ,  BX_params_format ),
-            ("MSV", MSX_params_format ), ("MSO", MSX_params_format ), ("MSI", MSX_params_format )
-            ]:
+        for ObjType, params_format in [("AV",  AX_params_format),
+                                       ("AO",  AX_params_format),
+                                       ("AI",  AX_params_format),
+                                       ("BV",  BX_params_format),
+                                       ("BO",  BX_params_format),
+                                       ("BI",  BX_params_format),
+                                       ("MSV", MSX_params_format),
+                                       ("MSO", MSX_params_format),
+                                       ("MSI", MSX_params_format)]:
             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
                 Objects_List.append(params_format % ObjProp)
-        
+
         # Normalize filename
         for extension in ["_EDE.csv", "_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
             if filename.lower().endswith(extension.lower()):
                 filename = filename[:-len(extension)]
-        
+
         # EDE_header
-        generate_file_name    = filename + "_EDE.csv"
-        template_file_name    = os.path.join(template_file_dir,"template_EDE.csv")
+        generate_file_name = filename + "_EDE.csv"
+        template_file_name = os.path.join(
+            template_file_dir, "template_EDE.csv")
         generate_file_content = open(template_file_name).read() % projdata_dict
-        generate_file_handle  = open(generate_file_name,'w')
+        generate_file_handle = open(generate_file_name, 'w')
         generate_file_handle  .write(generate_file_content)
         generate_file_handle  .write("\n".join(Objects_List))
         generate_file_handle  .close()
-        
-        # templates of remaining files do not need changes. They are simply copied unchanged!
+
+        # templates of remaining files do not need changes. They are simply
+        # copied unchanged!
         for extension in ["_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
-            generate_file_name    = filename + extension
-            template_file_name    = os.path.join(template_file_dir,"template" + extension)
+            generate_file_name = filename + extension
+            template_file_name = os.path.join(
+                template_file_dir, "template" + extension)
             generate_file_content = open(template_file_name).read()
-            generate_file_handle  = open(generate_file_name,'w')
+            generate_file_handle = open(generate_file_name, 'w')
             generate_file_handle  .write(generate_file_content)
             generate_file_handle  .close()
 
-    
-    #############################
+    #
     # Generate the source files #
-    #############################
-    def CTNGenerate_C(self, buildpath, locations):        
-        # Determine the current location in Beremiz's project configuration tree 
+    #
+    def CTNGenerate_C(self, buildpath, locations):
+        # Determine the current location in Beremiz's project configuration
+        # tree
         current_location = self.GetCurrentLocation()
-        # The current location of this plugin in Beremiz's configuration tree, separated by underscores 
+        # The current location of this plugin in Beremiz's configuration tree, separated by underscores
         #  NOTE: Since BACnet plugin currently does not use sub-branches in the tree (in other words, this
         #        _BacnetSlavePlug class was actually renamed as the RootClass), the current_location_dots
         #        will actually be a single number (e.g.: 0 or 3 or 6, corresponding to the location
         #        in which the plugin was inserted in the Beremiz configuration tree on Beremiz's left panel).
-        locstr = "_".join(map(str,current_location))
-
-        # First check whether all the current parameters (inserted by user in the GUI) are valid...
+        locstr = "_".join(map(str, current_location))
+
+        # First check whether all the current parameters (inserted by user in
+        # the GUI) are valid...
         if self.HasDuplicateObjectNames():
-            self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object names.\n")%(locstr, self.CTNName()))
-            raise Exception, False
-            # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
+            self.GetCTRoot().logger.write_warning(
+                _("Error: BACnet server '%s.x: %s' contains objects with duplicate object names.\n") % (locstr, self.CTNName()))
+            raise Exception(False)
+            # TODO: return an error code instead of raising an exception
+            # (currently unsupported by Beremiz)
 
         if self.HasDuplicateObjectIDs():
-            self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object identifiers.\n")%(locstr, self.CTNName()))
-            raise Exception, False
-            # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
-            
-        #-------------------------------------------------------------------------------
+            self.GetCTRoot().logger.write_warning(
+                _("Error: BACnet server '%s.x: %s' contains objects with duplicate object identifiers.\n") % (locstr, self.CTNName()))
+            raise Exception(False)
+            # TODO: return an error code instead of raising an exception
+            # (currently unsupported by Beremiz)
+
+        # -------------------------------------------------------------------------------
         # Create and populate the loc_dict dictionary with all parameters needed to configure
         #  the generated source code (.c and .h files)
-        #-------------------------------------------------------------------------------
-        
+        # ----------------------------------------------------------------------
+
         # 1) Create the dictionary (loc_dict = {})
         loc_dict = {}
-        loc_dict["locstr"]              =         locstr
-
-        #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
-        # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
-        loc_dict["network_interface"]            = self.BACnetServerNode.getNetwork_Interface()
-        loc_dict["port_number"]                  = self.BACnetServerNode.getUDP_Port_Number()
-        loc_dict["BACnet_Device_ID"]             = self.BACnetServerNode.getBACnet_Device_ID()
-        loc_dict["BACnet_Device_Name"]           = self.BACnetServerNode.getBACnet_Device_Name()
+        loc_dict["locstr"] = locstr
+
+        # The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
+        # It will be an XML parser object created by
+        # GenerateParserFromXSDstring(self.XSD).CreateRoot()
+        loc_dict["network_interface"] = self.BACnetServerNode.getNetwork_Interface()
+        loc_dict["port_number"] = self.BACnetServerNode.getUDP_Port_Number()
+        loc_dict["BACnet_Device_ID"] = self.BACnetServerNode.getBACnet_Device_ID()
+        loc_dict["BACnet_Device_Name"] = self.BACnetServerNode.getBACnet_Device_Name()
         loc_dict["BACnet_Comm_Control_Password"] = self.BACnetServerNode.getBACnet_Communication_Control_Password()
-        loc_dict["BACnet_Device_Location"]       = self.BACnetServerNode.getBACnet_Device_Location()
-        loc_dict["BACnet_Device_Description"]    = self.BACnetServerNode.getBACnet_Device_Description()
-        loc_dict["BACnet_Device_AppSoft_Version"]= self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
+        loc_dict["BACnet_Device_Location"] = self.BACnetServerNode.getBACnet_Device_Location()
+        loc_dict["BACnet_Device_Description"] = self.BACnetServerNode.getBACnet_Device_Description()
+        loc_dict["BACnet_Device_AppSoft_Version"] = self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
         loc_dict["BACnet_Vendor_ID"] = BACNET_VENDOR_ID
         loc_dict["BACnet_Vendor_Name"] = BACNET_VENDOR_NAME
         loc_dict["BACnet_Model_Name"] = BACNET_DEVICE_MODEL_NAME
@@ -563,90 +609,95 @@
         # 2) Add the data specific to each BACnet object type
         # For each BACnet object type, start off by creating some intermediate helpful lists
         #  a) parameters_list containing the strings that will
-        #     be included in the C source code, and which will initialize the struct with the 
+        #     be included in the C source code, and which will initialize the struct with the
         #     object (Analog Value, Binary Value, or Multi State Value) parameters
         #  b) locatedvar_list containing the strings that will
-        #     declare the memory to store the located variables, as well as the 
+        #     declare the memory to store the located variables, as well as the
         #     pointers (required by matiec) that point to that memory.
 
-        # format for delaring IEC 61131-3 variable (and pointer) onto which BACnet object is mapped
+        # format for delaring IEC 61131-3 variable (and pointer) onto which
+        # BACnet object is mapped
         locvar_format = '%(Ctype)s ___%(loc)s_%(Object Identifier)s; ' + \
                         '%(Ctype)s *__%(loc)s_%(Object Identifier)s = &___%(loc)s_%(Object Identifier)s;'
 
         # format for initializing a ANALOG_VALUE_DESCR struct in C code
         #    also valid for ANALOG_INPUT and ANALOG_OUTPUT
         AX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
-                          '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
+            '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
         # format for initializing a BINARY_VALUE_DESCR struct in C code
         #    also valid for BINARY_INPUT and BINARY_OUTPUT
         BX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
-                          '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
+            '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
 
         # format for initializing a MULTISTATE_VALUE_DESCR struct in C code
         #    also valid for MULTISTATE_INPUT and MULTISTATE_OUTPUT
         MSX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
-                           '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
-
-        AV_locstr  = 'MD' + locstr+'_2'  # see the comment in GetVariableLocationTree() to grok the '_2'
-        AO_locstr  = 'QD' + locstr+'_1'  # see the comment in GetVariableLocationTree() to grok the '_1'
-        AI_locstr  = 'ID' + locstr+'_0'  # see the comment in GetVariableLocationTree() to grok the '_0'
-        BV_locstr  = 'MX' + locstr+'_5'  # see the comment in GetVariableLocationTree() to grok the '_5'
-        BO_locstr  = 'QX' + locstr+'_4'  # see the comment in GetVariableLocationTree() to grok the '_4'
-        BI_locstr  = 'IX' + locstr+'_3'  # see the comment in GetVariableLocationTree() to grok the '_3'
-        MSV_locstr = 'MB' + locstr+'_19' # see the comment in GetVariableLocationTree() to grok the '_19'
-        MSO_locstr = 'QB' + locstr+'_14' # see the comment in GetVariableLocationTree() to grok the '_14'
-        MSI_locstr = 'IB' + locstr+'_13' # see the comment in GetVariableLocationTree() to grok the '_13'
-        
-        
-        for  ObjType,  ObjLocStr,     params_format   in [
-            ("AV"   ,  AV_locstr,  AX_params_format ),
-            ("AO"   ,  AO_locstr,  AX_params_format ),
-            ("AI"   ,  AI_locstr,  AX_params_format ),
-            ("BV"   ,  BV_locstr,  BX_params_format ),
-            ("BO"   ,  BO_locstr,  BX_params_format ),
-            ("BI"   ,  BI_locstr,  BX_params_format ),
-            ("MSV"  , MSV_locstr, MSX_params_format ),
-            ("MSO"  , MSO_locstr, MSX_params_format ),
-            ("MSI"  , MSI_locstr, MSX_params_format )
-            ]:
+            '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
+
+        # see the comment in GetVariableLocationTree()
+        AV_locstr = 'MD' + locstr + '_2'
+        AO_locstr = 'QD' + locstr + '_1'
+        AI_locstr = 'ID' + locstr + '_0'
+        BV_locstr = 'MX' + locstr + '_5'
+        BO_locstr = 'QX' + locstr + '_4'
+        BI_locstr = 'IX' + locstr + '_3'
+        MSV_locstr = 'MB' + locstr + '_19'
+        MSO_locstr = 'QB' + locstr + '_14'
+        MSI_locstr = 'IB' + locstr + '_13'
+
+        for ObjType,  ObjLocStr,     params_format in [
+                ("AV",  AV_locstr,  AX_params_format),
+                ("AO",  AO_locstr,  AX_params_format),
+                ("AI",  AI_locstr,  AX_params_format),
+                ("BV",  BV_locstr,  BX_params_format),
+                ("BO",  BO_locstr,  BX_params_format),
+                ("BI",  BI_locstr,  BX_params_format),
+                ("MSV", MSV_locstr, MSX_params_format),
+                ("MSO", MSO_locstr, MSX_params_format),
+                ("MSI", MSI_locstr, MSX_params_format)]:
             parameters_list = []
             locatedvar_list = []
             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
-                ObjProp["loc" ] = ObjLocStr
+                ObjProp["loc"] = ObjLocStr
                 parameters_list.append(params_format % ObjProp)
                 locatedvar_list.append(locvar_format % ObjProp)
-            loc_dict[ ObjType + "_count"]           =          len( parameters_list )
-            loc_dict[ ObjType + "_param"]           =   ",\n".join( parameters_list )
-            loc_dict[ ObjType + "_lvars"]           =    "\n".join( locatedvar_list )
-
-        #----------------------------------------------------------------------
+            loc_dict[ObjType + "_count"] = len(parameters_list)
+            loc_dict[ObjType + "_param"] = ",\n".join(parameters_list)
+            loc_dict[ObjType + "_lvars"] = "\n".join(locatedvar_list)
+
+        # ----------------------------------------------------------------------
         # Create the C source files that implement the BACnet server
-        #----------------------------------------------------------------------
-        
+        # ----------------------------------------------------------------------
+
         # Names of the .c files that will be generated, based on a template file with same name
         #   (names without '.c'  --> this will be added later)
         #   main server.c file is handled separately
         Generated_BACnet_c_mainfile = "server"
-        Generated_BACnet_c_files = ["ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
+        Generated_BACnet_c_files = [
+            "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
 
         # Names of the .h files that will be generated, based on a template file with same name
         #   (names without '.h'  --> this will be added later)
-        Generated_BACnet_h_files = ["server", "device", "config_bacnet_for_beremiz",
-                                    "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
-                                   ]
+        Generated_BACnet_h_files = [
+            "server", "device", "config_bacnet_for_beremiz",
+            "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
+        ]
 
         # Generate the files with the source code
         postfix = "_".join(map(str, current_location))
-        template_file_dir     = os.path.join(os.path.split(__file__)[0],"runtime")
-        
-        def generate_file(file_name, extension): 
-            generate_file_name    = os.path.join(buildpath, "%s_%s.%s"%(file_name,postfix,extension))
-            template_file_name    = os.path.join(template_file_dir,"%s.%s"%(file_name,extension))
+        template_file_dir = os.path.join(
+            os.path.split(__file__)[0], "runtime")
+
+        def generate_file(file_name, extension):
+            generate_file_name = os.path.join(
+                buildpath, "%s_%s.%s" % (file_name, postfix, extension))
+            template_file_name = os.path.join(
+                template_file_dir, "%s.%s" % (file_name, extension))
             generate_file_content = open(template_file_name).read() % loc_dict
-            generate_file_handle  = open(generate_file_name,'w')
-            generate_file_handle  .write(generate_file_content)
-            generate_file_handle  .close()
+            generate_file_handle = open(generate_file_name, 'w')
+            generate_file_handle.write(generate_file_content)
+            generate_file_handle.close()
 
         for file_name in Generated_BACnet_c_files:
             generate_file(file_name, "c")
@@ -654,44 +705,23 @@
             generate_file(file_name, "h")
         generate_file(Generated_BACnet_c_mainfile, "c")
         Generated_BACnet_c_mainfile_name = \
-            os.path.join(buildpath, "%s_%s.%s"%(Generated_BACnet_c_mainfile,postfix,"c"))
-
-        #----------------------------------------------------------------------
+            os.path.join(buildpath, "%s_%s.%s" %
+                         (Generated_BACnet_c_mainfile, postfix, "c"))
+
+        # ----------------------------------------------------------------------
         # Finally, define the compilation and linking commands and flags
-        #----------------------------------------------------------------------
-        
+        # ----------------------------------------------------------------------
+
         LDFLAGS = []
         # when using dynamically linked library...
-        #LDFLAGS.append(' -lbacnet')   
-        #LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
-        #LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
+        # LDFLAGS.append(' -lbacnet')
+        # LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
+        # LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
         # when using static library:
-        LDFLAGS.append(' "'+os.path.join(BacnetLibraryPath, "libbacnet.a")+'"')  
-
-        CFLAGS   = ' -I"'+BacnetIncludePath+'"'
-        CFLAGS  += ' -I"'+BacnetIncludePortPath+'"'
-        
+        LDFLAGS.append(
+            ' "' + os.path.join(BacnetLibraryPath, "libbacnet.a") + '"')
+
+        CFLAGS = ' -I"' + BacnetIncludePath + '"'
+        CFLAGS += ' -I"' + BacnetIncludePortPath + '"'
+
         return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
-
-
-
-###################################################
-###################################################
-#                                                 #
-#             R O O T    C L A S S                # 
-#                                                 #
-###################################################
-###################################################
-#class RootClass:
-    #XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
-    #<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-      #<xsd:element name="BACnetRoot">
-      #</xsd:element>
-    #</xsd:schema>
-    #"""
-    #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug,  "Bacnet Slave")
-                       ##,("XXX",_XXXXPlug, "XXX")
-                       #]
-    
-    #def CTNGenerate_C(self, buildpath, locations):        
-        #return [], "", True
--- a/editors/CodeFileEditor.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/editors/CodeFileEditor.py	Wed Aug 08 13:46:19 2018 +0200
@@ -598,7 +598,31 @@
 #                         Helper for VariablesGrid values
 # -------------------------------------------------------------------------------
 
+class AllGridCellEditor(wx.grid.GridCellTextEditor):
+    def __init__(self, table, row, col):
+        wx.grid.GridCellTextEditor.__init__(self)
+
+
+class ClassGridCellEditor(wx.grid.GridCellChoiceEditor):
+    def __init__(self, table, row, col):
+        wx.grid.GridCellChoiceEditor.__init__(self)
+        self.SetParameters("input,memory,output")
+
+
 class VariablesTable(CustomTable):
+    __defaultColumnType = dict(
+        [(name, AllGridCellEditor) for name in
+         ["Name", "Initial", "Description", "OnChange", "Options"]] +
+        [('Class', ClassGridCellEditor), ('Type', None)])
+
+    def __init__(self, *args, **kwargs):
+        my_columns = kwargs.pop("additional_columns")
+        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():
+                    self.columnTypes[key] = my_columns[key]
 
     def GetValue(self, row, col):
         if row < self.GetNumberRows():
@@ -621,15 +645,9 @@
                 renderer = None
                 colname = self.GetColLabelValue(col, False)
 
-                if colname in ["Name", "Initial", "Description", "OnChange", "Options"]:
-                    editor = wx.grid.GridCellTextEditor()
-                elif colname == "Class":
-                    editor = wx.grid.GridCellChoiceEditor()
-                    editor.SetParameters("input,memory,output")
-                elif colname == "Type":
-                    pass
-                else:
-                    grid.SetReadOnly(row, col, True)
+                editortype = self.columnTypes.get(colname, None)
+                if editortype is not None:
+                    editor = editortype(self, row, col)
 
                 grid.SetCellEditor(row, col, editor)
                 grid.SetCellRenderer(row, col, renderer)
@@ -640,7 +658,7 @@
 
 class VariablesEditor(wx.Panel):
 
-    def __init__(self, parent, window, controler):
+    def __init__(self, parent, window, controler, additional_columns=None):
         wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL)
 
         main_sizer = wx.FlexGridSizer(cols=2, hgap=0, rows=1, vgap=4)
@@ -680,7 +698,8 @@
             "OnChange":    "",
             "Options":     ""
         }
-        self.Table = VariablesTable(self, [], self.GetVariableTableColnames())
+
+        self.Table = VariablesTable(self, [], self.GetVariableTableColnames(), additional_columns=additional_columns)
         self.ColAlignements = [wx.ALIGN_RIGHT] +  \
                               [wx.ALIGN_LEFT]*(len(self.VariablesDefaultValue))
         self.ColSizes = [20, 150] + [130]*(len(self.VariablesDefaultValue)-1)
@@ -845,6 +864,7 @@
 
     CONFNODEEDITOR_TABS = []
     CODE_EDITOR = None
+    COLUMNS_TYPE = None
 
     def _create_CodePanel(self, prnt):
         self.CodeEditorPanel = wx.SplitterWindow(prnt)
@@ -852,7 +872,8 @@
 
         self.VariablesPanel = VariablesEditor(self.CodeEditorPanel,
                                               self.ParentWindow,
-                                              self.Controler)
+                                              self.Controler,
+                                              self.COLUMNS_TYPE)
 
         if self.CODE_EDITOR is not None:
             self.CodeEditor = self.CODE_EDITOR(self.CodeEditorPanel,
--- a/features.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/features.py	Wed Aug 08 13:46:19 2018 +0200
@@ -9,10 +9,10 @@
 # See COPYING file for copyrights details.
 
 libraries = [
-    ('Native', 'NativeLib.NativeLibrary'),
-    ('Python', 'py_ext.PythonLibrary'),
-    ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary'),
-    ('SVGUI', 'svgui.SVGUILibrary')]
+    ('Native', 'NativeLib.NativeLibrary', True),
+    ('Python', 'py_ext.PythonLibrary', True),
+    ('Etherlab', 'etherlab.EthercatMaster.EtherlabLibrary', False),
+    ('SVGUI', 'svgui.SVGUILibrary', False)]
 
 catalog = [
     ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'),
--- a/runtime/NevowServer.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/runtime/NevowServer.py	Wed Aug 08 13:46:19 2018 +0200
@@ -26,10 +26,19 @@
 from __future__ import absolute_import
 from __future__ import print_function
 import os
-from nevow import appserver, inevow, tags, loaders, athena
+import platform as platform_module
+from zope.interface import implements
+from nevow import appserver, inevow, tags, loaders, athena, url, rend
 from nevow.page import renderer
+from formless import annotate
+from formless import webform
+from formless import configurable
 from twisted.internet import reactor
+
 import util.paths as paths
+from runtime.loglevels import LogLevels, LogLevelsDict
+
+PAGE_TITLE = 'Beremiz Runtime Web Interface'
 
 xhtml_header = '''<?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
@@ -37,6 +46,7 @@
 '''
 
 WorkingDir = None
+_PySrv = None
 
 
 class PLCHMI(athena.LiveElement):
@@ -120,21 +130,130 @@
             child.detach()
 
 
+class ConfigurableBindings(configurable.Configurable):
+
+    def __init__(self):
+        configurable.Configurable.__init__(self, None)
+        self.bindingsNames = []
+
+    def getBindingNames(self, ctx):
+        return self.bindingsNames
+
+    def addExtension(self, name, desc, fields, btnlabel, callback):
+        def _bind(ctx):
+            return annotate.MethodBinding(
+                'action_' + name,
+                annotate.Method(
+                    arguments=[
+                        annotate.Argument(*field)
+                        for field in fields],
+                    label=desc),
+                action=btnlabel)
+        setattr(self, 'bind_' + name, _bind)
+
+        setattr(self, 'action_' + name, callback)
+
+        self.bindingsNames.append(name)
+
+ConfigurableSettings = ConfigurableBindings()
+
+
+class ISettings(annotate.TypedInterface):
+    platform = annotate.String(label=_("Platform"),
+                               default=platform_module.system() +
+                               " " + platform_module.release(),
+                               immutable=True)
+
+    # TODO version ?
+
+    # pylint: disable=no-self-argument
+    def sendLogMessage(
+            ctx=annotate.Context(),
+            level=annotate.Choice(LogLevels,
+                                  required=True,
+                                  label=_("Log message level")),
+            message=annotate.String(label=_("Message text"))):
+        pass
+
+    sendLogMessage = annotate.autocallable(sendLogMessage,
+                                           label=_(
+                                               "Send a message to the log"),
+                                           action=_("Send"))
+
+customSettingsURLs = {
+}
+
+
+class SettingsPage(rend.Page):
+    # We deserve a slash
+    addSlash = True
+
+    # This makes webform_css url answer some default CSS
+    child_webform_css = webform.defaultCSS
+
+    implements(ISettings)
+
+    docFactory = loaders.stan([
+        tags.html[
+            tags.head[
+                tags.title[_("Beremiz Runtime Settings")],
+                tags.link(rel='stylesheet',
+                          type='text/css',
+                          href=url.here.child("webform_css"))
+            ],
+            tags.body[
+                tags.h1["Runtime settings:"],
+                webform.renderForms('staticSettings'),
+                tags.h2["Extensions settings:"],
+                webform.renderForms('dynamicSettings'),
+            ]
+        ]
+    ])
+
+    def configurable_staticSettings(self, ctx):
+        return configurable.TypedInterfaceConfigurable(self)
+
+    def configurable_dynamicSettings(self, ctx):
+        return ConfigurableSettings
+
+    def sendLogMessage(self, level, message, **kwargs):
+        level = LogLevelsDict[level]
+        if _PySrv.plcobj is not None:
+            _PySrv.plcobj.LogMessage(
+                level, "Web form log message: " + message)
+
+    def locateChild(self, ctx, segments):
+        if segments[0] in customSettingsURLs:
+            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.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"))
+                                           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.jsModules.mapping[u'WebInterface'] = paths.AbsNeighbourFile(
+            __file__, 'webinterface.js')
         self.plcState = plcState
         self.MainPage.setPLCState(plcState)
 
@@ -194,6 +313,7 @@
 
 
 class statuslistener(object):
+
     def __init__(self, site):
         self.oldstate = None
         self.site = site
@@ -209,3 +329,8 @@
 
 def website_statuslistener_factory(site):
     return statuslistener(site).listen
+
+
+def SetServer(pysrv):
+    global _PySrv
+    _PySrv = pysrv
--- a/runtime/WampClient.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/runtime/WampClient.py	Wed Aug 08 13:46:19 2018 +0200
@@ -26,31 +26,53 @@
 from __future__ import print_function
 import time
 import json
+import os
+import re
 from autobahn.twisted import wamp
 from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
 from autobahn.wamp import types, auth
 from autobahn.wamp.serializer import MsgPackSerializer
 from twisted.internet.defer import inlineCallbacks
 from twisted.internet.protocol import ReconnectingClientFactory
-
-
+from twisted.python.components import registerAdapter
+
+from formless import annotate, webform
+import formless
+from nevow import tags, url, static
+
+mandatoryConfigItems = ["ID", "active", "realm", "url"]
+
+_transportFactory = None
 _WampSession = None
 _PySrv = None
+WorkingDir = None
+
+# Find pre-existing project WAMP config file
+_WampConf = None
+_WampSecret = None
 
 ExposedCalls = [
-    "StartPLC",
-    "StopPLC",
-    "ForceReload",
-    "GetPLCstatus",
-    "NewPLC",
-    "MatchMD5",
-    "SetTraceVariablesList",
-    "GetTraceVariables",
-    "RemoteExec",
-    "GetLogMessage",
-    "ResetLogCount",
+    ("StartPLC", {}),
+    ("StopPLC", {}),
+    ("ForceReload", {}),
+    ("GetPLCstatus", {}),
+    ("NewPLC", {}),
+    ("MatchMD5", {}),
+    ("SetTraceVariablesList", {}),
+    ("GetTraceVariables", {}),
+    ("RemoteExec", {}),
+    ("GetLogMessage", {}),
+    ("ResetLogCount", {})
 ]
 
+# de-activated dumb wamp config
+defaultWampConfig = {
+    "ID": "wamptest",
+    "active": False,
+    "realm": "Automation",
+    "url": "ws://127.0.0.1:8888"
+}
+
 # Those two lists are meant to be filled by customized runtime
 # or User python code.
 
@@ -60,6 +82,8 @@
 """ things to do on join (callables) """
 DoOnJoin = []
 
+lastKnownConfig = None
+
 
 def GetCallee(name):
     """ Get Callee or Subscriber corresponding to '.' spearated object path """
@@ -71,30 +95,41 @@
 
 
 class WampSession(wamp.ApplicationSession):
+
     def onConnect(self):
         if "secret" in self.config.extra:
-            user = self.config.extra["ID"].encode('utf8')
+            user = self.config.extra["ID"]
             self.join(u"Automation", [u"wampcra"], user)
         else:
             self.join(u"Automation")
 
     def onChallenge(self, challenge):
         if challenge.method == u"wampcra":
-            secret = self.config.extra["secret"].encode('utf8')
-            signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8'))
-            return signature.decode("ascii")
-        else:
-            raise Exception("don't know how to handle authmethod {}".format(challenge.method))
+            if "secret" in self.config.extra:
+                secret = self.config.extra["secret"].encode('utf8')
+                signature = auth.compute_wcs(
+                    secret, challenge.extra['challenge'].encode('utf8'))
+                return signature.decode("ascii")
+            else:
+                raise Exception("no secret given for authentication")
+        else:
+            raise Exception(
+                "don't know how to handle authmethod {}".format(challenge.method))
 
     @inlineCallbacks
     def onJoin(self, details):
         global _WampSession
         _WampSession = self
         ID = self.config.extra["ID"]
-        print('WAMP session joined by :', ID)
-        for name in ExposedCalls:
-            regoption = types.RegisterOptions(u'exact', u'last')
-            yield self.register(GetCallee(name), u'.'.join((ID, name)), regoption)
+
+        for name, kwargs in ExposedCalls:
+            try:
+                registerOptions = types.RegisterOptions(**kwargs)
+            except TypeError as e:
+                registerOptions = None
+                print(_("TypeError register option: {}".format(e)))
+
+            yield self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions)
 
         for name in SubscribedEvents:
             yield self.subscribe(GetCallee(name), unicode(name))
@@ -102,81 +137,299 @@
         for func in DoOnJoin:
             yield func(self)
 
+        print(_('WAMP session joined (%s) by:' % time.ctime()), ID)
+
     def onLeave(self, details):
-        global _WampSession
+        global _WampSession, _transportFactory
+        super(WampSession, self).onLeave(details)
         _WampSession = None
+        _transportFactory = None
         print(_('WAMP session left'))
 
 
 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
+
+    def __init__(self, config, *args, **kwargs):
+        global _transportFactory
+        WampWebSocketClientFactory.__init__(self, *args, **kwargs)
+
+        try:
+            protocolOptions = config.extra.get('protocolOptions', None)
+            if protocolOptions:
+                self.setProtocolOptions(**protocolOptions)
+            _transportFactory = self
+        except Exception, e:
+            print(_("Custom protocol options failed :"), e)
+            _transportFactory = None
+
+    def buildProtocol(self, addr):
+        self.resetDelay()
+        return ReconnectingClientFactory.buildProtocol(self, addr)
+
     def clientConnectionFailed(self, connector, reason):
-        print(_("WAMP Client connection failed (%s) .. retrying .." % time.ctime()))
-        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
+        if self.continueTrying:
+            print(_("WAMP Client connection failed (%s) .. retrying ..") %
+                  time.ctime())
+            super(ReconnectingWampWebSocketClientFactory,
+                  self).clientConnectionFailed(connector, reason)
+        else:
+            del connector
 
     def clientConnectionLost(self, connector, reason):
-        print(_("WAMP Client connection lost (%s) .. retrying .." % time.ctime()))
-        ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
-
-
-def LoadWampClientConf(wampconf):
-    try:
-        WSClientConf = json.load(open(wampconf))
-        return WSClientConf
-    except ValueError, ve:
-        print(_("WAMP load error: "), ve)
-        return None
-    except Exception:
-        return None
+        if self.continueTrying:
+            print(_("WAMP Client connection lost (%s) .. retrying ..") %
+                  time.ctime())
+            super(ReconnectingWampWebSocketClientFactory,
+                  self).clientConnectionFailed(connector, reason)
+        else:
+            del connector
+
+
+def CheckConfiguration(WampClientConf):
+    url = WampClientConf["url"]
+    if not IsCorrectUri(url):
+        raise annotate.ValidateError(
+            {"url": "Invalid URL: {}".format(url)},
+            _("WAMP configuration error:"))
+
+
+def GetConfiguration():
+    global lastKnownConfig
+
+    if os.path.exists(_WampConf):
+        WampClientConf = json.load(open(_WampConf))
+    else:
+        WampClientConf = defaultWampConfig.copy()
+
+    for itemName in mandatoryConfigItems:
+        if WampClientConf.get(itemName, None) is None:
+            raise Exception(
+                _("WAMP configuration error : missing '{}' parameter.").format(itemName))
+
+    CheckConfiguration(WampClientConf)
+
+    lastKnownConfig = WampClientConf.copy()
+    return WampClientConf
+
+
+def SetWampSecret(wampSecret):
+    with open(os.path.realpath(_WampSecret), 'w') as f:
+        f.write(wampSecret)
+
+
+def SetConfiguration(WampClientConf):
+    global lastKnownConfig
+
+    CheckConfiguration(WampClientConf)
+
+    lastKnownConfig = WampClientConf.copy()
+
+    with open(os.path.realpath(_WampConf), 'w') as f:
+        json.dump(WampClientConf, f, sort_keys=True, indent=4)
+    if 'active' in WampClientConf and WampClientConf['active']:
+        if _transportFactory and _WampSession:
+            StopReconnectWampClient()
+        StartReconnectWampClient()
+    else:
+        StopReconnectWampClient()
+
+    return WampClientConf
 
 
 def LoadWampSecret(secretfname):
-    try:
-        WSClientWampSecret = open(secretfname, 'rb').read()
-        return WSClientWampSecret
-    except ValueError, ve:
-        print(_("Wamp secret load error:"), ve)
-        return None
-    except Exception:
-        return None
-
-
-def RegisterWampClient(wampconf, secretfname):
-
-    WSClientConf = LoadWampClientConf(wampconf)
-
-    if not WSClientConf:
-        print(_("WAMP client connection not established!"))
+    WSClientWampSecret = open(secretfname, 'rb').read()
+    if len(WSClientWampSecret) == 0:
+        raise Exception(_("WAMP secret empty"))
+    return WSClientWampSecret
+
+
+def IsCorrectUri(uri):
+    return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None
+
+
+def RegisterWampClient(wampconf=None, wampsecret=None):
+    global _WampConf, _WampSecret
+    _WampConfDefault = os.path.join(WorkingDir, "wampconf.json")
+    _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret")
+
+    # set config file path only if not already set
+    if _WampConf is None:
+        # default project's wampconf has precedance over commandline given
+        if os.path.exists(_WampConfDefault) or wampconf is None:
+            _WampConf = _WampConfDefault
+        else:
+            _WampConf = wampconf
+
+    WampClientConf = GetConfiguration()
+
+    # set secret file path only if not already set
+    if _WampSecret is None:
+        # default project's wamp secret also
+        # has precedance over commandline given
+        if os.path.exists(_WampSecretDefault):
+            _WampSecret = _WampSecretDefault
+        else:
+            _WampSecret = wampsecret
+
+    if _WampSecret is not None:
+        WampClientConf["secret"] = LoadWampSecret(_WampSecret)
+    else:
+        print(_("WAMP authentication has no secret configured"))
+        _WampSecret = _WampSecretDefault
+
+    if not WampClientConf["active"]:
+        print(_("WAMP deactivated in configuration"))
         return
 
-    WampSecret = LoadWampSecret(secretfname)
-
-    if WampSecret is not None:
-        WSClientConf["secret"] = WampSecret
-
     # create a WAMP application session factory
     component_config = types.ComponentConfig(
-        realm=WSClientConf["realm"],
-        extra=WSClientConf)
+        realm=WampClientConf["realm"],
+        extra=WampClientConf)
     session_factory = wamp.ApplicationSessionFactory(
         config=component_config)
     session_factory.session = WampSession
 
     # create a WAMP-over-WebSocket transport client factory
-    transport_factory = ReconnectingWampWebSocketClientFactory(
+    ReconnectingWampWebSocketClientFactory(
+        component_config,
         session_factory,
-        url=WSClientConf["url"],
+        url=WampClientConf["url"],
         serializers=[MsgPackSerializer()])
 
     # start the client from a Twisted endpoint
-    conn = connectWS(transport_factory)
-    print(_("WAMP client connecting to :"), WSClientConf["url"])
-    return conn
+    if _transportFactory:
+        connectWS(_transportFactory)
+        print(_("WAMP client connecting to :"), WampClientConf["url"])
+        return True
+    else:
+        print(_("WAMP client can not connect to :"), WampClientConf["url"])
+        return False
+
+
+def StopReconnectWampClient():
+    if _transportFactory is not None:
+        _transportFactory.stopTrying()
+    if _WampSession is not None:
+        _WampSession.leave()
+
+
+def StartReconnectWampClient():
+    if _WampSession:
+        # do reconnect
+        _WampSession.disconnect()
+        return True
+    else:
+        # do connect
+        RegisterWampClient()
+        return True
 
 
 def GetSession():
     return _WampSession
 
 
+def getWampStatus():
+    if _transportFactory is not None:
+        if _WampSession is not None:
+            if _WampSession.is_attached():
+                return "Attached"
+            return "Established"
+        return "Connecting"
+    return "Disconnected"
+
+
 def SetServer(pysrv):
-    global _PySrv
     _PySrv = pysrv
+
+
+# WEB CONFIGURATION INTERFACE
+WAMP_SECRET_URL = "secret"
+webExposedConfigItems = ['active', 'url', 'ID']
+
+
+def wampConfigDefault(ctx, argument):
+    if lastKnownConfig is not None:
+        return lastKnownConfig.get(argument.name, None)
+
+
+def wampConfig(**kwargs):
+    secretfile_field = kwargs["secretfile"]
+    if secretfile_field is not None:
+        secretfile = getattr(secretfile_field, "file", None)
+        if secretfile is not None:
+            secret = secretfile_field.file.read()
+            SetWampSecret(secret)
+
+    newConfig = lastKnownConfig.copy()
+    for argname in webExposedConfigItems:
+        arg = kwargs.get(argname, None)
+        if arg is not None:
+            newConfig[argname] = arg
+
+    SetConfiguration(newConfig)
+
+
+class FileUploadDownload(annotate.FileUpload):
+    pass
+
+
+class FileUploadDownloadRenderer(webform.FileUploadRenderer):
+
+    def input(self, context, slot, data, name, value):
+        # pylint: disable=expression-not-assigned
+        slot[_("Upload:")]
+        slot = webform.FileUploadRenderer.input(
+            self, context, slot, data, name, value)
+        download_url = data.typedValue.getAttribute('download_url')
+        return slot[tags.a(href=download_url)[_("Download")]]
+
+registerAdapter(FileUploadDownloadRenderer, FileUploadDownload,
+                formless.iformless.ITypedRenderer)
+
+
+def getDownloadUrl(ctx, argument):
+    if lastKnownConfig is not None:
+        return url.URL.fromContext(ctx).\
+            child(WAMP_SECRET_URL).\
+            child(lastKnownConfig["ID"] + ".secret")
+
+webFormInterface = [
+    ("status",
+     annotate.String(label=_("Current status"),
+                     immutable=True,
+                     default=lambda *k:getWampStatus())),
+    ("ID",
+     annotate.String(label=_("ID"),
+                     default=wampConfigDefault)),
+    ("secretfile",
+     FileUploadDownload(label=_("File containing secret for that ID"),
+                        download_url=getDownloadUrl)),
+    ("active",
+     annotate.Boolean(label=_("Enable WAMP connection"),
+                      default=wampConfigDefault)),
+    ("url",
+     annotate.String(label=_("WAMP Server URL"),
+                     default=wampConfigDefault))]
+
+
+def deliverWampSecret(ctx, segments):
+    # filename = segments[1].decode('utf-8')
+
+    # FIXME: compare filename to ID+".secret"
+    # for now all url under /secret returns the secret
+
+    # TODO: make beautifull message in case of exception
+    # while loading secret (if empty or dont exist)
+    secret = LoadWampSecret(_WampSecret)
+    return static.Data(secret, 'application/octet-stream'), ()
+
+
+def RegisterWebSettings(NS):
+    NS.ConfigurableSettings.addExtension(
+        "wamp",
+        _("Wamp Settings"),
+        webFormInterface,
+        _("Set"),
+        wampConfig)
+
+    NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret
--- a/tests/tools/check_source.sh	Wed Aug 01 13:09:45 2018 +0300
+++ b/tests/tools/check_source.sh	Wed Aug 08 13:46:19 2018 +0200
@@ -316,7 +316,7 @@
 
 get_files_to_check()
 {
-    py_files=$(find . -name '*.py' -not -path '*/build/*')
+    py_files=$(find . -name '*.py' -not -path '*/build/*' -not -path './etherlab/*')
     if [ -e .hg/skiphook ]; then
 	echo "Skipping checks in the hook ..."
 	exit 0
--- a/tests/wamp/.crossbar/config.json	Wed Aug 01 13:09:45 2018 +0300
+++ b/tests/wamp/.crossbar/config.json	Wed Aug 08 13:46:19 2018 +0200
@@ -39,13 +39,15 @@
             "transports": [
                 {
                     "type": "websocket",
+                    "debug": true,
                     "endpoint": {
                         "type": "tcp",
                         "port": 8888
                     },
                     "url": "ws://127.0.0.1:8888/",
                     "serializers": [
-                        "msgpack"
+                        "msgpack",
+                        "json"
                     ]
                 }
             ]
--- a/tests/wamp/README	Wed Aug 01 13:09:45 2018 +0300
+++ b/tests/wamp/README	Wed Aug 08 13:46:19 2018 +0200
@@ -1,25 +1,26 @@
-Crossbar test router configuration is available in .crossbar directory.
-
-Starting command:
-crossbar start
-
-This project contains wamp client config to be loaded at runtime startup.
-
-project_files/wampconf.json
+/* This project contains wamp client config to be loaded at runtime startup. */
+./project_files/wampconf.json
 
 wampconf.json is in "Project Files", so it is copied to runtime's working directory, and then loaded after program transfer + runtime restart.
 
 Otherwise, wamp config file path can be forced :
 ./Beremiz_service.py -c /path/to/my/wampconf.json /working/dir
 
-Otherwise, path for CRA secret can be forced :
-./Beremiz_service.py -s /path/to/my/secret /working/dir
+/* Crossbar install */
+#sudo apt-get update
+#sudo apt-get -y dist-upgrade
+sudo apt-get -y install build-essential libssl-dev libffi-dev libreadline-dev libbz2-dev libsqlite3-dev libncurses5-dev
+sudo python -m pip install -U pip
+sudo pip install crossbar
+crossbar version
 
+/* Start Crossbar command: */
+crossbar start
+
+/* Crossbar test router configuration is available in .crossbar directory. */
 Tested on version:
- Crossbar.io        : 17.12.1 (Crossbar.io COMMUNITY)
-   Autobahn         : 17.10.1 (with JSON, MessagePack, CBOR, UBJSON)
+ Crossbar.io        : 18.3.1 (Crossbar.io COMMUNITY)
+   Autobahn         : 18.3.1 (with JSON, MessagePack, CBOR, UBJSON)
    Twisted          : 17.9.0-EPollReactor
    LMDB             : 0.93/lmdb-0.9.18
-   Python           : 2.7.12/CPython
-
-
+   Python           : 2.7.12/CPython
\ No newline at end of file
--- a/tests/wamp/beremiz.xml	Wed Aug 01 13:09:45 2018 +0300
+++ b/tests/wamp/beremiz.xml	Wed Aug 08 13:46:19 2018 +0200
@@ -1,4 +1,4 @@
 <?xml version='1.0' encoding='utf-8'?>
-<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#wamptest">
+<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="WAMP://127.0.0.1:8888#Automation#WampID">
   <TargetType/>
 </BeremizRoot>
--- a/tests/wamp/project_files/wampconf.json	Wed Aug 01 13:09:45 2018 +0300
+++ b/tests/wamp/project_files/wampconf.json	Wed Aug 08 13:46:19 2018 +0200
@@ -1,7 +1,10 @@
 {
-    "url":"ws://127.0.0.1:8888",
-    "realm":"Automation",
-    "ID":"wamptest",
-    "password":"1234567890",
-    "key":"ABCDEFGHIJ"
+    "ID": "wamptest", 
+    "active": true, 
+    "protocolOptions": {
+        "autoPingInterval": 60, 
+        "autoPingTimeout": 20
+    }, 
+    "realm": "Automation", 
+    "url": "ws://127.0.0.1:8888"
 }
--- a/wxglade_hmi/wxglade_hmi.py	Wed Aug 01 13:09:45 2018 +0300
+++ b/wxglade_hmi/wxglade_hmi.py	Wed Aug 08 13:46:19 2018 +0200
@@ -80,7 +80,8 @@
         if wx.Platform == '__WXMSW__':
             glade = "\"%s\"" % glade
         mode = {False: os.P_NOWAIT, True: os.P_WAIT}[wait]
-        os.spawnv(mode, sys.executable, ["\"%s\"" % sys.executable] + [glade] + options)
+        os.spawnv(mode, sys.executable,
+                  ["\"%s\"" % sys.executable] + [glade] + options)
 
     def OnCTNSave(self, from_project_path=None):
         if from_project_path is not None:
@@ -92,7 +93,7 @@
 
         # list containing description of all objects declared in wxglade
         hmi_objects = []
-        # list containing only description of the main frame object 
+        # list containing only description of the main frame object
         main_frames = []
 
         wxgfile_path = self._getWXGLADEpath()
@@ -112,7 +113,7 @@
                             node.getElementsByTagName("handler")]}
 
                     hmi_objects.append(wxglade_object_desc)
-                    if name == self.CTNName() :
+                    if name == self.CTNName():
                         main_frames.append(wxglade_object_desc)
 
             hmipyfile_path = os.path.join(self._getBuildPath(), "hmi.py")
@@ -121,7 +122,8 @@
                 wxghmipyfile_path = "\"%s\"" % hmipyfile_path
             else:
                 wxghmipyfile_path = hmipyfile_path
-            self.launch_wxglade(['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True)
+            self.launch_wxglade(
+                ['-o', wxghmipyfile_path, '-g', 'python', wxgfile_path], wait=True)
 
             hmipyfile = open(hmipyfile_path, 'r')
             define_hmi = hmipyfile.read().decode('utf-8')
@@ -132,8 +134,8 @@
 
         declare_hmi = "\n".join(["%(name)s = None\n" % x for x in main_frames])
         declare_hmi += "\n".join(["\n".join(["%(class)s.%(h)s = %(h)s" %
-                                            dict(x, h=h) for h in x['handlers']])
-                                 for x in hmi_objects])
+                                             dict(x, h=h) for h in x['handlers']])
+                                  for x in hmi_objects])
         global_hmi = ("global %s\n" % ",".join(
             [x["name"] for x in main_frames]) if len(main_frames) > 0 else "")
         init_hmi = "\n".join(["""\
@@ -160,9 +162,9 @@
 
         if len(main_frames) == 0 and \
            len(getattr(self.CodeFile, "start").getanyText().strip()) == 0:
-                self.GetCTRoot().logger.write_warning(
-                    _("Warning: WxGlade HMI has no object with name identical to extension name, and no python code is provided in start section to create object.\n"))
-            
+            self.GetCTRoot().logger.write_warning(
+                _("Warning: WxGlade HMI has no object with name identical to extension name, and no python code is provided in start section to create object.\n"))
+
         return PythonFileCTNMixin.CTNGenerate_C(self, buildpath, locations)
 
     def _editWXGLADE(self):