merge
authorMario de Sousa <msousa@fe.up.pt>
Fri, 25 Dec 2020 17:12:02 +0000
changeset 2718 76e8ec46828a
parent 2716 ebb2595504f0 (diff)
parent 2717 b3b6991f1cb6 (current diff)
child 2719 745b64e7c695
child 2723 cde2e410b874
merge
modbus/modbus.py
--- a/Beremiz_service.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/Beremiz_service.py	Fri Dec 25 17:12:02 2020 +0000
@@ -30,6 +30,7 @@
 import sys
 import getopt
 import threading
+import shlex
 from threading import Thread, Semaphore, Lock, currentThread
 from builtins import str as text
 from past.builtins import execfile
@@ -44,6 +45,10 @@
 from runtime.Stunnel import ensurePSK
 import util.paths as paths
 
+try:
+    from runtime.spawn_subprocess import Popen
+except ImportError:
+    from subprocess import Popen
 
 def version():
     from version import app_version
@@ -72,7 +77,7 @@
 
 
 try:
-    opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h", ["help", "version"])
+    opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h", ["help", "version", "status-change=", "on-plc-start=", "on-plc-stop="])
 except getopt.GetoptError as err:
     # print help information and exit:
     print(str(err))  # will print something like "option -a not recognized"
@@ -93,6 +98,13 @@
 havetwisted = False
 
 extensions = []
+statuschange = []
+def status_change_call_factory(wanted, args):
+    def status_change_call(status):
+        if wanted is None or status is wanted:
+            cmd = shlex.split(args.format(status))
+            Popen(cmd)
+    return status_change_call
 
 for o, a in opts:
     if o == "-h" or o == "--help":
@@ -101,6 +113,12 @@
     if o == "--version":
         version()
         sys.exit()
+    if o == "--on-plc-start":
+        statuschange.append(status_change_call_factory(PlcStatus.Started, a))
+    elif o == "--on-plc-stop":
+        statuschange.append(status_change_call_factory(PlcStatus.Stopped, a))
+    elif o == "--status-change":
+        statuschange.append(status_change_call_factory(None, a))
     elif o == "-i":
         if len(a.split(".")) == 4:
             interface = a
@@ -401,7 +419,6 @@
             havetwisted = False
 
 pyruntimevars = {}
-statuschange = []
 
 if havetwisted:
     if havewx:
@@ -475,37 +492,16 @@
 
 installThreadExcepthook()
 havewamp = False
-haveBNconf = False
-haveMBconf = False
-
 
 if havetwisted:
     if webport is not None:
         try:
             import runtime.NevowServer as NS  # pylint: disable=ungrouped-imports
+            NS.WorkingDir = WorkingDir
         except Exception:
             LogMessageAndException(_("Nevow/Athena import failed :"))
             webport = None
-        NS.WorkingDir = WorkingDir  # bug? what happens if import fails?
-
-    # Try to add support for BACnet configuration via web server interface
-    # NOTE:BACnet web config only makes sense if web server is available
-    if webport is not None:
-        try:
-            import runtime.BACnet_config as BNconf
-            haveBNconf = True
-        except Exception:
-            LogMessageAndException(_("BACnet configuration web interface - import failed :"))
-
-    # Try to add support for Modbus configuration via web server interface
-    # NOTE:Modbus web config only makes sense if web server is available
-    if webport is not None:
-        try:
-            import runtime.Modbus_config as MBconf
-            haveMBconf = True
-        except Exception:
-            LogMessageAndException(_("Modbus configuration web interface - import failed :"))
-                
+
     try:
         import runtime.WampClient as WC  # pylint: disable=ungrouped-imports
         WC.WorkingDir = WorkingDir
@@ -527,8 +523,6 @@
 runtime.CreatePLCObjectSingleton(
     WorkingDir, argv, statuschange, evaluator, pyruntimevars)
 
-plcobj = runtime.GetPLCObjectSingleton()
-
 pyroserver = PyroServer(servicename, interface, port)
 
 if havewx:
@@ -544,18 +538,6 @@
         except Exception:
             LogMessageAndException(_("Nevow Web service failed. "))
 
-    if haveBNconf:
-        try:
-            BNconf.init(plcobj, NS, WorkingDir)
-        except Exception:
-            LogMessageAndException(_("BACnet web configuration failed startup. "))
-
-    if haveMBconf:
-        try:
-            MBconf.init(plcobj, NS, WorkingDir)
-        except Exception:
-            LogMessageAndException(_("Modbus web configuration failed startup. "))
-
     if havewamp:
         try:
             WC.SetServer(pyroserver)
@@ -619,6 +601,7 @@
 pyroserver.Quit()
 pyro_thread.join()
 
+plcobj = runtime.GetPLCObjectSingleton()
 plcobj.StopPLC()
 plcobj.UnLoadPLC()
 
--- a/ConfigTreeNode.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/ConfigTreeNode.py	Fri Dec 25 17:12:02 2020 +0000
@@ -678,6 +678,3 @@
 
         raise UserAddressedException(message)
 
-class UserAddressedException(Exception):
-    pass
-
--- a/POULibrary.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/POULibrary.py	Fri Dec 25 17:12:02 2020 +0000
@@ -26,6 +26,10 @@
 from __future__ import absolute_import
 from weakref import ref
 
+# Exception type for problems that user has to take action in order to fix
+class UserAddressedException(Exception):
+    pass
+
 
 class POULibrary(object):
     def __init__(self, CTR, LibName, TypeStack):
@@ -59,6 +63,11 @@
         # Pure python or IEC libs doesn't produce C code
         return ((""), [], False), ""
 
+    def FatalError(self, message):
+        """ Raise an exception that will trigger error message intended to 
+            the user, but without backtrace since it is not a software error """
+
+        raise UserAddressedException(message)
 
 def SimplePOULibraryFactory(path):
     class SimplePOULibrary(POULibrary):
--- a/ProjectController.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/ProjectController.py	Fri Dec 25 17:12:02 2020 +0000
@@ -63,7 +63,8 @@
 import targets
 from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer
 from runtime import PlcStatus
-from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage, UserAddressedException
+from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
+from POULibrary import UserAddressedException
 
 base_folder = paths.AbsParentDir(__file__)
 
@@ -1441,7 +1442,8 @@
         PlcStatus.Stopped:      {"_Run": True,
                                  "_Transfer": True,
                                  "_Connect": False,
-                                 "_Disconnect": True},
+                                 "_Disconnect": True,
+                                 "_Repair": True},
         PlcStatus.Empty:        {"_Transfer": True,
                                  "_Connect": False,
                                  "_Disconnect": True},
@@ -1978,6 +1980,13 @@
             "shown":      False,
         },
         {
+            "bitmap":    "Disconnect",
+            "name":    _("Disconnect"),
+            "tooltip": _("Disconnect from PLC"),
+            "method":   "_Disconnect",
+            "shown":      False,
+        },
+        {
             "bitmap":    "Repair",
             "name":    _("Repair"),
             "tooltip": _("Repair broken PLC"),
@@ -1985,13 +1994,6 @@
             "shown":      False,
         },
         {
-            "bitmap":    "Disconnect",
-            "name":    _("Disconnect"),
-            "tooltip": _("Disconnect from PLC"),
-            "method":   "_Disconnect",
-            "shown":      False,
-        },
-        {
             "bitmap":    "IDManager",
             "name":    _("ID Manager"),
             "tooltip": _("Manage secure connection identities"),
--- a/bacnet/bacnet.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/bacnet/bacnet.py	Fri Dec 25 17:12:02 2020 +0000
@@ -36,6 +36,7 @@
 from bacnet.BacnetSlaveEditor import ObjectProperties
 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
 from ConfigTreeNode import ConfigTreeNode
+import util.paths as paths
 
 base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
 base_folder = os.path.join(base_folder, "..")
@@ -775,5 +776,20 @@
         # fobject     = file object, already open'ed for read() !!
         #
         # extra_files -> files that will be downloaded to the PLC!
-        return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
+
+        websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
+        websettingcode = websettingfile.read()
+        websettingfile.close()
+
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        websettingcode = websettingcode % locals()
+
+        runtimefile_path = os.path.join(buildpath, "runtime_bacnet_websettings.py")
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write(websettingcode)
+        runtimefile.close()
+
+        return ([(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True,
+                ("runtime_bacnet_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+        )
         #return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True, ('extrafile1.txt', extra_file_handle)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/web_settings.py	Fri Dec 25 17:12:02 2020 +0000
@@ -0,0 +1,402 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+#
+# Copyright (C) 2020: Mario de Sousa
+#
+# See COPYING.Runtime file for copyrights details.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+import json
+import os
+import ctypes
+
+from formless import annotate, webform
+
+import runtime.NevowServer as NS
+
+
+# Will contain references to the C functions 
+# (implemented in beremiz/bacnet/runtime/server.c)
+# used to get/set the BACnet specific configuration paramters
+BacnetGetParamFuncs = {}
+BacnetSetParamFuncs = {}
+
+
+# Upon PLC load, this Dictionary is initialised with the BACnet configuration
+# hardcoded in the C file
+# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
+_BacnetDefaultConfiguration = None
+
+
+# Dictionary that contains the BACnet configuration currently being shown
+# on the web interface
+# This configuration will almost always be identical to the current
+# configuration in the PLC (i.e., the current state stored in the 
+# C variables in the .so file).
+# The configuration viewed on the web will only be different to the current 
+# configuration when the user edits the configuration, and when
+# the user asks to save the edited configuration but it contains an error.
+_BacnetWebviewConfiguration = None
+
+
+# Dictionary that stores the BACnet configuration currently stored in a file
+# Currently only used to decide whether or not to show the "Delete" button on the
+# web interface (only shown if _BacnetSavedConfiguration is not None)
+_BacnetSavedConfiguration = None
+
+
+# File to which the new BACnet configuration gets stored on the PLC
+# Note that the stored configuration is likely different to the
+# configuration hardcoded in C generated code (.so file), so
+# this file should be persistent across PLC reboots so we can
+# re-configure the PLC (change values of variables in .so file)
+# before it gets a chance to start running
+#
+#_BACnetConfFilename = None
+_BACnetConfFilename = os.path.join(WorkingDir, "bacnetconf.json")
+
+
+
+class BN_StrippedString(annotate.String):
+    def __init__(self, *args, **kwargs):
+        annotate.String.__init__(self, strip = True, *args, **kwargs)
+
+
+
+BACnet_parameters = [
+    #    param. name             label                                            ctype type      annotate type
+    # (C code var name)         (used on web interface)                          (C data type)    (web data type)
+    #                                                                                             (annotate.String,
+    #                                                                                              annotate.Integer, ...)
+    ("network_interface"      , _("Network Interface")                         , ctypes.c_char_p, BN_StrippedString),
+    ("port_number"            , _("UDP Port Number")                           , ctypes.c_char_p, BN_StrippedString),
+    ("comm_control_passwd"    , _("BACnet Communication Control Password")     , ctypes.c_char_p, annotate.String),
+    ("device_id"              , _("BACnet Device ID")                          , ctypes.c_int,    annotate.Integer),
+    ("device_name"            , _("BACnet Device Name")                        , ctypes.c_char_p, annotate.String),
+    ("device_location"        , _("BACnet Device Location")                    , ctypes.c_char_p, annotate.String),
+    ("device_description"     , _("BACnet Device Description")                 , ctypes.c_char_p, annotate.String),
+    ("device_appsoftware_ver" , _("BACnet Device Application Software Version"), ctypes.c_char_p, annotate.String)
+    ]
+
+
+
+
+
+
+def _CheckBacnetPortnumber(port_number):
+    """ check validity of the port number """
+    try:
+        portnum = int(port_number)
+        if (portnum < 0) or (portnum > 65535):
+           raise Exception
+    except Exception:    
+        return False
+        
+    return True    
+    
+
+
+def _CheckBacnetDeviceID(device_id):
+    """ 
+    # check validity of the Device ID 
+    # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
+    #       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
+    """
+    try:
+        devid = int(device_id)
+        if (devid < 0) or (devid > 4194302):
+            raise Exception
+    except Exception:    
+        return False
+        
+    return True    
+
+
+
+
+
+def _CheckBacnetConfiguration(BACnetConfig):
+    res = True    
+    res = res and _CheckBacnetPortnumber(BACnetConfig["port_number"])
+    res = res and _CheckBacnetDeviceID  (BACnetConfig["device_id"])
+    return res
+
+
+
+def _CheckBacnetWebConfiguration(BACnetConfig):
+    res = True
+    
+    # check the port number
+    if not _CheckBacnetPortnumber(BACnetConfig["port_number"]):
+        raise annotate.ValidateError(
+            {"port_number": "Invalid port number: " + str(BACnetConfig["port_number"])},
+            _("BACnet configuration error:"))
+        res = False
+    
+    if not _CheckBacnetDeviceID(BACnetConfig["device_id"]):
+        raise annotate.ValidateError(
+            {"device_id": "Invalid device ID: " + str(BACnetConfig["device_id"])},
+            _("BACnet configuration error:"))
+        res = False
+        
+    return res
+
+
+
+
+
+
+def _SetBacnetSavedConfiguration(BACnetConfig):
+    """ Stores in a file a dictionary containing the BACnet parameter configuration """
+    global _BacnetSavedConfiguration
+
+    if BACnetConfig == _BacnetDefaultConfiguration :
+        _DelBacnetSavedConfiguration()
+        _BacnetSavedConfiguration = None
+    else :
+        with open(os.path.realpath(_BACnetConfFilename), 'w') as f:
+            json.dump(BACnetConfig, f, sort_keys=True, indent=4)
+        _BacnetSavedConfiguration = BACnetConfig
+
+
+def _DelBacnetSavedConfiguration():
+    """ Deletes the file cotaining the persistent BACnet configuration """
+    if os.path.exists(_BACnetConfFilename):
+        os.remove(_BACnetConfFilename)
+
+
+def _GetBacnetSavedConfiguration():
+    """
+    # Returns a dictionary containing the BACnet parameter configuration
+    # that was last saved to file. If no file exists, then return None
+    """
+    try:
+        #if os.path.isfile(_BACnetConfFilename):
+        saved_config = json.load(open(_BACnetConfFilename))
+    except Exception:    
+        return None
+
+    if _CheckBacnetConfiguration(saved_config):
+        return saved_config
+    else:
+        return None
+
+
+def _GetBacnetPLCConfiguration():
+    """
+    # Returns a dictionary containing the current BACnet parameter configuration
+    # stored in the C variables in the loaded PLC (.so file)
+    """
+    current_config = {}
+    for par_name, x1, x2, x3 in BACnet_parameters:
+        value = BacnetGetParamFuncs[par_name]()
+        if value is not None:
+            current_config[par_name] = value
+    
+    return current_config
+
+
+def _SetBacnetPLCConfiguration(BACnetConfig):
+    """
+    # Stores the BACnet parameter configuration into the
+    # the C variables in the loaded PLC (.so file)
+    """
+    for par_name in BACnetConfig:
+        value = BACnetConfig[par_name]
+        #PLCObject.LogMessage("BACnet web server extension::_SetBacnetPLCConfiguration()  Setting "
+        #                       + par_name + " to " + str(value) )
+        if value is not None:
+            BacnetSetParamFuncs[par_name](value)
+    # update the configuration shown on the web interface
+    global _BacnetWebviewConfiguration 
+    _BacnetWebviewConfiguration = _GetBacnetPLCConfiguration()
+
+
+
+def _GetBacnetWebviewConfigurationValue(ctx, argument):
+    """
+    # Callback function, called by the web interface (NevowServer.py)
+    # to fill in the default value of each parameter
+    """
+    try:
+        return _BacnetWebviewConfiguration[argument.name]
+    except Exception:
+        return ""
+
+
+# The configuration of the web form used to see/edit the BACnet parameters
+webFormInterface = [(name, web_dtype (label=web_label, default=_GetBacnetWebviewConfigurationValue)) 
+                    for name, web_label, c_dtype, web_dtype in BACnet_parameters]
+
+
+def OnBacnetButtonSave(**kwargs):
+    """
+    # Function called when user clicks 'Save' button in web interface
+    # The function will configure the BACnet plugin in the PLC with the values
+    # specified in the web interface. However, values must be validated first!
+    """
+
+    #PLCObject.LogMessage("BACnet web server extension::OnBacnetButtonSave()  Called")
+    
+    newConfig = {}
+    for par_name, x1, x2, x3 in BACnet_parameters:
+        value = kwargs.get(par_name, None)
+        if value is not None:
+            newConfig[par_name] = value
+
+    
+    # First check if configuration is OK.
+    if not _CheckBacnetWebConfiguration(newConfig):
+        return
+
+    # store to file the new configuration so that 
+    # we can recoup the configuration the next time the PLC
+    # has a cold start (i.e. when Beremiz_service.py is retarted)
+    _SetBacnetSavedConfiguration(newConfig)
+
+    # Configure PLC with the current BACnet parameters
+    _SetBacnetPLCConfiguration(newConfig)
+
+
+
+def OnBacnetButtonReset(**kwargs):
+    """
+    # Function called when user clicks 'Delete' button in web interface
+    # The function will delete the file containing the persistent
+    # BACnet configution
+    """
+
+    # Delete the file
+    _DelBacnetSavedConfiguration()
+    # Set the current configuration to the default (hardcoded in C)
+    _SetBacnetPLCConfiguration(_BacnetDefaultConfiguration)
+    # Reset global variable
+    global _BacnetSavedConfiguration
+    _BacnetSavedConfiguration = None
+
+
+
+# location_str is replaced by extension's value in CTNGenerateC call
+def _runtime_bacnet_websettings_%(location_str)s_init():
+    """
+    # Callback function, called (by PLCObject.py) when a new PLC program
+    # (i.e. XXX.so file) is transfered to the PLC runtime
+    # and oaded into memory
+    """
+
+    #PLCObject.LogMessage("BACnet web server extension::OnLoadPLC() Called...")
+
+    if PLCObject.PLClibraryHandle is None:
+        # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
+        # Hmm... This shold never occur!! 
+        return  
+
+    # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
+    for name, web_label, c_dtype, web_dtype in BACnet_parameters:
+        # location_str is replaced by extension's value in CTNGenerateC call
+        GetParamFuncName = "__bacnet_%(location_str)s_get_ConfigParam_" + name
+        SetParamFuncName = "__bacnet_%(location_str)s_set_ConfigParam_" + name
+        
+        # XXX TODO : stop reading from PLC .so file. This code is template code
+        #            that can use modbus extension build data
+        BacnetGetParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, GetParamFuncName)
+        BacnetGetParamFuncs[name].restype  = c_dtype
+        BacnetGetParamFuncs[name].argtypes = None
+        
+        BacnetSetParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, SetParamFuncName)
+        BacnetSetParamFuncs[name].restype  = None
+        BacnetSetParamFuncs[name].argtypes = [c_dtype]
+
+    # Default configuration is the configuration done in Beremiz IDE
+    # whose parameters get hardcoded into C, and compiled into the .so file
+    # We read the default configuration from the .so file before the values
+    # get changed by the user using the web server, or by the call (further on)
+    # to _SetBacnetPLCConfiguration(BacnetSavedConfiguration)
+    global _BacnetDefaultConfiguration 
+    _BacnetDefaultConfiguration = _GetBacnetPLCConfiguration()
+    
+    # Show the current PLC configuration on the web interface        
+    global _BacnetWebviewConfiguration
+    _BacnetWebviewConfiguration = _GetBacnetPLCConfiguration()
+ 
+    # Read from file the last used configuration, which is likely
+    # different to the hardcoded configuration.
+    # We Reset the current configuration (i.e., the config stored in the 
+    # variables of .so file) to this saved configuration
+    # so the PLC will start off with this saved configuration instead
+    # of the hardcoded (in Beremiz C generated code) configuration values.
+    #
+    # Note that _SetBacnetPLCConfiguration() will also update 
+    # _BacnetWebviewConfiguration , if necessary.
+    global _BacnetSavedConfiguration
+    _BacnetSavedConfiguration  = _GetBacnetSavedConfiguration()
+    if _BacnetSavedConfiguration is not None:
+        if _CheckBacnetConfiguration(_BacnetSavedConfiguration):
+            _SetBacnetPLCConfiguration(_BacnetSavedConfiguration)
+            
+    WebSettings = NS.newExtensionSetting("BACnet extension", "bacnet_token")
+
+    # Configure the web interface to include the BACnet config parameters
+    WebSettings.addSettings(
+        "BACnetConfigParm",                # name
+        _("BACnet Configuration"),         # description
+        webFormInterface,                  # fields
+        _("Apply"),  # button label
+        OnBacnetButtonSave)                      # callback    
+
+    # Add the Delete button to the web interface
+    WebSettings.addSettings(
+        "BACnetConfigDelSaved",                   # name
+        _("BACnet Configuration"),                # description
+        [ ("status",
+           annotate.String(label=_("Current state"),
+                           immutable=True,
+                           default=lambda *k:getBacnetConfigStatus())),
+        ],                                       # fields  (empty, no parameters required!)
+        _("Reset"), # button label
+        OnBacnetButtonReset) 
+
+
+
+def getBacnetConfigStatus():
+    if _BacnetWebviewConfiguration == _BacnetDefaultConfiguration :
+        return "Unchanged"
+    return "Modified"
+
+
+# location_str is replaced by extension's value in CTNGenerateC call
+def _runtime_bacnet_websettings_%(location_str)s_cleanup():
+    """
+    # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
+    """
+
+    #PLCObject.LogMessage("BACnet web server extension::OnUnLoadPLC() Called...")
+    
+    NS.removeExtensionSetting("bacnet_token")
+    
+    BacnetGetParamFuncs = {}
+    BacnetSetParamFuncs = {}
+    _BacnetWebviewConfiguration = None
+    _BacnetSavedConfiguration   = None
+
+
+
+
--- a/etherlab/CommonEtherCATFunction.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/CommonEtherCATFunction.py	Fri Dec 25 17:12:02 2020 +0000
@@ -50,10 +50,9 @@
                 return name.getcontent()
     return default
 
-# --------------------------------------------------
+#--------------------------------------------------
 #         Remote Exec Etherlab Commands
-# --------------------------------------------------
-
+#--------------------------------------------------
 
 # --------------------- for master ---------------------------
 MASTER_STATE = """
@@ -84,18 +83,21 @@
 returnVal = result
 """
 
-# ethercat sdos -p (slave position)
-SLAVE_SDO = """
+# ethercat upload -p (slave position) -t (type) (index) (sub index)
+SDO_UPLOAD = """
 import commands
-result = commands.getoutput("ethercat sdos -p %d")
-returnVal =result
-"""
-
-# ethercat upload -p (slave position) (main index) (sub index)
-GET_SLOW_SDO = """
-import commands
-result = commands.getoutput("ethercat upload -p %d %s %s")
-returnVal =result
+sdo_data = []
+input_data = "%s"
+slave_pos = %d
+command_string = ""
+for sdo_token in input_data.split(","):  
+    if len(sdo_token) > 1:
+        sdo_token = sdo_token.strip()
+        type, idx, subidx = sdo_token.split(" ")
+        command_string = "ethercat upload -p " + str(slave_pos) + " -t " + type + " " + idx + " " + subidx
+        result = commands.getoutput(command_string)
+        sdo_data.append(result)
+returnVal =sdo_data
 """
 
 # ethercat download -p (slave position) (main index) (sub index) (value)
@@ -119,6 +121,25 @@
 returnVal =result
 """
 
+# ethercat reg_read -p (slave position) (address) (size)
+MULTI_REG_READ = """ 
+import commands
+output = []
+addr, size = range(2)
+slave_num = %d 
+reg_info_str = "%s"
+reg_info_list = reg_info_str.split("|")
+for slave_idx in range(slave_num):
+    for reg_info in reg_info_list:
+        param = reg_info.split(",")
+        result = commands.getoutput("ethercat reg_read -p "
+                                    + str(slave_idx) + " "
+                                    + param[addr] + " "
+                                    + param[size])
+        output.append(str(slave_idx) + "," + param[addr] + "," + result)
+returnVal = output
+"""
+
 # ethercat sii_write -p (slave position) - (contents)
 SII_WRITE = """
 import subprocess
@@ -143,6 +164,12 @@
 returnVal =result
 """
 
+# ethercat pdos
+PDOS = """
+import commands
+result = commands.getoutput("ethercat pdos -p 0")
+returnVal =result  
+"""
 
 # --------------------------------------------------
 #    Common Method For EtherCAT Management
@@ -152,14 +179,40 @@
     # ----- Data Structure for ethercat management ----
     SlaveState = ""
 
+    # SDO base data type for Ethercatmaster
+    BaseDataTypes = {
+            "bool": ["BOOLEAN", "BOOL", "BIT"],
+            "uint8": ["BYTE", "USINT", "BIT1", "BIT2", "BIT3", "BIT4", "BIT5", "BIT6",
+                      "BIT7", "BIT8", "BITARR8", "UNSIGNED8"],
+            "uint16": ["BITARR16", "UNSIGNED16", "UINT"],
+            "uint32": ["BITARR32", "UNSIGNED24", "UINT24", "UNSIGNED32", "UDINT"],
+            "uint64": ["UNSINED40", "UINT40", "UNSIGNED48", "UINT48", "UNSIGNED56", 
+                       "UINT56", "UNSIGNED64", "ULINT"],
+            "int8": ["INTEGER8", "SINT"],
+            "int16": ["INTEGER16", "INT"],
+            "int32": ["INTEGER24", "INT24", "INTEGER32", "DINT"],
+            "int64": ["INTEGER40", "INT40", "INTEGER48", "INT48", "INTEGER56", "INT56",
+                      "INTEGER64", "LINT"],
+            "float": ["REAL", "REAL32"],
+            "double": ["LREAL", "REAL64"],
+            "string": ["VISUBLE_STRING", "STRING(n)"],
+            "octet_string": ["OCTET_STRING"],
+            "unicode_string": ["UNICODE_STRING"]
+            }
+    
     # category of SDO data
     DatatypeDescription, CommunicationObject, ManufacturerSpecific, \
         ProfileSpecific, Reserved, AllSDOData = range(6)
 
-    # store the execution result of "ethercat sdos" command into SaveSDOData.
-    SaveSDOData = []
-
-    # Flags for checking "write" permission of OD entries
+    # SDO data informations: index, sub-index, type, bit size, category-name 
+    SDOVariables = []
+    SDOSubEntryData = []
+    
+    # defalut value of SDO data in XML
+    # Not Used
+    DefaultValueDic = {}
+    
+    # Flags for checking "write" permission of OD entries 
     CheckPREOP = False
     CheckSAFEOP = False
     CheckOP = False
@@ -241,15 +294,6 @@
     # -------------------------------------------------------------------------------
     #                        Used SDO Management
     # -------------------------------------------------------------------------------
-    def GetSlaveSDOFromSlave(self):
-        """
-        Get SDO objects information of current slave using "ethercat sdos -p %d" command.
-        Command example : "ethercat sdos -p 0"
-        @return return_val : execution results of "ethercat sdos" command (need to be parsed later)
-        """
-        _error, return_val = self.Controler.RemoteExec(SLAVE_SDO % (self.Controler.GetSlavePos()), return_val=None)
-        return return_val
-
     def SDODownload(self, data_type, idx, sub_idx, value):
         """
         Set an SDO object value to user-specified value using "ethercat download" command.
@@ -258,11 +302,14 @@
         @param idx : index of the SDO entry
         @param sub_idx : subindex of the SDO entry
         @param value : value of SDO entry
-        """
-        _error, _return_val = self.Controler.RemoteExec(
-            SDO_DOWNLOAD % (data_type, self.Controler.GetSlavePos(), idx, sub_idx, value),
-            return_val=None)
-
+        """ 
+        valid_type = self.GetValidDataType(data_type)
+        _error, return_val = self.Controler.RemoteExec(
+            SDO_DOWNLOAD%(valid_type, self.Controler.GetSlavePos(),
+                idx, sub_idx, value), return_val = None)
+        
+        return return_val
+    
     def BackupSDODataSet(self):
         """
         Back-up current SDO entry information to restore the SDO data
@@ -282,12 +329,316 @@
         for dummy in range(6):
             self.SaveSDOData.append([])
 
+    def GetAllSDOValuesFromSlave(self):
+        """
+        Get SDO values of All SDO entries.
+        @return return_val: list of result of "SDO_UPLOAD"
+        """
+        entry_infos = ""
+        alldata_idx = len(self.SDOVariables)
+        counter = 0
+        for category in self.SDOVariables:
+            counter +=1
+            # for avoid redundant repetition 
+            if counter == alldata_idx:
+                continue
+            
+            for entry in category:
+                valid_type = self.GetValidDataType(entry["type"])
+                for_command_string = "%s %s %s ," % \
+                        (valid_type, entry["idx"], entry["subIdx"])
+                entry_infos += for_command_string
+             
+        error, return_val = self.Controler.RemoteExec(SDO_UPLOAD%(entry_infos, self.Controler.GetSlavePos()), return_val = None)
+        
+        return return_val
+
+    def GetSDOValuesFromSlave(self, entries_info):
+        """
+        Get SDO values of some SDO entries.
+        @param entries_info: dictionary of SDO entries that is wanted to know the value. 
+        @return return_val: list of result of "SDO_UPLOAD"
+        """
+        entry_infos = ""
+
+        entries_info_list = entries_info.items()
+        entries_info_list.sort()
+        
+        for (idx, subIdx), entry in entries_info_list:
+            valid_type = self.GetValidDataType(entry["type"])
+            for_command_string = "%s %s %s ," % \
+                        (valid_type, str(idx), str(subIdx))
+            entry_infos += for_command_string
+
+        error, return_val = self.Controler.RemoteExec(SDO_UPLOAD%(entry_infos, self.Controler.GetSlavePos()), return_val = None)
+        
+        return return_val
+
+    def ExtractObjects(self):
+        """
+        Extract object type items from imported ESI xml.
+        And they are stuctured as dictionary.
+        @return objects: dictionary of objects
+        """
+        objects = {}
+
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+         
+        if device is not None :
+            for dictionary in device.GetProfileDictionaries():
+                dictionary.load()
+                for object in dictionary.getObjects().getObject():
+                    object_index = ExtractHexDecValue(object.getIndex().getcontent())
+                    objects[(object_index)] = object
+        
+        return objects
+
+    def ExtractAllDataTypes(self):
+        """
+        Extract all data types from imported ESI xml.
+        @return dataTypes: dictionary of datatypes 
+        """
+        dataTypes = {}
+        
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+
+        for dictionary in device.GetProfileDictionaries():
+            dictionary.load()
+        
+            datatypes = dictionary.getDataTypes()
+            if datatypes is not None:
+
+                for datatype in datatypes.getDataType():
+                    dataTypes[datatype.getName()] = datatype
+        return dataTypes
+    
+    def IsBaseDataType(self, datatype):
+        """
+        Check if the datatype is a base data type.
+        @return baseTypeFlag: true if datatype is a base data type, unless false
+        """
+        baseTypeFlag = False
+        for baseDataTypeList in self.BaseDataTypes.values():
+            if datatype in baseDataTypeList:
+                baseTypeFlag = True
+                break
+        return baseTypeFlag
+
+    def GetBaseDataType(self, datatype):
+        """
+        Get a base data type corresponding the datatype.
+        @param datatype: Some data type (string format)
+        @return base data type
+        """
+        if self.IsBaseDataType(datatype):
+            return datatype
+        elif not datatype.find("STRING") == -1:
+            return datatype
+        else:
+            datatypes = self.ExtractAllDataTypes()
+            base_datatype = datatypes[datatype].getBaseType()
+            return self.GetBaseDataType(base_datatype)
+
+    def GetValidDataType(self, datatype):
+        """
+        Convert the datatype into a data type that is possible to download/upload 
+        in etherlab master stack.
+        @param datatype: Some data type (string format)
+        @return base_type: vaild data type
+        """
+        base_type = self.GetBaseDataType(datatype)
+
+        if re.match("STRING\([0-9]*\)", datatype) is not None:
+            return "string"
+        else:
+            for key, value in self.BaseDataTypes.items():
+                if base_type in value:
+                    return key
+        return base_type 
+
+    # Not Used 
+    def GetAllEntriesList(self):
+        """
+        Get All entries information that includes index, sub-index, name,
+        type, bit size, PDO mapping, and default value.
+        @return self.entries: dictionary of entry
+        """
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+        self.entries = device.GetEntriesList()
+        datatypes = self.ExtractAllDataTypes()
+        objects = self.ExtractObjects()
+        entries_list = self.entries.items()
+        entries_list.sort()
+
+        # append sub entries
+        for (index, subidx), entry in entries_list:
+            # entry_* is string type
+            entry_type = entry["Type"]
+            entry_index = entry["Index"]
+            
+            try:
+                object_info = objects[index].getInfo()
+            except:
+                continue
+            
+            if object_info is not None:
+                obj_content = object_info.getcontent()
+            
+            typeinfo = datatypes.get(entry_type, None)
+            bitsize = typeinfo.getBitSize()
+            type_content = typeinfo.getcontent()
+           
+            # ArrayInfo type
+            if type_content is not None and type_content["name"] == "ArrayInfo":
+                for arrayinfo in type_content["value"]:
+                    element_num = arrayinfo.getElements()
+                    first_subidx = arrayinfo.getLBound()
+                    for offset in range(element_num):
+                        new_subidx = int(first_subidx) + offset
+                        entry_subidx = hex(new_subidx)
+                        if obj_content["value"][new_subidx]["name"] == "SubItem":
+                            subitem = obj_content["value"][new_subidx]["value"]
+                            subname = subitem[new_subidx].getName()
+                        if subname is not None:
+                            entry_name = "%s - %s" % \
+                                    (ExtractName(objects[index].getName()), subname)
+                        else:
+                            entry_name = ExtractName(objects[index].getName()) 
+                        self.entries[(index, new_subidx)] = {
+                                "Index": entry_index,
+                                "SubIndex": entry_subidx,
+                                "Name": entry_name,
+                                "Type": typeinfo.getBaseType(),
+                                "BitSize": str(bitsize/element_num),
+                                "Access": entry["Access"],
+                                "PDOMapping": entry["PDOMapping"]}
+                        try:
+                            value_info = subitem[new_subidx].getInfo().getcontent()\
+                                                            ["value"][0]["value"][0]
+                            self.AppendDefaultValue(index, new_subidx, value_info)
+                        except:
+                            pass
+
+                try:
+                    value_info = subitem[subidx].getInfo().getcontent()\
+                                                            ["value"][0]["value"][0]
+                    self.AppendDefaultValue(index, subidx, value_info)
+                except:
+                    pass
+            
+            # EnumInfo type
+            elif type_content is not None and type_content["name"] == "EnumInfo":
+                self.entries[(index, subidx)]["EnumInfo"] = {}
+                
+                for enuminfo in type_content["value"]:
+                    text = enuminfo.getText()
+                    enum = enuminfo.getEnum()
+                    self.entries[(index, subidx)]["EnumInfo"][str(enum)] = text
+
+                self.entries[(index, subidx)]["DefaultValue"] = "0x00" 
+            
+            # another types
+            else:
+                if subidx == 0x00:
+                    tmp_subidx = 0x00
+
+                try:
+                    if obj_content["value"][tmp_subidx]["name"] == "SubItem":
+                        sub_name = entry["Name"].split(" - ")[1]
+                        for num in range(len(obj_content["value"])):
+                            if sub_name == \
+                                    obj_content["value"][num]["value"][num].getName():
+                                subitem_content = obj_content["value"][tmp_subidx]\
+                                                             ["value"][tmp_subidx]
+                                value_info = subitem_content.getInfo().getcontent()\
+                                                             ["value"][0]["value"][0]
+                                tmp_subidx += 1
+                                break
+                            else:
+                                value_info = None
+                        
+                    else:
+                        value_info = \
+                                obj_content["value"][tmp_subidx]["value"][tmp_subidx]
+
+                    self.AppendDefaultValue(index, subidx, value_info)
+
+                except:
+                    pass
+
+        return self.entries
+                   
+    # Not Used  
+    def AppendDefaultValue(self, index, subidx, value_info=None):
+        """
+        Get the default value from the ESI xml
+        @param index: entry index
+        @param subidx: entry sub index
+        @param value_info: dictionary of infomation about default value
+
+        """
+        # there is not default value.
+        if value_info == None:
+            return
+
+        raw_value = value_info["value"]
+        
+        # default value is hex binary type.
+        if value_info["name"]  == "DefaultData":
+            raw_value_bit = list(hex(raw_value).split("0x")[1])
+             
+            datatype = self.GetValidDataType(self.entries[(index, subidx)]["Type"])
+            if datatype is "string" or datatype is "octet_string":
+
+                if "L" in raw_value_bit:
+                    raw_value_bit.remove("L")
+
+                default_value = "".join(raw_value_bit).decode("hex")
+           
+            elif datatype is "unicode_string":
+                default_value = "".join(raw_value_bit).decode("hex").\
+                                                           decode("utf-8")
+            
+            else:   
+                bit_num = len(raw_value_bit)
+                # padding 
+                if not bit_num%2 == 0:
+                    raw_value_bit.insert(0, "0")
+
+                default_value_bit = []
+            
+                # little endian -> big endian
+                for num in range(bit_num):
+                    if num%2 == 0:
+                        default_value_bit.insert(0, raw_value_bit[num])
+                        default_value_bit.insert(1, raw_value_bit[num+1])
+                
+                default_value = "0x%s" % "".join(default_value_bit)
+
+        # default value is string type.
+        # this case is not tested yet.
+        elif value_info["name"] == "DefaultString":
+            default_value = raw_value
+
+        # default value is Hex or Dec type.
+        elif value_info["name"] == "DefaultValue":
+            default_value = "0x" + hex(ExtractHexDecValue(raw_value))
+
+        self.entries[(index, subidx)]["DefaultValue"] = default_value
+
     # -------------------------------------------------------------------------------
     #                        Used PDO Monitoring
     # -------------------------------------------------------------------------------
     def RequestPDOInfo(self):
         """
-        Load slave information from RootClass (XML data) and parse the information (calling SlavePDOData() method).
+        Load slave information from RootClass (XML data) and parse the information 
+        (calling SlavePDOData() method).
         """
         # Load slave information from ESI XML file (def EthercatMaster.py)
         slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
@@ -314,6 +665,13 @@
             pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
             entries = pdo.getEntry()
             pdo_name = ExtractName(pdo.getName())
+            excludes = pdo.getExclude()
+            exclude_list = []
+            Sm = pdo.getSm()
+
+            if excludes :
+                for exclude in excludes:
+                    exclude_list.append(ExtractHexDecValue(exclude.getcontent()))
 
             # Initialize entry number count
             count = 0
@@ -329,13 +687,15 @@
                         "entry_index": index,
                         "subindex": subindex,
                         "name": ExtractName(entry.getName()),
-                        "bitlen": entry.getBitLen(),
-                        "type": entry.getDataType().getcontent()
+                        "bitlen": entry.getBitLen()
                     }
+                    if entry.getDataType() is not None:
+                        entry_infos["type"] = entry.getDataType().getcontent()
                     self.TxPDOInfo.append(entry_infos)
                     count += 1
 
-            categorys = {"pdo_index": pdo_index, "name": pdo_name, "number_of_entry": count}
+            categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "sm" : Sm,
+                         "number_of_entry" : count, "exclude_list" : exclude_list}  
             self.TxPDOCategory.append(categorys)
 
         # Parsing RxPDO entries
@@ -344,6 +704,13 @@
             pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
             entries = pdo.getEntry()
             pdo_name = ExtractName(pdo.getName())
+            excludes = pdo.getExclude()
+            exclude_list = []
+            Sm = pdo.getSm()
+
+            if excludes :
+                for exclude in excludes:
+                    exclude_list.append(ExtractHexDecValue(exclude.getcontent()))
 
             # Initialize entry number count
             count = 0
@@ -359,14 +726,16 @@
                         "entry_index": index,
                         "subindex": subindex,
                         "name": ExtractName(entry.getName()),
-                        "bitlen": str(entry.getBitLen()),
-                        "type": entry.getDataType().getcontent()
+                        "bitlen": str(entry.getBitLen())
                     }
+                    if entry.getDataType() is not None:
+                        entry_infos["type"] = entry.getDataType().getcontent()
                     self.RxPDOInfo.append(entry_infos)
                     count += 1
 
-            categorys = {"pdo_index": pdo_index, "name": pdo_name, "number_of_entry": count}
-            self.RxPDOCategory.append(categorys)
+            categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "sm" : Sm,
+                         "number_of_entry" : count, "exclude_list" : exclude_list}
+            self.RxPDOCategory.append(categorys) 
 
     def GetTxPDOCategory(self):
         """
@@ -404,10 +773,10 @@
         """
         Initialize PDO management data structure.
         """
-        self.TxPDOInfos = []
-        self.TxPDOCategorys = []
-        self.RxPDOInfos = []
-        self.RxPDOCategorys = []
+        self.TxPDOInfo = []
+        self.TxPDOCategory = []
+        self.RxPDOInfo = []
+        self.RxPDOCategory = []
 
     # -------------------------------------------------------------------------------
     #                        Used EEPROM Management
@@ -496,11 +865,11 @@
                         smartview_infos[cfg] = str(int(bootstrap_data[4*iter+2:4*(iter+1)]+bootstrap_data[4*iter:4*iter+2], 16))
 
             # get protocol (profile) types supported by mailbox; <Device>-<Mailbox>
-            mb = device.getMailbox()
-            if mb is not None:
-                for mailbox_protocol in mailbox_protocols:
-                    if getattr(mb, "get%s" % mailbox_protocol)() is not None:
-                        smartview_infos["supported_mailbox"] += "%s,  " % mailbox_protocol
+            with device.getMailbox() as mb:
+                if mb is not None:
+                    for mailbox_protocol in mailbox_protocols:
+                        if getattr(mb, "get%s" % mailbox_protocol)() is not None:
+                            smartview_infos["supported_mailbox"] += "%s,  " % mailbox_protocol
             smartview_infos["supported_mailbox"] = smartview_infos["supported_mailbox"].strip(", ")
 
             # get standard configuration of mailbox; <Device>-<Sm>
@@ -515,24 +884,24 @@
                     pass
 
             # get device identity from <Device>-<Type>
-            #  vendor ID; by default, pre-defined value in self.ModulesLibrary
-            #             if device type in 'vendor' item equals to actual slave device type, set 'vendor_id' to vendor ID.
+            # vendor ID; by default, pre-defined value in self.ModulesLibrary
+            # if device type in 'vendor' item equals to actual slave device type, set 'vendor_id' to vendor ID.
             for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems():
                 for available_device in vendor["groups"][vendor["groups"].keys()[0]]["devices"]:
                     if available_device[0] == type_infos["device_type"]:
                         smartview_infos["vendor_id"] = "0x" + "{:0>8x}".format(vendor_id)
 
-            #  product code;
+            # product code;
             if device.getType().getProductCode() is not None:
                 product_code = device.getType().getProductCode()
                 smartview_infos["product_code"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(product_code))
 
-            #  revision number;
+            # revision number;
             if device.getType().getRevisionNo() is not None:
                 revision_no = device.getType().getRevisionNo()
                 smartview_infos["revision_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(revision_no))
 
-            #  serial number;
+            # serial number;
             if device.getType().getSerialNo() is not None:
                 serial_no = device.getType().getSerialNo()
                 smartview_infos["serial_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(serial_no))
@@ -548,14 +917,14 @@
         @param decnum : decimal value
         @return hex_data : hexadecimal representation of input value in decimal
         """
-        value = "%x" % decnum
+        value = "%x" % int(decnum, 16)
         value_len = len(value)
         if (value_len % 2) == 0:
             hex_len = value_len
         else:
             hex_len = (value_len // 2) * 2 + 2
 
-        hex_data = ("{:0>"+str(hex_len)+"x}").format(decnum)
+        hex_data = ("{:0>"+str(hex_len)+"x}").format(int(decnum, 16))
 
         return hex_data
 
@@ -750,7 +1119,7 @@
             data = ""
             for eeprom_element in device.getEeprom().getcontent():
                 if eeprom_element["name"] == "BootStrap":
-                    data = "{:0>16x}".format(eeprom_element)
+                    data = "{:0>16x}".format(int(eeprom_element,16))
             eeprom += self.GenerateEEPROMList(data, 0, 16)
 
             # get Standard Mailbox for EEPROM offset 0x0030-0x0037; <Device>-<sm>
@@ -794,11 +1163,11 @@
 
             # get supported mailbox protocols for EEPROM offset 0x0038-0x0039;
             data = 0
-            mb = device.getMailbox()
-            if mb is not None:
-                for bit, mbprot in enumerate(mailbox_protocols):
-                    if getattr(mb, "get%s" % mbprot)() is not None:
-                        data += 1 << bit
+            with device.getMailbox() as mb:
+                if mb is not None:
+                    for bit, mbprot in enumerate(mailbox_protocols):
+                        if getattr(mb, "get%s" % mbprot)() is not None:
+                            data += 1 << bit
             data = "{:0>4x}".format(data)
             eeprom.append(data[2:4])
             eeprom.append(data[0:2])
@@ -808,11 +1177,12 @@
                 eeprom.append("00")
 
             # get EEPROM size for EEPROM offset 0x007c-0x007d;
+            # Modify by jblee because of update IDE module (minidom -> lxml) 
             data = ""
-            for eeprom_element in device.getEeprom().getcontent():
-                if eeprom_element["name"] == "ByteSize":
-                    eeprom_size = int(str(eeprom_element))
-                    data = "{:0>4x}".format(int(eeprom_element)//1024*8-1)
+            for eeprom_element in device.getEeprom().getchildren():
+                if eeprom_element.tag == "ByteSize":
+                    eeprom_size = int(objectify.fromstring(eeprom_element.tostring()).text)
+                    data = "{:0>4x}".format(eeprom_size/1024*8-1)
 
             if data == "":
                 eeprom.append("00")
@@ -822,7 +1192,7 @@
                 eeprom.append(data[0:2])
 
             # Version for EEPROM 0x007e-0x007f;
-            #  According to "EtherCAT Slave Device Description(V0.3.0)"
+            # According to "EtherCAT Slave Device Description(V0.3.0)"
             eeprom.append("01")
             eeprom.append("00")
 
@@ -915,7 +1285,7 @@
                     vendor_specific_data += "{:0>2x}".format(ord(data[character]))
         data = ""
 
-        #  element2-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<GroupType>
+        # element2-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<GroupType>
         data = device.getGroupType()
         if data is not None and isinstance(data, text):
             for vendor_spec_string in vendor_spec_strings:
@@ -978,6 +1348,7 @@
                 vendor_specific_data += "{:0>2x}".format(len(data))
                 for character in range(len(data)):
                     vendor_specific_data += "{:0>2x}".format(ord(data[character]))
+
         data = ""
 
         #  element4; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Name(LcId is "1033" or "1"?)>
@@ -1183,42 +1554,41 @@
         # word 3 : Physical Layer Port info. and CoE Details
         eeprom.append("01")  # Physical Layer Port info - assume 01
         #  CoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>
-        coe_details = 0
-        mb = device.getMailbox()
         coe_details = 1  # sdo enabled
-        if mb is not None:
-            coe = mb.getCoE()
-            if coe is not None:
-                for bit, flag in enumerate(["SdoInfo", "PdoAssign", "PdoConfig",
-                                            "PdoUpload", "CompleteAccess"]):
-                    if getattr(coe, "get%s" % flag)() is not None:
-                        coe_details += 1 << bit
-        eeprom.append("{:0>2x}".format(coe_details))
-
-        # word 4 : FoE Details and EoE Details
-        #  FoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<FoE>
-        if mb is not None and mb.getFoE() is not None:
-            eeprom.append("01")
-        else:
-            eeprom.append("00")
-        #  EoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<EoE>
-        if mb is not None and mb.getEoE() is not None:
-            eeprom.append("01")
-        else:
-            eeprom.append("00")
-
-        # word 5 : SoE Channels(reserved) and DS402 Channels
-        #  SoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<SoE>
-        if mb is not None and mb.getSoE() is not None:
-            eeprom.append("01")
-        else:
-            eeprom.append("00")
-        #  DS402Channels; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>: DS402Channels
-        ds402ch = False
-        if mb is not None:
-            coe = mb.getCoE()
-            if coe is not None:
-                ds402ch = coe.getDS402Channels()
+        with device.getMailbox() as mb
+            if mb is not None:
+                coe = mb.getCoE()
+                if coe is not None:
+                    for bit, flag in enumerate(["SdoInfo", "PdoAssign", "PdoConfig",
+                                                "PdoUpload", "CompleteAccess"]):
+                        if getattr(coe, "get%s" % flag)() is not None:
+                            coe_details += 1 << bit
+            eeprom.append("{:0>2x}".format(coe_details))
+
+            # word 4 : FoE Details and EoE Details
+            #  FoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<FoE>
+            if mb is not None and mb.getFoE() is not None:
+                eeprom.append("01")
+            else:
+                eeprom.append("00")
+            #  EoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<EoE>
+            if mb is not None and mb.getEoE() is not None:
+                eeprom.append("01")
+            else:
+                eeprom.append("00")
+
+            # word 5 : SoE Channels(reserved) and DS402 Channels
+            #  SoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<SoE>
+            if mb is not None and mb.getSoE() is not None:
+                eeprom.append("01")
+            else:
+                eeprom.append("00")
+            #  DS402Channels; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>: DS402Channels
+            ds402ch = False
+            if mb is not None:
+                coe = mb.getCoE()
+                if coe is not None:
+                    ds402ch = coe.getDS402Channels()
         eeprom.append("01" if ds402ch in [True, 1] else "00")
 
         # word 6 : SysmanClass(reserved) and Flags
@@ -1546,6 +1916,24 @@
             return_val=None)
         return return_val
 
+    def MultiRegRead(self, slave_num, reg_infos):
+        """
+        
+        @slave_num:
+        @param addr_info:
+        @return return_val: 
+        """
+        reg_info_str = ""
+        for reg_info in reg_infos:
+            reg_info_str = reg_info_str + "%s|" % reg_info
+        reg_info_str = reg_info_str.strip("|")
+
+        _error, return_val = self.Controler.RemoteExec(\
+            MULTI_REG_READ%(slave_num, reg_info_str),
+            return_val = None)
+        
+        return return_val
+    
     def RegWrite(self, address, data):
         """
         Write data to slave ESC register using "ethercat reg_write -p %d %s %s" command.
@@ -1567,6 +1955,40 @@
         _error, _return_val = self.Controler.RemoteExec(RESCAN % (self.Controler.GetSlavePos()), return_val=None)
 
     # -------------------------------------------------------------------------------
+    #                        Used DC Configuration
+    #-------------------------------------------------------------------------------
+    def LoadESIData(self):
+        return_data = []
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+        if device.getDc() is not None:
+            for OpMode in device.getDc().getOpMode():
+                temp_data = {
+                    "desc" : OpMode.getDesc() if OpMode.getDesc() is not None else "Unused",
+                    "assign_activate" : OpMode.getAssignActivate() \
+                        if OpMode.getAssignActivate() is not None else "#x0000",
+                    "cycletime_sync0" : OpMode.getCycleTimeSync0().getcontent() \
+                        if OpMode.getCycleTimeSync0() is not None else None,
+                    "shifttime_sync0" : OpMode.getShiftTimeSync0().getcontent() \
+                        if OpMode.getShiftTimeSync0() is not None else None,
+                    "cycletime_sync1" : OpMode.getShiftTimeSync1().getcontent() \
+                        if OpMode.getShiftTimeSync1() is not None else None,
+                    "shifttime_sync1" : OpMode.getShiftTimeSync1().getcontent() \
+                        if OpMode.getShiftTimeSync1() is not None else None
+                }
+
+                if OpMode.getCycleTimeSync0() is not None:
+                    temp_data["cycletime_sync0_factor"] = OpMode.getCycleTimeSync0().getFactor()
+
+                if OpMode.getCycleTimeSync1() is not None:
+                    temp_data["cycletime_sync1_factor"] = OpMode.getCycleTimeSync1().getFactor()
+
+                return_data.append(temp_data)
+
+        return return_data
+    
+    #-------------------------------------------------------------------------------
     #                        Common Use Methods
     # -------------------------------------------------------------------------------
     def CheckConnect(self, cyclic_flag):
--- a/etherlab/ConfigEditor.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/ConfigEditor.py	Fri Dec 25 17:12:02 2020 +0000
@@ -149,6 +149,9 @@
                     self.VariablesGrid.SetItemText(item, str(idx), 0)
                 else:
                     value = entry.get(colname, "")
+                    # add jblee
+                    if value is None:
+                        value = ""
                     if colname == "Access":
                         value = GetAccessValue(value, entry.get("PDOMapping", ""))
                     self.VariablesGrid.SetItemText(item, value, col)
@@ -617,9 +620,7 @@
         self.MasterStateEditor = wx.ScrolledWindow(prnt, style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL)
         self.MasterStateEditor.Bind(wx.EVT_SIZE, self.OnResize)
 
-        self.MasterStateEditor_Panel_Main_Sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
-        self.MasterStateEditor_Panel_Main_Sizer.AddGrowableCol(0)
-        self.MasterStateEditor_Panel_Main_Sizer.AddGrowableRow(0)
+        self.MasterStateEditor_Panel_Main_Sizer = wx.BoxSizer(wx.VERTICAL)
 
         self.MasterStateEditor_Panel = MasterStatePanelClass(self.MasterStateEditor, self.Controler)
 
@@ -628,6 +629,18 @@
         self.MasterStateEditor.SetSizer(self.MasterStateEditor_Panel_Main_Sizer)
         return self.MasterStateEditor
 
+    def OnResize2(self, event):
+        self.MasterStateEditor.GetBestSize()
+        xstart, ystart = self.MasterStateEditor.GetViewStart()
+        window_size = self.MasterStateEditor.GetClientSize()
+        maxx, maxy = self.MasterStateEditor.GetMinSize()
+        posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
+        posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
+        self.MasterStateEditor.Scroll(posx, posy)
+        self.MasterStateEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
+                maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
+        event.Skip()
+
     def _create_EthercatMasterEditor(self, prnt):
         self.EthercatMasterEditor = wx.ScrolledWindow(prnt,
                                                       style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL)
--- a/etherlab/EtherCATManagementEditor.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/EtherCATManagementEditor.py	Fri Dec 25 17:12:02 2020 +0000
@@ -71,11 +71,15 @@
         panels = [
             ("Slave State",        SlaveStatePanelClass, []),
             ("SDO Management",     SDOPanelClass, []),
-            ("PDO Monitoring",     PDOPanelClass, []),
+            ("PDO Mapping",     PDOPanelClass, [
+                    ("Rx PDO", RxPDOPanelClass),
+                    ("Tx PDO", TxPDOPanelClass)]),
+            ("MDP Setting",		MDPPanel, []),
             ("ESC Management",     EEPROMAccessPanel, [
-                ("Smart View", SlaveSiiSmartView),
-                ("Hex View", HexView)]),
-            ("Register Access",     RegisterAccessPanel, [])
+                    ("Smart View",  SlaveSiiSmartView),
+                    ("Hex View",    HexView)]),
+            ("Register Access",    RegisterAccessPanel, []),
+            ("DC Configuration",    DCConfigPanel, [])
         ]
         for pname, pclass, subs in panels:
             self.AddPage(pclass(self, self.Controler), pname)
@@ -230,7 +234,9 @@
         Event handler for slave state transition button click (Init, PreOP, SafeOP, OP button)
         @param event : wx.EVT_BUTTON object
         """
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        # Check whether beremiz connected or not.
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
             state_dic = ["INIT", "PREOP", "SAFEOP", "OP"]
 
@@ -255,7 +261,12 @@
         Timer event handler for periodic slave state monitoring (Default period: 1 sec = 1000 msec).
         @param event : wx.TIMER object
         """
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(True)
+        if self.IsShownOnScreen() is False:
+            return
+
+        # Check whether beremiz connected or not.
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = True)
         if check_connect_flag:
             returnVal = self.Controler.CommonMethod.GetSlaveStateFromSlave()
             line = returnVal.split("\n")
@@ -285,9 +296,15 @@
           - start slave state monitoring thread
         @param event : wx.EVT_BUTTON object
         """
-        self.SlaveStateThread = wx.Timer(self)
-        # set timer period (1000 ms)
-        self.SlaveStateThread.Start(1000)
+        # Check whether beremiz connected or not.
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+        if check_connect_flag:
+            self.SlaveStateThread = wx.Timer(self)
+            # set timer period (2000 ms)
+            self.SlaveStateThread.Start(2000)
+        else:
+            pass
 
     def CurrentStateThreadStop(self, event):
         """
@@ -318,143 +335,218 @@
 
         self.Controler = controler
 
+        self.SDOMonitorEntries = {}
+        #----------------------------- SDO Monitor -------------------------------#
+        self.RBList = ["ON","OFF"]
+        
+        self.SDOMonitorRB = wx.RadioBox(self, label=_("monitoring"),
+                                        choices=self.RBList, majorDimension=2)
+        
+        self.SDOMonitorRB.SetSelection(1)
+        self.Bind(wx.EVT_RADIOBOX, self.OnRadioBox, self.SDOMonitorRB)
+       
+        self.SDOMonitorGrid = wx.grid.Grid(self,size=wx.Size(850,150),style=wx.EXPAND
+                                                        |wx.ALIGN_CENTER_HORIZONTAL
+                                                        |wx.ALIGN_CENTER_VERTICAL) 
+        self.SDOMonitorGrid.Bind(gridlib.EVT_GRID_CELL_LEFT_DCLICK, 
+                                                    self.onMonitorGridDoubleClick)
+
+        #----------------------------- SDO Entries ----------------------------#
+        self.SDOUpdateBtn = wx.Button(self, label=_("update"))         
+        self.SDOUpdateBtn.Bind(wx.EVT_BUTTON, self.OnSDOUpdate)
+        
+        self.SDOTraceThread = None
+        self.SDOMonitoringFlag = False
+        self.SDOValuesList = []
+        # Default SDO Page Number
+        self.SDOPageNum = 2
+
+        #----------------------------- Sizer --------------------------------------#
         self.SDOManagementMainSizer = wx.BoxSizer(wx.VERTICAL)
-        self.SDOManagementInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
-
-        self.SDOUpdate = wx.Button(self, label=_('update'))
-        self.SDOUpdate.Bind(wx.EVT_BUTTON, self.SDOInfoUpdate)
-
-        self.CallSDONoteBook = SDONoteBook(self, controler=self.Controler)
-        self.SDOManagementInnerMainSizer.Add(self.SDOUpdate)
-        self.SDOManagementInnerMainSizer.Add(self.CallSDONoteBook, wx.ALL | wx.EXPAND)
-
-        self.SDOManagementMainSizer.Add(self.SDOManagementInnerMainSizer)
-
+        self.SDOInfoBox = wx.StaticBox(self, label=_("SDO Entries"))
+        self.SDOMonitorBox = wx.StaticBox(self, label=_("SDO Monitor"))
+
+        self.SDONoteBook = SDONoteBook(self, controler=self.Controler)
+        self.SDOInfoBoxSizer = wx.StaticBoxSizer(self.SDOInfoBox, orient=wx.VERTICAL)
+        self.SDOMonitorBoxSizer = wx.StaticBoxSizer(self.SDOMonitorBox, 
+                                                                    orient=wx.VERTICAL)
+        self.SDOInfoBoxSizer.Add(self.SDOUpdateBtn)
+        
+        self.SDOInfoBoxSizer.Add(self.SDONoteBook, wx.ALL|wx.EXPAND)
+        self.SDOMonitorBoxSizer.Add(self.SDOMonitorRB)
+        self.SDOMonitorBoxSizer.Add(self.SDOMonitorGrid)
+        self.SDOManagementMainSizer.AddMany([self.SDOInfoBoxSizer, 
+                                             self.SDOMonitorBoxSizer])
         self.SetSizer(self.SDOManagementMainSizer)
-
-    def SDOInfoUpdate(self, event):
-        """
-        Evenet handler for SDO "update" button.
-          - Load SDO data from current slave
-        @param event : wx.EVT_BUTTON object
-        """
-        self.Controler.CommonMethod.SaveSDOData = []
+        
+        #----------------------------- fill the contents --------------------------# 
+        #self.entries = self.Controler.CTNParent.CTNParent.GetEntriesList()
+
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+        self.entries = device.GetEntriesList()
+
+        self.Controler.CommonMethod.SDOVariables = []
+        self.Controler.CommonMethod.SDOSubEntryData = []
         self.Controler.CommonMethod.ClearSDODataSet()
-        self.SDOFlag = False
-
-        # Check whether beremiz connected or not.
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        self.SDOParserXML(self.entries)
+        self.SDONoteBook.CreateNoteBook()      
+        self.CreateSDOMonitorGrid()
+        self.Refresh()
+
+    def OnSDOUpdate(self, event):
+        SlavePos = self.Controler.GetSlavePos()
+        num = self.SDOPageNum - 1
+        if num < 0:
+            for i in range(len(self.Controler.CommonMethod.SDOVariables)):
+                data = self.Controler.GetCTRoot()._connector.GetSDOEntriesData(
+                           self.Controler.CommonMethod.SDOVariables[i], SlavePos)
+                self.Controler.CommonMethod.SDOVariables[i] = data
+        else :
+            SDOUploadEntries = self.Controler.CommonMethod.SDOVariables[num]        
+            data = self.Controler.GetCTRoot()._connector.GetSDOEntriesData(SDOUploadEntries, SlavePos)
+            self.Controler.CommonMethod.SDOVariables[num] = data
+
+        self.SDONoteBook.CreateNoteBook()      
+        self.Refresh()
+
+    def OnRadioBox(self, event):
+        """
+        There are two selections that are on and off.
+        If the on is selected, the monitor thread begins to run.
+        If the off is selected, the monitor thread gets off.
+        @param event: wx.EVT_RADIOBOX object 
+        """
+        on, off = range(2)
+
+        if event.GetInt() == on:
+            CheckThreadFlag = self.SDOMonitoringThreadOn()
+            if not CheckThreadFlag:
+                self.SDOMonitorRB.SetSelection(off)
+        elif event.GetInt() == off:
+            self.SDOMonitoringThreadOff()
+
+    def SDOMonitoringThreadOn(self):
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
-            self.SDOs = self.Controler.CommonMethod.GetSlaveSDOFromSlave()
-            # SDOFlag is "False", user click "Cancel" button
-            self.SDOFlag = self.SDOParser()
-
-            if self.SDOFlag:
-                self.CallSDONoteBook.CreateNoteBook()
-                self.Refresh()
-
-    def SDOParser(self):
-        """
-        Parse SDO data set that obtain "SDOInfoUpdate" Method
-        @return True or False
-        """
-
-        slaveSDO_progress = wx.ProgressDialog(_("Slave SDO Monitoring"), _("Now Uploading..."),
-                                              maximum=len(self.SDOs.splitlines()),
-                                              parent=self,
-                                              style=wx.PD_CAN_ABORT | wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME |
-                                              wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME |
-                                              wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
-
-        # If keep_going flag is False, SDOParser method is stop and return "False".
-        keep_going = True
-        count = 0
-
-        # SDO data example
-        # SDO 0x1000, "Device type"
-        # 0x1000:00,r-r-r-,uint32,32 bit,"Device type",0x00020192, 131474
-        for details_line in self.SDOs.splitlines():
-            count += 1
-            line_token = details_line.split("\"")
-            # len(line_token[2]) case : SDO 0x1000, "Device type"
-            if len(line_token[2]) == 0:
-                title_name = line_token[1]
-            # else case : 0x1000:00,r-r-r-,uint32,32 bit,"Device type",0x00020192, 131474
-            else:
-                # line_token = ['0x1000:00,r-r-r-,uint32,32 bit,', 'Device type', ',0x00020192, 131474']
-                token_head, name, token_tail = line_token
-
-                # token_head = ['0x1000:00', 'r-r-r-', 'uint32', '32 bit', '']
-                token_head = token_head.split(",")
-                ful_idx, access, type, size, _empty = token_head
-                # ful_idx.split(":") = ['0x1000', '00']
-                idx, sub_idx = ful_idx.split(":")
-
-                # token_tail = ['', '0x00020192', '131474']
-                token_tail = token_tail.split(",")
-                try:
-                    _empty, hex_val, _dec_val = token_tail
-
-                # SDO data is not return "dec value"
-                # line example :
-                # 0x1702:01,rwr-r-,uint32,32 bit," 1st mapping", ----
-                except Exception:
-                    _empty, hex_val = token_tail
-
-                name_after_check = self.StringTest(name)
-
-                # convert hex type
-                sub_idx = "0x" + sub_idx
-
-                if type == "octet_string":
-                    hex_val = ' ---- '
-
-                # SResult of SlaveSDO data parsing. (data type : dictionary)
-                self.Data = {'idx': idx.strip(), 'subIdx': sub_idx.strip(), 'access': access.strip(),
-                             'type': type.strip(), 'size': size.strip(),  'name': name_after_check.strip("\""),
-                             'value': hex_val.strip(), "category": title_name.strip("\"")}
-
-                category_divide_value = [0x1000, 0x2000, 0x6000, 0xa000, 0xffff]
-
-                for count in range(len(category_divide_value)):
-                    if int(idx, 0) < category_divide_value[count]:
-                        self.Controler.CommonMethod.SaveSDOData[count].append(self.Data)
-                        break
-
-                self.Controler.CommonMethod.SaveSDOData[self.AllSDOData].append(self.Data)
-
-            if count >= len(self.SDOs.splitlines()) // 2:
-                (keep_going, _skip) = slaveSDO_progress.Update(count, "Please waiting a moment!!")
-            else:
-                (keep_going, _skip) = slaveSDO_progress.Update(count)
-
-            # If user click "Cancel" loop suspend immediately
-            if not keep_going:
-                break
-
-        slaveSDO_progress.Destroy()
-        return keep_going
-
-    def StringTest(self, check_string):
-        """
-        Test value 'name' is alphanumeric
-        @param check_string : input data for check
-        @return result : output data after check
-        """
-        # string.printable is print this result
-        # '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
-        # !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c
-        allow_range = string.printable
-        result = check_string
-        for i in range(0, len(check_string)):
-            # string.isalnum() is check whether string is alphanumeric or not
-            if check_string[len(check_string)-1-i:len(check_string)-i] in allow_range:
-                result = check_string[:len(check_string) - i]
-                break
-        return result
-
-
-# -------------------------------------------------------------------------------
+            self.SetSDOTraceValues(self.SDOMonitorEntries)
+            self.Controler.GetCTRoot()._connector.GetSDOData()
+            self.SDOTraceThread = Thread(target=self.SDOMonitorThreadProc)
+            self.SDOMonitoringFlag = True
+            self.SDOTraceThread.start()
+        return check_connect_flag
+
+    def SDOMonitoringThreadOff(self):
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+        if check_connect_flag:
+            self.SDOMonitoringFlag = False
+            if self.SDOTraceThread is not None:
+                self.SDOTraceThread.join()
+            self.SDOTraceThread = None
+            self.Controler.GetCTRoot()._connector.StopSDOThread()
+
+    def SetSDOTraceValues(self, SDOMonitorEntries):
+        SlavePos = self.Controler.GetSlavePos()
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = True)
+        if check_connect_flag:
+            self.Controler.GetCTRoot()._connector.SetSDOTraceValues(SDOMonitorEntries, SlavePos)
+
+    def SDOMonitorThreadProc(self):
+        while self.SDOMonitoringFlag and self.Controler.GetCTRoot()._connector.PLCStatus != "Started":
+            self.SDOValuesList = self.Controler.GetCTRoot()._connector.GetSDOData()
+            LocalData = self.SDOValuesList[0].items()
+            LocalData.sort()
+            if self.SDOValuesList[1] != self.Controler.GetSlavePos():
+                continue
+            row = 0
+            for (idx, subidx), data in LocalData:
+                self.SDOMonitorGrid.SetCellValue(row, 6, str(data["value"]))
+                row += 1
+            time.sleep(0.5)
+
+    def CreateSDOMonitorGrid(self):
+        """
+        It creates SDO Monitor table and specifies cell size and labels.
+        """
+        self.SDOMonitorGrid.CreateGrid(0,7) 
+        SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 70), 
+                         (4, 40), (5, 450), (6, 85)]
+
+        for (index, size) in SDOCellSize:
+            self.SDOMonitorGrid.SetColSize(index, size)
+        
+        self.SDOMonitorGrid.SetRowLabelSize(0)
+
+        SDOTableLabel = [(0, "Index"), (1, "Subindex"), (2, "Access"),
+                         (3, "Type"), (4, "Size"), (5, "Name"), (6, "Value")]
+        
+        for (index, label) in SDOTableLabel:
+            self.SDOMonitorGrid.SetColLabelValue(index, label)
+            self.SDOMonitorGrid.SetColLabelAlignment(index, wx.ALIGN_CENTER)
+
+    def onMonitorGridDoubleClick(self, event):
+        """
+        Event Handler for double click on the SDO entries table.
+        It adds the entry into the SDO monitor table.
+        If the entry is already in the SDO monitor table, 
+        then it's removed from the SDO monitor table.
+        @pram event: gridlib.EVT_GRID_CELL_LEFT_DCLICK object
+        """
+        row = event.GetRow()
+        idx = self.SDOMonitorGrid.GetCellValue(row, 0)
+        subIdx = self.SDOMonitorGrid.GetCellValue(row, 1)
+        
+        del self.SDOMonitorEntries[(idx, subIdx)]
+        self.SDOMonitorGrid.DeleteRows(row, 1)
+        # add jblee
+        self.SetSDOTraceValues(self.SDOMonitorEntries)
+        self.SDOMonitorGrid.Refresh()
+
+    def SDOParserXML(self, entries):
+        """
+        Parse SDO data set that obtain "ESI file"
+        @param entries: SDO entry list 
+        """  
+        entries_list = entries.items()
+        entries_list.sort()
+        self.ForDefaultValueFlag = False
+        self.CompareValue = ""
+        self.sub_entry_value_list = []
+
+        for (index, subidx), entry in entries_list:
+            # exclude entry that isn't in the objects
+            check_mapping = entry["PDOMapping"]
+            if check_mapping is "T" or check_mapping is "R":
+                if "PDO index" not in entry.keys():
+                    continue
+
+            idx = "0" + entry["Index"].strip("#")
+            #subidx = hex(int(entry["SubIndex"], 0))
+            try :        
+                subidx = "0x" + entry["SubIndex"]
+            except :
+                subidx = "0x0"
+            datatype = entry["Type"]
+
+            try :
+                default_value = entry["DefaultData"]
+            except :
+                default_value = " --- "
+            # Result of SlaveSDO data parsing. (data type : dictionary)
+            self.Data = {'idx':idx, 'subIdx':subidx, 'access':entry["Access"], 
+                         'type':datatype, 'size': str(entry["BitSize"]), 
+                         'name':entry["Name"], 'value':default_value}
+            category_divide_value = [0x1000, 0x2000, 0x6000, 0xa000, 0xffff]
+                
+            for count in range(len(category_divide_value)) :
+                if int(idx, 0) < category_divide_value[count]:
+                    self.Controler.CommonMethod.SDOVariables[count].append(self.Data)
+                    break
+
+        self.Controler.CommonMethod.SDOSubEntryData = self.sub_entry_value_list
+    
+#-------------------------------------------------------------------------------
 #                    For SDO Notebook (divide category)
 # -------------------------------------------------------------------------------
 class SDONoteBook(wx.Notebook):
@@ -494,17 +586,35 @@
 
         self.DeleteAllPages()
 
+        self.Controler.CommonMethod.SDOVariables[5] = []
+        for i in range(4):
+            self.Controler.CommonMethod.SDOVariables[5] += self.Controler.CommonMethod.SDOVariables[i]
+            
         for txt, count in page_texts:
-            self.Data = self.Controler.CommonMethod.SaveSDOData[count]
-            self.Win = SlaveSDOTable(self, self.Data)
+            self.Data = self.Controler.CommonMethod.SDOVariables[count]
+            self.SubEntryData = self.Controler.CommonMethod.SDOSubEntryData
+            self.Win = SlaveSDOTable(self, self.Data, self.SubEntryData)
             self.AddPage(self.Win, txt)
 
+    # add jblee
+    def OnPageChanged(self, event):
+        old = event.GetOldSelection()
+        new = event.GetSelection()
+        sel = self.GetSelection()
+        self.parent.SDOPageNum = new
+        event.Skip()
+
+    def OnPageChanging(self, event):
+        old = event.GetOldSelection()
+        new = event.GetSelection()
+        sel = self.GetSelection()
+        event.Skip()
 
 # -------------------------------------------------------------------------------
 #                    For SDO Grid (fill index, subindex, etc...)
 # -------------------------------------------------------------------------------
 class SlaveSDOTable(wx.grid.Grid):
-    def __init__(self, parent, data):
+    def __init__(self, parent, data, fixed_value):
         """
         Constructor
         @param parent: Reference to the parent SDOPanelClass class
@@ -518,30 +628,31 @@
         self.SDOFlag = True
         if data is None:
             self.SDOs = []
-        else:
+            self.sub_entry_value = []
+        else :
             self.SDOs = data
-
-        self.CreateGrid(len(self.SDOs), 8)
-        SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 55),
-                       (4, 40), (5, 200), (6, 250), (7, 85)]
-
+            self.sub_entry_value = fixed_value
+        
+        self.CreateGrid(len(self.SDOs), 7)
+        SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 70), 
+                         (4, 40), (5, 400), (6, 135)]
+        
         for (index, size) in SDOCellSize:
             self.SetColSize(index, size)
 
         self.SetRowLabelSize(0)
 
         SDOTableLabel = [(0, "Index"), (1, "Subindex"), (2, "Access"),
-                         (3, "Type"), (4, "Size"), (5, "Category"),
-                         (6, "Name"), (7, "Value")]
+                         (3, "Type"), (4, "Size"), (5, "Name"), (6, "Value")]
 
         for (index, label) in SDOTableLabel:
             self.SetColLabelValue(index, label)
-            self.SetColLabelAlignment(index, wx.ALIGN_CENTRE)
+            self.SetColLabelAlignment(index, wx.ALIGN_CENTER)
 
         attr = wx.grid.GridCellAttr()
 
-        # for SDO download
-        self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.SDOModifyDialog)
+        # for SDO download and monitoring 
+        self.Bind(gridlib.EVT_GRID_CELL_LEFT_DCLICK, self.onGridDoubleClick)
 
         for i in range(7):
             self.SetColAttr(i, attr)
@@ -554,56 +665,45 @@
         """
         Cell is filled by new parsing data
         """
-        sdo_list = ['idx', 'subIdx', 'access', 'type', 'size', 'category', 'name', 'value']
+        sdo_list = ['idx', 'subIdx', 'access', 'type', 'size', 'name', 'value']
+        count = 0
         for row_idx in range(len(self.SDOs)):
             for col_idx in range(len(self.SDOs[row_idx])):
-                self.SetCellValue(row_idx, col_idx, self.SDOs[row_idx][sdo_list[col_idx]])
+                
+                # the top entries that have sub index is shaded.
+                if int(self.SDOs[row_idx]['subIdx'], 16) == 0x00:
+                    try:
+                        if int(self.SDOs[row_idx + 1]['subIdx'], 16) is not 0x00:
+                            self.SetCellBackgroundColour(row_idx, col_idx, \
+                                                                wx.LIGHT_GREY)
+                    except:
+                        pass
+
+                if self.SDOs[row_idx][sdo_list[col_idx]] == "modifying":
+                    if len(self.sub_entry_value) == count:
+                        continue
+                    self.SetCellValue(row_idx, col_idx, self.sub_entry_value[count])
+                    count += 1
+                else :
+                    self.SetCellValue(row_idx, col_idx, \
+                                  self.SDOs[row_idx][sdo_list[col_idx]])
+
                 self.SetReadOnly(row_idx, col_idx, True)
                 if col_idx < 5:
                     self.SetCellAlignment(row_idx, col_idx, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
 
     def CheckSDODataAccess(self, row):
         """
-        CheckSDODataAccess method is checking that access data has "w"
-        Access field consist 6 char, if mean
-           rw      rw     rw
-        (preop) (safeop) (op)
-        Example Access field : rwrwrw, rwrw--
+        check that access field has "rw"
         @param row : Selected cell by user
         @return Write_flag : If data has "w", flag is true
         """
-        write_flag = False
         check = self.SDOs[row]['access']
-        if check[1:2] == 'w':
-            self.Controler.CommonMethod.Check_PREOP = True
-            write_flag = True
-        if check[3:4] == 'w':
-            self.Controler.CommonMethod.Check_SAFEOP = True
-            write_flag = True
-        if check[5:] == 'w':
-            self.Controler.CommonMethod.Check_OP = True
-            write_flag = True
-
-        return write_flag
-
-    def DecideSDODownload(self, state):
-        """
-        compare current state and "access" field,
-        result notify SDOModifyDialog method
-        @param state : current slave state
-        @return True or False
-        """
-        # Example of 'state' parameter : "0  0:0  PREOP  +  EL9800 (V4.30) (PIC24, SPI, ET1100)"
-        state = state[self.Controler.GetSlavePos()].split("  ")[2]
-        if state == "PREOP" and self.Controler.CommonMethod.Check_PREOP:
+        if check == "rw":
             return True
-        elif state == "SAFEOP" and self.Controler.CommonMethod.Check_SAFEOP:
-            return True
-        elif state == "OP" and self.Controler.CommonMethod.Check_OP:
-            return True
-
-        return False
-
+        else:
+            return False
+    
     def ClearStateFlag(self):
         """
         Initialize StateFlag
@@ -613,51 +713,112 @@
         self.Controler.CommonMethod.Check_SAFEOP = False
         self.Controler.CommonMethod.Check_OP = False
 
-    def SDOModifyDialog(self, event):
+    def onGridDoubleClick (self, event):
         """
         Create dialog for SDO value modify
-        if user enter data, perform command "ethercat download"
-        @param event : wx.grid.EVT_GRID_CELL_LEFT_DCLICK object
+        if user enter data, perform command "ethercat download"  
+        @param event : gridlib.EVT_GRID_CELL_LEFT_DCLICK object
         """
         self.ClearStateFlag()
-
-        # CheckSDODataAccess is checking that OD(Object Dictionary) has "w"
-        if event.GetCol() == 7 and self.CheckSDODataAccess(event.GetRow()):
-            dlg = wx.TextEntryDialog(
-                self,
-                _("Enter hex or dec value (if enter dec value, it automatically conversed hex value)"),
-                "SDOModifyDialog",
-                style=wx.OK | wx.CANCEL)
-
-            start_value = self.GetCellValue(event.GetRow(), event.GetCol())
+        
+        # CheckSDODataAccess is checking that OD(Object Dictionary) has "w" 
+        if event.GetCol() == 6 and self.CheckSDODataAccess(event.GetRow()) :    
+            dlg = wx.TextEntryDialog (self, 
+                    "Enter hex or dec value (if enter dec value, " \
+                  + "it automatically conversed hex value)",
+                    "SDOModifyDialog", style = wx.OK | wx.CANCEL)
+
+            start_value = self.GetCellValue(event.GetRow(), event.GetCol()) 
             dlg.SetValue(start_value)
-
+            
             if dlg.ShowModal() == wx.ID_OK:
-                try:
-                    int(dlg.GetValue(), 0)
-                    # check "Access" field
-                    if self.DecideSDODownload(self.Controler.CommonMethod.SlaveState[self.Controler.GetSlavePos()]):
+                # Check whether beremiz connected or not.
+                # If this method is called cyclically, set the cyclic flag true
+                check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+                if check_connect_flag:
+                    try :
+                        input_val = hex(int(dlg.GetValue(), 0))
                         # Request "SDODownload"
-                        self.Controler.CommonMethod.SDODownload(
-                            self.SDOs[event.GetRow()]['type'],
-                            self.SDOs[event.GetRow()]['idx'],
-                            self.SDOs[event.GetRow()]['subIdx'],
-                            dlg.GetValue())
-
-                        self.SetCellValue(event.GetRow(), event.GetCol(), hex(int(dlg.GetValue(), 0)))
-                    else:
-                        self.Controler.CommonMethod.CreateErrorDialog(_('You cannot SDO download this state'))
-                # Error occured process of "int(variable)"
-                # User input is not hex, dec value
-                except ValueError:
-                    self.Controler.CommonMethod.CreateErrorDialog(_('You can input only hex, dec value'))
-
-
-# -------------------------------------------------------------------------------
-#                 For PDO Monitoring Panel
+                        return_val = self.Controler.CommonMethod.SDODownload(
+                                                self.SDOs[event.GetRow()]["type"], 
+                                                self.SDOs[event.GetRow()]["idx"], 
+                                                self.SDOs[event.GetRow()]["subIdx"], 
+                                                dlg.GetValue())
+                        if return_val is "":
+                            SDOUploadEntry = {"idx" : self.SDOs[event.GetRow()]["idx"],
+                                              "subIdx" : self.SDOs[event.GetRow()]["subIdx"],
+                                              "size" : self.SDOs[event.GetRow()]["size"]
+                                             }
+                            data = self.Controler.GetCTRoot()._connector.GetSDOEntryData(
+                                SDOUploadEntry, self.Controler.GetSlavePos())
+                            hex_val = hex(data)[:-1]                           
+
+                            # download data check
+                            if input_val == hex_val:
+                                display_val = "%s(%d)" % (hex_val, data) 
+                                self.SetCellValue(event.GetRow(), event.GetCol(), 
+                                                  display_val)
+                            else :
+                                self.Controler.CommonMethod.CreateErrorDialog(\
+                                            'SDO Value not completely download, please try again')    
+                        else:
+                            self.Controler.GetCTRoot().logger.write_error(return_val)
+                            
+                    # Error occured process of "int(variable)"
+                    # User input is not hex, dec value
+                    except ValueError:
+                        self.Controler.CommonMethod.CreateErrorDialog(\
+                                            'You can input only hex, dec value')    
+        else:
+            SDOPanel = self.parent.parent
+            row = event.GetRow() 
+            
+            idx = self.SDOs[row]["idx"]
+            subIdx = self.SDOs[row]["subIdx"]
+            SDOPanel.SDOMonitorEntries[(idx, subIdx)] = {
+                                                "access": self.SDOs[row]["access"],
+                                                "type": self.SDOs[row]["type"],
+                                                "size": self.SDOs[row]["size"],
+                                                "name": self.SDOs[row]["name"],
+                                                # add jblee
+                                                "value": ""}
+            
+            del_rows = SDOPanel.SDOMonitorGrid.GetNumberRows()
+            
+            try: 
+                SDOPanel.SDOMonitorGrid.DeleteRows(0, del_rows)
+            except:
+                pass
+
+            SDOPanel.SDOMonitorGrid.AppendRows(len(SDOPanel.SDOMonitorEntries))
+            SDOPanel.SetSDOTraceValues(SDOPanel.SDOMonitorEntries)
+            
+            SME_list = SDOPanel.SDOMonitorEntries.items()
+            SME_list.sort()
+
+            gridRow = 0
+            for (idx, subIdx), entry in SME_list:
+                SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, 0, str(idx))
+                SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, 1, str(subIdx))
+                for col, key in [(2, "access"),
+                                 (3, "type"),
+                                 (4, "size"),
+                                 (5, "name")]:
+                    SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, col, entry[key])
+                for col in range(7):
+                    SDOPanel.SDOMonitorGrid.SetReadOnly(gridRow, col, True)
+                    if col < 5 :
+                        SDOPanel.SDOMonitorGrid.SetCellAlignment(\
+                                        gridRow, col, wx.ALIGN_CENTER, wx.ALIGN_CENTER)
+                gridRow += 1
+            
+            SDOPanel.SDOMonitorGrid.Refresh()
+
+#-------------------------------------------------------------------------------
+#                 For PDO Mapping Panel
 # PDO Class UI  : Panel -> Choicebook (RxPDO, TxPDO) ->
 #                 Notebook (PDO Index) -> Grid (PDO entry)
-# -------------------------------------------------------------------------------
+#-------------------------------------------------------------------------------
 class PDOPanelClass(wx.Panel):
     def __init__(self, parent, controler):
         """
@@ -667,16 +828,152 @@
         """
         wx.Panel.__init__(self, parent, -1)
         self.Controler = controler
-
-        self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
-        self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
-
-        self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler)
-        self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
-
-        self.PDOMonitoringEditorMainSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
-
-        self.SetSizer(self.PDOMonitoringEditorMainSizer)
+        sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+        line = wx.StaticText(self, -1, "\n In order to control Ethercat device, user must select proper PDO set.\
+                                        \n Each PDO sets describe operation modes (CSP, CSV, CST) supported by Ethercat devices.\
+                                      \n\n PDOs have two types, RxPDO and TxPDO.\
+                                        \n  - RxPDO refers to the Receive Process Data Object. It means the control parameters which sent from controller to the EtherCAT Slave device.\
+                                        \n    In general, ControlWord (0x6040), Modes of Operations (0x6060), and TargetPosition (0x607A) are regarded as RxPDO.\
+                                        \n  - TxPDO refers to the Transmit Process Data Object. It used to report status of EtherCAT Slave device to the controller in order to calibrate their next actuation.\
+                                        \n    StatusWord (0x6041), Modes of Operation Display (0x6061), and ActualPosition (0x607A) include in TxPDO.\
+                                      \n\n PDO Mapping feature provides available RxPDO and TxPDO sets which defined in Ethercat slave description XML.\
+                                        \n If there is no selection of PDO set, first set (0x1600, 0x1A00) will be chosen as default configuration.")
+        
+        sizer.Add(line)
+        self.SetSizer(sizer)
+
+class RxPDOPanelClass(wx.Panel):
+    def __init__(self, parent, controler):
+        """
+        Constructor
+        @param parent: Reference to the parent EtherCATManagementTreebook class
+        @param controler: _EthercatSlaveCTN class in EthercatSlave.py
+        """
+        wx.Panel.__init__(self, parent, -1)
+        self.Controler = controler
+
+        # add jblee
+        #self.PDOIndexList = ["RxPDO"]
+        self.PDOIndexList = []
+        self.LoadPDOSelectData()
+
+        #HSAHN ADD. 2015.7.26 PDO Select Function ADD
+        self.Controler.CommonMethod.RequestPDOInfo()
+        self.PDOcheckBox = []
+        self.rx_pdo_entries = self.Controler.CommonMethod.GetRxPDOCategory()
+        if len(self.rx_pdo_entries):
+            for i in range(len(self.rx_pdo_entries)):
+                self.PDOcheckBox.append(wx.CheckBox(self, label=str(hex(self.rx_pdo_entries[i]['pdo_index'])), size=(120,15)))
+                if not self.Controler.SelectedRxPDOIndex and self.rx_pdo_entries[i]['sm'] is not None:
+                    self.PDOcheckBox[-1].SetValue(True)
+                    self.Controler.SelectedRxPDOIndex.append(int(self.PDOcheckBox[-1].GetLabel(), 0))
+                    self.InitSavePDO()
+                elif self.rx_pdo_entries[i]['pdo_index'] in self.Controler.SelectedRxPDOIndex:
+                    self.PDOIndexList.append(str(hex(self.rx_pdo_entries[i]['pdo_index'])))
+                    self.PDOcheckBox[-1].SetValue(True)
+                    
+            for cb in self.PDOcheckBox:
+                self.Bind(wx.EVT_CHECKBOX, self.PDOSelectCheck, cb)
+
+            self.PDOListBox = wx.StaticBox(self, label=_("PDO Mapping Select"))
+            self.PDOListBoxSizer = wx.StaticBoxSizer(self.PDOListBox, orient=wx.HORIZONTAL)
+            self.RxPDOListBox = wx.StaticBox(self, label=_("RxPDO"))
+            self.RxPDOListBoxSizer = wx.StaticBoxSizer(self.RxPDOListBox, orient=wx.VERTICAL)
+            self.RxPDOListBoxInnerSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=10, vgap=5)
+            self.RxPDOListBoxInnerSizer.AddMany(self.PDOcheckBox[0:len(self.rx_pdo_entries)])
+            self.RxPDOListBoxSizer.Add(self.RxPDOListBoxInnerSizer)
+            self.PDOListBoxSizer.Add(self.RxPDOListBoxSizer)
+            self.PDOWarningText = wx.StaticText(self, -1,
+                       "  *Warning*\n\n By default configuration, \n\n first mapping set is selected. \n\n Choose the PDO mapping!",
+                       size=(220, -1))
+            self.PDOListBoxSizer.Add(self.PDOWarningText)
+
+            self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
+            self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
+
+        
+            self.CallPDOChoicebook = PDONoteBook(self, controler=self.Controler, name="Rx")
+            self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
+
+            self.PDOInformationBox = wx.StaticBox(self, label=_("RxPDO Mapping List"))
+            self.PDOInformationBoxSizer = wx.StaticBoxSizer(self.PDOInformationBox, orient=wx.VERTICAL)
+            self.PDOInformationBoxSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
+
+            self.PDOMonitoringEditorMainSizer.Add(self.PDOListBoxSizer)
+            self.PDOMonitoringEditorMainSizer.Add(self.PDOInformationBoxSizer)
+            self.SetSizer(self.PDOMonitoringEditorMainSizer)
+
+            # add jblee
+            self.PDOExcludeCheck()
+        else:
+            sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+            line = wx.StaticText(self, -1, "\n  This device does not support RxPDO.")
+        
+            sizer.Add(line)
+            self.SetSizer(sizer)
+
+    def LoadPDOSelectData(self):
+        RxPDOData = self.Controler.BaseParams.getRxPDO()
+        RxPDOs = []
+        if RxPDOData != "None":
+            RxPDOs = RxPDOData.split()
+        if RxPDOs :
+            for RxPDO in RxPDOs :
+                self.Controler.SelectedRxPDOIndex.append(int(RxPDO, 0))
+
+    def PDOSelectCheck(self, event):
+        # add jblee for Save User Select
+        cb = event.GetEventObject()
+                         # prevent duplicated check
+        if cb.GetValue() and int(cb.GetLabel(), 0) not in self.Controler.SelectedRxPDOIndex:
+            self.Controler.SelectedRxPDOIndex.append(int(cb.GetLabel(), 0))
+            self.PDOIndexList.append(cb.GetLabel())
+        else:
+            self.Controler.SelectedRxPDOIndex.remove(int(cb.GetLabel(), 0))
+            self.PDOIndexList.remove(cb.GetLabel())
+
+        data = ""
+        for PDOIndex in self.PDOIndexList:            
+            data = data + " " + PDOIndex
+
+        self.Controler.BaseParams.setRxPDO(data)
+        self.Controler.GetCTRoot().CTNRequestSave()
+
+        self.PDOExcludeCheck()
+
+    def InitSavePDO(self):
+        for PDOIndex in self.Controler.SelectedRxPDOIndex:
+            self.PDOIndexList.append(str(hex(PDOIndex)))
+
+        data = ""
+        for PDOIndex in self.PDOIndexList:            
+            data = data + " " + PDOIndex
+
+        self.Controler.BaseParams.setRxPDO(data)
+
+    # 2016.06.21
+    # add jblee for check exclude pdo list
+    def PDOExcludeCheck(self):
+        #files = os.listdir(self.Controler.CTNPath())
+        #filepath = os.path.join(self.Controler.CTNPath(), "DataForPDO.txt")
+        CurIndexs = self.Controler.SelectedRxPDOIndex
+        for CB in self.PDOcheckBox:
+            if len(CB.GetLabel().split()) > 1:
+                CB.Enable()
+                CB.SetLabel(CB.GetLabel().split()[0])
+
+        for pdo in self.rx_pdo_entries:
+            for CurIndex in CurIndexs:
+                if pdo["pdo_index"] == CurIndex:
+                    ex_list = pdo["exclude_list"]
+                    for ex_item in ex_list:
+                        for CB in self.PDOcheckBox:
+                            if CB.GetLabel() == hex(ex_item):
+                                CB.SetLabel(CB.GetLabel() + " (Excluded)")
+                                CB.Disable()
+
+    def RefreshPDOInfo(self):
+        pass
 
     def PDOInfoUpdate(self):
         """
@@ -684,28 +981,142 @@
         """
         self.Controler.CommonMethod.RequestPDOInfo()
         self.CallPDOChoicebook.Destroy()
-        self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler)
+        self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler, name="Rx")
         self.Refresh()
 
-
-# -------------------------------------------------------------------------------
-#                    For PDO Choicebook (divide Tx, Rx PDO)
-# -------------------------------------------------------------------------------
-class PDOChoicebook(wx.Choicebook):
+class TxPDOPanelClass(wx.Panel):
     def __init__(self, parent, controler):
         """
         Constructor
-        @param parent: Reference to the parent PDOPanelClass class
+        @param parent: Reference to the parent EtherCATManagementTreebook class
         @param controler: _EthercatSlaveCTN class in EthercatSlave.py
         """
-        wx.Choicebook.__init__(self, parent, id=-1, size=(500, 500), style=wx.CHB_DEFAULT)
+        wx.Panel.__init__(self, parent, -1)
         self.Controler = controler
 
-        RxWin = PDONoteBook(self, controler=self.Controler, name="Rx")
-        TxWin = PDONoteBook(self, controler=self.Controler, name="Tx")
-        self.AddPage(RxWin, "RxPDO")
-        self.AddPage(TxWin, "TxPDO")
-
+        # add jblee
+        self.PDOIndexList = []
+        self.LoadPDOSelectData()
+
+        #HSAHN ADD. 2015.7.26 PDO Select Function ADD
+        self.Controler.CommonMethod.RequestPDOInfo()
+        self.PDOcheckBox = []
+        self.tx_pdo_entries = self.Controler.CommonMethod.GetTxPDOCategory()
+        if len(self.tx_pdo_entries):
+            for i in range(len(self.tx_pdo_entries)):
+                self.PDOcheckBox.append(wx.CheckBox(self, label=str(hex(self.tx_pdo_entries[i]['pdo_index'])), size=(120,15)))
+                if not self.Controler.SelectedTxPDOIndex and self.tx_pdo_entries[i]['sm'] is not None:
+                    self.PDOcheckBox[-1].SetValue(True)
+                    self.Controler.SelectedTxPDOIndex.append(int(self.PDOcheckBox[-1].GetLabel(), 0))
+                    self.InitSavePDO()
+                elif self.tx_pdo_entries[i]['pdo_index'] in self.Controler.SelectedTxPDOIndex:
+                    self.PDOIndexList.append(str(hex(self.tx_pdo_entries[i]['pdo_index'])))
+                    self.PDOcheckBox[-1].SetValue(True)
+            for cb in self.PDOcheckBox:
+                self.Bind(wx.EVT_CHECKBOX, self.PDOSelectCheck, cb)
+        
+            self.PDOListBox = wx.StaticBox(self, label=_("PDO Mapping Select"))
+            self.PDOListBoxSizer = wx.StaticBoxSizer(self.PDOListBox, orient=wx.HORIZONTAL)
+            self.TxPDOListBox = wx.StaticBox(self, label=_("TxPDO"))
+            self.TxPDOListBoxSizer = wx.StaticBoxSizer(self.TxPDOListBox, orient=wx.VERTICAL)
+            self.TxPDOListBoxInnerSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=10, vgap=5)
+            self.TxPDOListBoxInnerSizer.AddMany(self.PDOcheckBox[0:len(self.tx_pdo_entries)])
+            self.TxPDOListBoxSizer.Add(self.TxPDOListBoxInnerSizer)
+            self.PDOListBoxSizer.Add(self.TxPDOListBoxSizer)
+            self.PDOWarningText = wx.StaticText(self, -1,
+                       "  *Warning*\n\n By default configuration, \n\n first mapping set is selected. \n\n Choose the PDO mapping!",
+                       size=(220, -1))
+            self.PDOListBoxSizer.Add(self.PDOWarningText)
+
+            self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
+            self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
+
+            self.CallPDOChoicebook = PDONoteBook(self, controler=self.Controler, name="Tx")
+            self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
+
+            self.PDOInformationBox = wx.StaticBox(self, label=_("TxPDO Mapping List"))
+            self.PDOInformationBoxSizer = wx.StaticBoxSizer(self.PDOInformationBox, orient=wx.VERTICAL)
+            self.PDOInformationBoxSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
+
+            self.PDOMonitoringEditorMainSizer.Add(self.PDOListBoxSizer)
+            self.PDOMonitoringEditorMainSizer.Add(self.PDOInformationBoxSizer)
+            self.SetSizer(self.PDOMonitoringEditorMainSizer)
+
+            # add jblee
+            self.PDOExcludeCheck()
+        else:
+            sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+            line = wx.StaticText(self, -1, "\n  This device does not support TxPDO.")
+        
+            sizer.Add(line)
+            self.SetSizer(sizer)
+
+    def LoadPDOSelectData(self):
+        TxPDOData = self.Controler.BaseParams.getTxPDO()
+        TxPDOs = []
+        if TxPDOData != "None":
+            TxPDOs = TxPDOData.split()
+        if TxPDOs :
+            for TxPDO in TxPDOs :
+                self.Controler.SelectedTxPDOIndex.append(int(TxPDO, 0))
+
+    def PDOSelectCheck(self, event):
+        # add jblee for Save User Select
+        cb = event.GetEventObject()
+                         # prevent duplicated check
+        if cb.GetValue() and int(cb.GetLabel(), 0) not in self.Controler.SelectedTxPDOIndex:
+            self.Controler.SelectedTxPDOIndex.append(int(cb.GetLabel(), 0))
+            self.PDOIndexList.append(cb.GetLabel())
+        else:
+            self.Controler.SelectedTxPDOIndex.remove(int(cb.GetLabel(), 0))
+            self.PDOIndexList.remove(cb.GetLabel())
+
+        data = ""
+        for PDOIndex in self.PDOIndexList:            
+            data = data + " " + PDOIndex
+
+        self.Controler.BaseParams.setTxPDO(data)
+        self.Controler.GetCTRoot().CTNRequestSave()
+
+        self.PDOExcludeCheck()
+
+    def InitSavePDO(self):
+        for PDOIndex in self.Controler.SelectedTxPDOIndex:
+            self.PDOIndexList.append(str(hex(PDOIndex)))
+
+        data = ""
+        for PDOIndex in self.PDOIndexList:            
+            data = data + " " + PDOIndex
+
+        self.Controler.BaseParams.setTxPDO(data)
+
+    # 2016.06.21
+    # add jblee for check exclude pdo list
+    def PDOExcludeCheck(self):
+        CurIndexs = self.Controler.SelectedTxPDOIndex
+        for CB in self.PDOcheckBox:
+            if len(CB.GetLabel().split()) > 1:
+                CB.Enable()
+                CB.SetLabel(CB.GetLabel().split()[0])
+
+        for pdo in self.tx_pdo_entries:
+            for CurIndex in CurIndexs:
+                if pdo["pdo_index"] == CurIndex:
+                    ex_list = pdo["exclude_list"]
+                    for ex_item in ex_list:
+                        for CB in self.PDOcheckBox:
+                            if CB.GetLabel() == hex(ex_item):
+                                CB.SetLabel(CB.GetLabel() + " (Excluded)")
+                                CB.Disable()
+
+    def PDOInfoUpdate(self):
+        """
+        Call RequestPDOInfo method and create Choicebook
+        """
+        self.Controler.CommonMethod.RequestPDOInfo()
+        self.CallPDOChoicebook.Destroy()
+        self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler, name="Tx")
+        self.Refresh()
 
 # -------------------------------------------------------------------------------
 #                    For PDO Notebook (divide PDO index)
@@ -718,14 +1129,12 @@
         @param name: identifier whether RxPDO or TxPDO
         @param controler: _EthercatSlaveCTN class in EthercatSlave.py
         """
-        wx.Notebook.__init__(self, parent, id=-1, size=(640, 400))
+        wx.Notebook.__init__(self, parent, id=-1, size=(600, 400))
         self.Controler = controler
 
         count = 0
         page_texts = []
 
-        self.Controler.CommonMethod.RequestPDOInfo()
-
         if name == "Tx":
             # obtain pdo_info and pdo_entry
             # pdo_info include (PDO index, name, number of entry)
@@ -820,12 +1229,10 @@
                 self.SetRowSize(row_idx, 25)
             start_value += 1
 
-
-# -------------------------------------------------------------------------------
-#                    For EEPROM Access Main Panel
-#                 (This class explain EEPROM Access)
-# -------------------------------------------------------------------------------
-class EEPROMAccessPanel(wx.Panel):
+#-------------------------------------------------------------------------------
+#                    For MDP Main Panel         
+#-------------------------------------------------------------------------------  
+class MDPPanel(wx.Panel):
     def __init__(self, parent, controler):
         """
         Constructor
@@ -833,6 +1240,185 @@
         @param controler: _EthercatSlaveCTN class in EthercatSlave.py
         """
         wx.Panel.__init__(self, parent, -1)
+        self.parent = parent
+        self.Controler = controler
+
+        sizer = wx.FlexGridSizer(cols=3, hgap=20, rows=1, vgap=20)
+
+        # Include Module ListBox
+        leftInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+        # Include Add, Delete Button
+        middleInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+        # Include Slot ListBox
+        rightInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+        
+        # Get Module Name as Array
+        # MDPArray = {SlaveName, [data0, data1, ...], SlotIndexIncrement, SlotPdoIncrement}
+        # data = [ModuleName, ModuleInfo, [PDOInfo1, PDOInfo2, ...]]
+        # PDOInfo = {Index, Name, BitSize, Access, PDOMapping, SubIndex, Type}
+        slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+        type_infos = slave.getType()
+        MDPArray = self.Controler.CTNParent.CTNParent.GetMDPInfos(type_infos)
+
+        NameSet = []
+        if MDPArray:
+            for info in MDPArray[0][1]:
+                NameSet.append(info[0])
+
+        # Module ListBox
+        self.ModuleLabel = wx.StaticText(self, -1, "Module")
+        self.ModuleListBox = wx.ListBox(self, size = (150, 200), choices = NameSet)
+        #self.ModuleListBox = wx.ListBox(self, size = (150, 200), choices = [])
+        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnModuleListBoxDCClick, self.ModuleListBox)
+
+        # Button
+        self.AddButton = wx.Button(self, label=_(" Add Module  "))
+        self.DeleteButton = wx.Button(self, label=_("Delete Module"))
+
+        # Button Event Mapping
+        self.AddButton.Bind(wx.EVT_BUTTON, self.OnAddButton)
+        self.DeleteButton.Bind(wx.EVT_BUTTON, self.OnDeleteButton)
+
+        # Slot ListBox
+        self.SlotLabel = wx.StaticText(self, -1, "Slot")
+        self.SlotListBox = wx.ListBox(self, size = (150, 200))
+        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnSlotListBoxDCClick, self.SlotListBox)
+        self.SelectModule = []
+
+        # Add Object Each Sizer
+        leftInnerSizer.AddMany([self.ModuleLabel, self.ModuleListBox])
+        middleInnerSizer.Add(self.AddButton, 0, wx.TOP, 100)
+        middleInnerSizer.Add(self.DeleteButton, 0, wx.BOTTOM, 120)
+        rightInnerSizer.AddMany([self.SlotLabel, self.SlotListBox])
+        
+        sizer.AddMany([leftInnerSizer, middleInnerSizer, rightInnerSizer])
+        
+        self.SetSizer(sizer)
+
+        self.InitMDPSet()
+
+    def InitMDPSet(self):
+        files = os.listdir(self.Controler.CTNPath())
+        filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+        try:
+            moduleDataFile = open(filepath, 'r')
+            lines = moduleDataFile.readlines()
+
+            for line in lines:
+                if line == "\n":
+                    continue
+                module_pos = line.split()[-1]
+                name_len_limit = len(line) - len(module_pos) - 2
+                module_name = line[0:name_len_limit]
+                
+                self.SelectModule.append((module_name, int(module_pos)))
+
+            localModuleInfo = []        
+            count = 1
+            for (item, pos) in self.SelectModule:
+                slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+                localModuleInfo.append(slotString)
+                count += 1
+            self.SlotListBox.SetItems(localModuleInfo)
+
+        except:
+            moduleDataFile = open(filepath, 'w')
+
+        moduleDataFile.close()
+
+    def OnAddButton(self, event):
+        files = os.listdir(self.Controler.CTNPath())
+        filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+        moduleDataFile = open(filepath, 'w')
+
+        selectNum = self.ModuleListBox.GetSelection()
+        if selectNum >= 0:
+            selectStr = self.ModuleListBox.GetString(selectNum)
+            self.SelectModule.append((selectStr, selectNum))
+            localModuleInfo = []
+            count = 1
+            for (item, pos) in self.SelectModule:
+                slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+                localModuleInfo.append(slotString)
+                count += 1
+            self.SlotListBox.SetItems(localModuleInfo)
+
+        moduleDataFile.close()
+        
+    def OnDeleteButton(self, event):
+        files = os.listdir(self.Controler.CTNPath())
+        filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+        moduleDataFile = open(filepath, 'w')
+
+        selectNum = self.SlotListBox.GetSelection()
+        if selectNum >= 0:
+            selectStr = self.SlotListBox.GetString(selectNum)
+            self.SelectModule.pop(selectNum)
+            localModuleInfo = []
+            count = 1
+            for (item, pos) in self.SelectModule:
+                moduleDataFile.write(item + " " + str(pos) + "\n")
+                slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+                localModuleInfo.append(slotString)
+                count += 1
+            self.SlotListBox.SetItems(localModuleInfo)
+
+        moduleDataFile.close()
+
+    def OnModuleListBoxDCClick(self, event):
+        files = os.listdir(self.Controler.CTNPath())
+        filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+        moduleDataFile = open(filepath, 'w')
+
+        selectNum = self.ModuleListBox.GetSelection()
+        if selectNum >= 0:
+            selectStr = self.ModuleListBox.GetString(selectNum)
+            self.SelectModule.append((selectStr, selectNum))
+            localModuleInfo = []
+            count = 1
+            for (item, pos) in self.SelectModule:
+                moduleDataFile.write(item + " " + str(pos) + "\n")
+                slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+                localModuleInfo.append(slotString)
+                module = self.Controler.CTNParent.CTNParent.GetSelectModule(pos)
+                self.Controler.CommonMethod.SavePDOData(module)
+                count += 1
+            self.SlotListBox.SetItems(localModuleInfo)
+
+        moduleDataFile.close()
+
+    def OnSlotListBoxDCClick(self, event):
+        files = os.listdir(self.Controler.CTNPath())
+        filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+        moduleDataFile = open(filepath, 'w')
+
+        selectNum = self.SlotListBox.GetSelection()
+        if selectNum >= 0:
+            selectStr = self.SlotListBox.GetString(selectNum)
+            self.SelectModule.pop(selectNum)
+            localModuleInfo = []
+            count = 1
+            for (item, pos) in self.SelectModule:
+                moduleDataFile.write(item + " " + str(pos) + "\n")
+                slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+                localModuleInfo.append(slotString)
+                count += 1
+            self.SlotListBox.SetItems(localModuleInfo)
+
+        moduleDataFile.close()
+
+# -------------------------------------------------------------------------------
+#                    For EEPROM Access Main Panel
+#                 (This class explain EEPROM Access)
+# -------------------------------------------------------------------------------
+class EEPROMAccessPanel(wx.Panel):
+    def __init__(self, parent, controler):
+        """
+        Constructor
+        @param parent: Reference to the parent EtherCATManagementTreebook class
+        @param controler: _EthercatSlaveCTN class in EthercatSlave.py
+        """
+        wx.Panel.__init__(self, parent, -1)
         sizer = wx.FlexGridSizer(cols=1, hgap=20, rows=3, vgap=20)
 
         line = wx.StaticText(self, -1, "\n  EEPROM Access is composed to SmartView and HexView. \
@@ -858,22 +1444,24 @@
         self.parent = parent
         self.Controler = controler
 
-        self.PDIType = {
-            0: ['none', '00000000'],
-            4: ['Digital I/O', '00000100'],
-            5: ['SPI Slave', '00000101'],
-            7: ['EtherCAT Bridge (port3)', '00000111'],
-            8: ['uC async. 16bit', '00001000'],
-            9: ['uC async. 8bit', '00001001'],
-            10: ['uC sync. 16bit', '00001010'],
-            11: ['uC sync. 8bit', '00001011'],
-            16: ['32 Digtal Input and 0 Digital Output', '00010000'],
-            17: ['24 Digtal Input and 8 Digital Output', '00010001'],
-            18: ['16 Digtal Input and 16 Digital Output', '00010010'],
-            19: ['8 Digtal Input and 24 Digital Output', '00010011'],
-            20: ['0 Digtal Input and 32 Digital Output', '00010100'],
-            128: ['On-chip bus', '11111111']
-        }
+        # PDI Type 1, 13 unknown Type, Fix Future
+        self.PDIType = {0  :['none', '00000000'], 
+                        1  :['unknown', '00000000'],
+                        4  :['Digital I/O', '00000100'],
+                        5  :['SPI Slave', '00000101'],
+                        7  :['EtherCAT Bridge (port3)', '00000111'],
+                        8  :['uC async. 16bit', '00001000'],
+                        9  :['uC async. 8bit', '00001001'],
+                        10 :['uC sync. 16bit', '00001010'],
+                        11 :['uC sync. 8bit', '00001011'],
+                        13 :['unknown', '00000000'],
+                        16 :['32 Digtal Input and 0 Digital Output', '00010000'],
+                        17 :['24 Digtal Input and 8 Digital Output', '00010001'],
+                        18 :['16 Digtal Input and 16 Digital Output','00010010'],
+                        19 :['8 Digtal Input and 24 Digital Output', '00010011'],
+                        20 :['0 Digtal Input and 32 Digital Output', '00010100'],
+                        128:['On-chip bus', '11111111']
+                        }
 
         sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5)
         button_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
@@ -935,7 +1523,8 @@
         @param event : wx.EVT_BUTTON object
         """
         # Check whether beremiz connected or not.
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
             self.SiiBinary = self.Controler.CommonMethod.LoadData()
             self.SetEEPROMData()
@@ -1257,7 +1846,8 @@
         @param event : wx.EVT_BUTTON object
         """
         # Check whether beremiz connected or not.
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
             # load from EEPROM data and parsing
             self.SiiBinary = self.Controler.CommonMethod.LoadData()
@@ -1274,7 +1864,8 @@
         """
         # Check whether beremiz connected or not,
         # and whether status is "Started" or not.
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
             status, _log_count = self.Controler.GetCTRoot()._connector.GetPLCstatus()
             if status is not PlcStatus.Started:
@@ -1439,7 +2030,7 @@
         # flag for compact view
         self.CompactFlag = False
 
-        # main grid의 rows and cols
+        # main grid rows and cols
         self.MainRow = [512, 512, 512, 512]
         self.MainCol = 4
 
@@ -1675,7 +2266,8 @@
         @param event: wx.EVT_BUTTON object
         """
         # Check whether beremiz connected or not.
-        check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+        # If this method is called cyclically, set the cyclic flag true
+        check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
         if check_connect_flag:
             self.LoadData()
             self.BasicSetData()
@@ -2059,8 +2651,7 @@
         @param parent: wx.ScrollWindow object
         @Param controler: _EthercatSlaveCTN class in EthercatSlave.py
         """
-        wx.Panel.__init__(self, parent, -1, (0, 0),
-                          size=wx.DefaultSize, style=wx.SUNKEN_BORDER)
+        wx.Panel.__init__(self, parent)
         self.Controler = controler
         self.parent = parent
         self.StaticBox = {}
@@ -2070,21 +2661,25 @@
         # ----------------------- Main Sizer and Update Button --------------------------------------------
         self.MasterStateSizer = {"main": wx.BoxSizer(wx.VERTICAL)}
         for key, attr in [
-                ("innerMain",           [1, 10, 2, 10]),
-                ("innerTopHalf",        [2, 10, 1, 10]),
-                ("innerBottomHalf",     [2, 10, 1, 10]),
-                ("innerMasterState",    [2, 10, 3, 10]),
-                ("innerDeviceInfo",     [4, 10, 3, 10]),
-                ("innerFrameInfo",      [4, 10, 5, 10])]:
+            ("innerTop",            [2, 10, 1, 10]),
+            ("innerMiddle",         [1, 10, 1, 10]),
+            ("innerBottom",         [1, 10, 1, 10]),
+            ("innerMasterState",    [2, 10, 3, 10]),
+            ("innerDeviceInfo",     [4, 10, 3, 10]),
+            ("innerFrameInfo",      [4, 10, 5, 10]),
+            ("innerSlaveInfo",     [1, 10, 2, 10])]:
             self.MasterStateSizer[key] = wx.FlexGridSizer(cols=attr[0], hgap=attr[1], rows=attr[2], vgap=attr[3])
 
-        self.UpdateButton = wx.Button(self, label=_('Update'))
-        self.UpdateButton.Bind(wx.EVT_BUTTON, self.OnButtonClick)
-
-        for key, label in [
-                ('masterState', 'EtherCAT Master State'),
-                ('deviceInfo', 'Ethernet Network Card Information'),
-                ('frameInfo', 'Network Frame Information')]:
+        self.MSUpdateButton = wx.Button(self, label=_("Update"))
+        self.MSUpdateButton.Bind(wx.EVT_BUTTON, self.OnMSUpdateButtonClick)
+        self.SIUpdateButton = wx.Button(self, label=_("Update"))
+        self.SIUpdateButton.Bind(wx.EVT_BUTTON, self.OnSIUpdateButtonClick)
+        
+        for key, label in [                
+            ("masterState", "EtherCAT Master State"),
+            ("deviceInfo", "Ethernet Network Card Information"),
+            ("frameInfo", "Network Frame Information"),
+            ("slaveInfo", "Slave Information")]: 
             self.StaticBox[key] = wx.StaticBox(self, label=_(label))
             self.MasterStateSizer[key] = wx.StaticBoxSizer(self.StaticBox[key])
 
@@ -2114,10 +2709,12 @@
 
         # ----------------------- Network Frame Information -----------------------------------------------
         for key, label in [
-                ('Tx frame rate [1/s]', 'Tx Frame Rate [1/s]:'),
-                ('Rx frame rate [1/s]', 'Tx Rate [kByte/s]:'),
-                ('Loss rate [1/s]', 'Loss Rate [1/s]:'),
-                ('Frame loss [%]', 'Frame Loss [%]:')]:
+            ("Tx frame rate [1/s]", "Tx Frame Rate [1/s]:"), 
+            ("Tx rate [KByte/s]", "Tx Rate [KByte/s]:"), 
+            ("Rx frame rate [1/s]", "Rx Frame Rate [1/s]:"), 
+            ("Rx rate [KByte/s]", "Rx Rate [KByte/s]:"), 
+            ("Loss rate [1/s]", "Loss Rate [1/s]:"),
+            ("Frame loss [%]", "Frame Loss [%]:")]:
             self.StaticText[key] = wx.StaticText(self, label=_(label))
             self.MasterStateSizer['innerFrameInfo'].Add(self.StaticText[key])
             self.TextCtrl[key] = {}
@@ -2127,21 +2724,30 @@
 
         self.MasterStateSizer['frameInfo'].AddSizer(self.MasterStateSizer['innerFrameInfo'])
 
+        # ------------------------------- Slave Information  -----------------------------------------------
+        self.SITreeListCtrl = SITreeListCtrl(self, self.Controler) 
+        self.MasterStateSizer["innerSlaveInfo"].AddMany([self.SIUpdateButton,
+                                                               self.SITreeListCtrl])
+        self.MasterStateSizer["slaveInfo"].AddSizer(
+                self.MasterStateSizer["innerSlaveInfo"]) 
+
         # --------------------------------- Main Sizer ----------------------------------------------------
+        self.MasterStateSizer["main"].Add(self.MSUpdateButton)
         for key, sub, in [
-                ('innerTopHalf', ['masterState', 'deviceInfo']),
-                ('innerBottomHalf', ['frameInfo']),
-                ('innerMain', ['innerTopHalf', 'innerBottomHalf'])
-        ]:
+            ("innerTop", [
+                    "masterState", "deviceInfo"]),
+            ("innerMiddle", [
+                    "frameInfo"]),
+            ("innerBottom", [
+                    "slaveInfo"]),
+            ("main", [
+                    "innerTop", "innerMiddle", "innerBottom"])]:
             for key2 in sub:
                 self.MasterStateSizer[key].AddSizer(self.MasterStateSizer[key2])
 
-        self.MasterStateSizer['main'].AddSizer(self.UpdateButton)
-        self.MasterStateSizer['main'].AddSizer(self.MasterStateSizer['innerMain'])
-
-        self.SetSizer(self.MasterStateSizer['main'])
-
-    def OnButtonClick(self, event):
+        self.SetSizer(self.MasterStateSizer["main"])
+    
+    def OnMSUpdateButtonClick(self, event):
         """
         Handle the event of the 'Update' button.
         Update the data of the master state.
@@ -2159,3 +2765,818 @@
                         self.TextCtrl[key].SetValue(self.MasterState[key][0])
         else:
             self.Controler.CommonMethod.CreateErrorDialog(_('PLC not connected!'))
+
+    def OnSIUpdateButtonClick(self, event):
+        """
+        Handle the event of the radio box in the slave information 
+        @param event: wx.EVT_RADIOBOX object
+        """
+        if self.Controler.GetCTRoot()._connector is not None:
+            self.SITreeListCtrl.UpdateSI()
+        
+        else :
+            self.Controler.CommonMethod.CreateErrorDialog('PLC not connected!')
+    
+
+#-------------------------------------------------------------------------------
+#                    For Slave Information  Panel
+#------------------------------------------------------------------------------- 
+class SITreeListCtrl(wx.Panel):
+    
+    EC_Addrs = ["0x0300", "0x0302", "0x0304", "0x0306", "0x0301", "0x0303", "0x0305", 
+                "0x0307", "0x0308", "0x0309", "0x030A", "0x030B", "0x030C", "0x030D",
+                "0x0310", "0x0311", "0x0312", "0x0313", "0x0442", "0x0443"]
+
+    def __init__(self, parent, controler):
+        """
+        Constructor
+        @param parent: Reference to the MasterStatePanel class
+        @param Controler: _EthercatCTN class in EthercatMaster.py
+        """
+
+        wx.Panel.__init__(self, parent, -1, size=wx.Size(750, 350))
+
+        self.Controler=controler
+        
+        self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=wx.Size(750,350), 
+                                                            style=wx.TR_HAS_BUTTONS
+                                                            |wx.TR_HIDE_ROOT
+                                                            |wx.TR_ROW_LINES
+                                                            |wx.TR_COLUMN_LINES
+                                                            |wx.TR_FULL_ROW_HIGHLIGHT)
+        for label, width in [
+                ("name",        400),
+                ("position",    100),
+                ("state",       100),
+                ("error",       100)]:
+            self.Tree.AddColumn(label, width=width)
+        
+        self.Tree.SetMainColumn(0)
+        
+    def UpdateSI(self):
+        """
+        Update the data of the slave information.
+        """
+        position, not_used, state, not_used, name = range(5)
+        
+        slave_node = []
+        slave_info_list = []
+        error_counter= []
+        
+        # get slave informations (name, position, state)
+        slaves_infos = self.Controler.CommonMethod.GetSlaveStateFromSlave()
+        slave_info_lines = slaves_infos.splitlines()
+        
+        for line in slave_info_lines:
+            slave_info_list.append(line.split(None,4))
+
+        slave_num = len(slave_info_lines)
+        
+        reg_info = []
+        for ec in self.EC_Addrs:
+            reg_info.append(ec + ",0x001")
+        
+        # get error counts of slaves
+        err_count_list = self.Controler.CommonMethod.MultiRegRead(slave_num, reg_info)
+                
+        self.Tree.DeleteAllItems()
+        
+        root = self.Tree.AddRoot("") 
+        ec_list_idx = 0
+
+        for slave_idx in range(slave_num):
+            slave_node = self.Tree.AppendItem(root, "")
+            
+            # set name, postion, state 
+            col_num = 0 
+            for info_idx in [name, position, state]:
+                self.Tree.SetItemText(slave_node, 
+                                      slave_info_list[slave_idx][info_idx], col_num)
+                col_num += 1
+
+            error_counter = {}
+            ec_idx = 0
+            
+            # set error counter's name and default value 
+            for ec, sub_ecs in [("Port Error Counters 0/1/2/3",[
+                                    "Invaild Frame Counter 0/1/2/3",
+                                    "RX Error Counter 0/1/2/3"]),
+                                ("Forward RX Error Counter 0/1/2/3", []),       
+                                ("ECAT Processing Unit Error Counter", []),
+                                ("PDI Error Counter", []),
+                                ("Lost Link Counter 0/1/2/3", []),
+                                ("Watchdog Counter Process Data", []),
+                                ("Watchdog Counter PDI", [])]:
+                ec_sub_idx = 0
+                ec_name = ec
+                tree_node = self.Tree.AppendItem(slave_node, "%s" % ec)
+                
+                if ec_name.find("0/1/2/3") > 0:
+                    num_ports = 4
+                    err_count = [0, 0, 0, 0]
+                else:
+                    num_ports = 1
+                    err_count = [0]
+
+                error_counter[(ec_idx, ec_sub_idx)] = {
+                        "name": ec_name,
+                        "tree_node": tree_node,
+                        "num_ports": num_ports,
+                        "err_count": err_count}
+
+                for sub_ec in sub_ecs:
+                    ec_sub_idx += 1
+                    ec_name = sub_ec
+                    tree_node = self.Tree.AppendItem(\
+                        error_counter[(ec_idx, 0)]["tree_node"], 
+                            "%s" % sub_ec)
+                    
+                    if ec_name.find("0/1/2/3") > 0:
+                        num_ports = 4
+                        err_count = [0, 0, 0, 0]
+                    else:
+                        num_ports = 1
+                        err_count = [0]
+
+                    error_counter[(ec_idx, ec_sub_idx)] = {
+                            "name": ec_name,
+                            "tree_node": tree_node, 
+                            "num_ports": num_ports,
+                            "err_count": err_count}
+
+                    for port_num in range(num_ports):
+                        try:
+                            error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] += \
+                                    int(err_count_list[ec_list_idx].split(",")[2], 16)
+                        except:
+                            error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] = -1
+
+                        ec_list_idx += 1
+                
+                if ec_sub_idx > 0:
+                    for port_num in range(num_ports):
+                        err_sum = 0
+                        for sub_idx in range(1, ec_sub_idx+1):
+                            err_sum += error_counter[(ec_idx, sub_idx)]\
+                                                ["err_count"][port_num]
+                        error_counter[(ec_idx, 0)]["err_count"][port_num] = err_sum
+                        
+                else:
+                    for port_num in range(num_ports):
+                        try:
+                            error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] += \
+                                    int(err_count_list[ec_list_idx].split(",")[2], 16)
+                        except:
+                            error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] = -1
+                        ec_list_idx += 1
+
+                ec_idx += 1
+            
+            # set texts in "error" column. 
+            ec_info_list = error_counter.items()
+            ec_info_list.sort()
+            
+            err_checker = "none"
+           
+            for (idx, sub_idx), ec_info in ec_info_list:
+                ec_text = ""
+                for port_num in range(ec_info["num_ports"]):
+                    if ec_info["err_count"][port_num] != 0:
+                        err_checker = "occurred"
+
+                    if ec_info["err_count"][port_num] < 0:
+                        ec_text = "reg I/O error"
+                    else:
+                        ec_text = ec_text + "%d/" % ec_info["err_count"][port_num]
+
+                ec_text = ec_text.strip("/")
+                
+                self.Tree.SetItemText(ec_info["tree_node"], ec_text, col_num)
+            
+            self.Tree.SetItemText(slave_node, err_checker, col_num)
+
+class DCConfigPanel(wx.Panel):
+    def __init__(self, parent, controler):
+        """
+        Constructor
+        @param parent: Reference to the MasterStatePanel class
+        @param Controler: _EthercatCTN class in EthercatMaster.py
+        """
+
+        wx.Panel.__init__(self, parent, -1, size=wx.Size(750, 350))
+
+        self.Controler = controler
+        self.parent = parent
+
+        self.ESI_DC_Data = self.Controler.CommonMethod.LoadESIData()
+
+        # initialize SlaveStatePanel UI dictionaries
+        self.StaticBoxDic = {}
+        self.StaticTextDic = {}
+        self.TextCtrlDic = {}
+        self.ComboBoxDic = {}
+        self.CheckBoxDic = {}
+        self.RadioButtonDic = {}
+        OperationModeComboList = []
+        Sync1CycleComboList = []
+        
+        for ESI_Data in self.ESI_DC_Data:
+            OperationModeComboList.append(ESI_Data["desc"])
+
+        UnitComboList = [ "/100", "/ 50", "/ 40", "/ 30", "/ 25", "/ 20", "/16",
+            "/ 10", "/ 8", "/ 5", "/ 4", "/ 3", "/ 2", "x 1", "x 2", "x 3", "x 4", 
+            "x 5", "x 8", "x 10", "x 16", "x 20", "x 25", "x 30", "x 40", "x 50", 
+            "x 100"
+        ]
+
+        UnitComboListPlus = [ "/100", "/ 50", "/ 40", "/ 30", "/ 25", "/ 20", "/16",
+            "/ 10", "/ 8", "/ 5", "/ 4", "/ 3", "/ 2", "x 0", "x 1", "x 2", "x 3", 
+            "x 4", "x 5", "x 8", "x 10", "x 16", "x 20", "x 25", "x 30", "x 40", 
+            "x 50", "x 100"
+        ]
+
+        for i in range(1024):
+            Sync1CycleComboList.append("x " + str(i + 1))
+        
+        # iniitalize BoxSizer and FlexGridSizer
+        self.SizerDic = {
+            "DCConfig_main_sizer" : wx.BoxSizer(wx.VERTICAL),
+            "DCConfig_inner_main_sizer" : wx.FlexGridSizer(cols=1, hgap=50, rows=2, vgap=10),
+            "CyclicMode_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5),
+            "SyncMode_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5),
+            "OperationMode_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=100, rows=2, vgap=10),
+            "CheckEnable_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=1, vgap=10),
+            "Sync0_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=15, rows=3, vgap=10),
+            "Sync0_CycleTimeSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=2, vgap=5),
+            "Sync0_ShiftTimeSizer" : wx.FlexGridSizer(cols=2, hgap=20, rows=2, vgap=5),
+            "Sync1_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=15, rows=3, vgap=10),
+            "Sync1_CycleTimeSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=2, vgap=5),
+            "Sync1_ShiftTimeSizer" : wx.FlexGridSizer(cols=2, hgap=20, rows=2, vgap=5)
+        }
+        
+        # initialize StaticBox and StaticBoxSizer
+        for box_name, box_label in [
+                ("CyclicModeBox", "Cyclic Mode"),
+                ("Sync0Box", "Sync0"),
+                ("Sync0CycleTimeBox", "Cycle Time (us):"),
+                ("Sync0ShiftTimeBox", "Shift Time (us):"),
+                ("Sync1Box", "Sync1"),
+                ("Sync1CycleTimeBox", "Cycle Time (us):"),
+                ("Sync1ShiftTimeBox", "Shift Time (us):")
+            ]:
+            self.StaticBoxDic[box_name] = wx.StaticBox(self, label=_(box_label))
+            self.SizerDic[box_name] = wx.StaticBoxSizer(self.StaticBoxDic[box_name])  
+        
+        for statictext_name, statictext_label in [
+                ("MainLabel", "Distributed Clock"),
+                ("OperationModeLabel", "Operation Mode:"),
+                ("SyncUnitCycleLabel", "Sync Unit Cycle (us)"),
+                ("Sync0ShiftTimeUserDefinedLabel", "User Defined"),
+                ("Sync1ShiftTimeUserDefinedLabel", "User Defined"),
+                ("BlankObject", ""),
+                ("BlankObject1", "")
+            ]:
+            self.StaticTextDic[statictext_name] = wx.StaticText(self, label=_(statictext_label))
+
+        for textctl_name in [
+                ("SyncUnitCycle_Ctl"),
+                ("Sync0CycleTimeUserDefined_Ctl"),
+                ("Sync0ShiftTimeUserDefined_Ctl"),
+                ("Sync1CycleTimeUserDefined_Ctl"),
+                ("Sync1ShiftTimeUserDefined_Ctl"),
+            ]:
+            self.TextCtrlDic[textctl_name] = wx.TextCtrl(
+                self, size=wx.Size(130, 24), style=wx.TE_READONLY)
+
+        for checkbox_name, checkbox_label in [
+                ("DCEnable", "Enable"),
+                ("Sync0Enable", "Enable Sync0"),
+                ("Sync1Enable", "Enable Sync1")
+            ]:
+            self.CheckBoxDic[checkbox_name] = wx.CheckBox(self, -1, checkbox_label)
+
+        for combobox_name, combobox_list, size in [
+                ("OperationModeChoice", OperationModeComboList, 250),
+                ("Sync0UnitCycleChoice", UnitComboList, 130),
+                ("Sync1UnitCycleChoice", UnitComboList, 130)
+            ]:
+            self.ComboBoxDic[combobox_name] = wx.ComboBox(self, size=wx.Size(size, 24), 
+                choices = combobox_list, style = wx.CB_DROPDOWN | wx.CB_READONLY)
+
+        for radiobutton_name, radiobutton_label in [
+                ("Sync0CycleTimeUnitRadioButton", "Sync Unit Cycle"),
+                ("Sync0CycleTimeUserDefinedRadioButton", "User Defined"),
+                ("Sync1CycleTimeUnitRadioButton", "Sync Unit Cycle"),
+                ("Sync1CycleTimeUserDefinedRadioButton", "User Defined")
+            ]:
+            self.RadioButtonDic[radiobutton_name] = wx.RadioButton(
+                self, label = radiobutton_label, style = wx.RB_SINGLE)
+
+
+        self.ApplyButton = wx.Button(self, label="Apply")
+
+        # binding event
+        self.Bind(wx.EVT_CHECKBOX, self.CheckDCEnable, self.CheckBoxDic["DCEnable"])
+        #self.Bind(wx.EVT_COMBOBOX, self.SelectOperationMode, self.ComboBoxDic["OperationModeChoice"])
+        #self.Bind(wx.EVT_COMBOBOX, self.SelectUnitCycle, self.ComboBoxDic["Sync0UnitChoice"])
+        self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync0CycleTime, 
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"])
+        self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync0CycleTime, 
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"])
+        self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync1CycleTime, 
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"])
+        self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync1CycleTime, 
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"])
+        self.Bind(wx.EVT_CHECKBOX, self.CheckSync0Enable, self.CheckBoxDic["Sync0Enable"])
+        self.Bind(wx.EVT_CHECKBOX, self.CheckSync1Enable, self.CheckBoxDic["Sync1Enable"])
+        self.Bind(wx.EVT_BUTTON, self.OnClickApplyButton, self.ApplyButton)
+
+        # sync1 shifttime box contents
+        self.SizerDic["Sync1_ShiftTimeSizer"].AddMany([
+            self.StaticTextDic["Sync1ShiftTimeUserDefinedLabel"],
+            self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"]
+        ])
+
+        # sync1 shifttime box
+        self.SizerDic["Sync1ShiftTimeBox"].Add(self.SizerDic["Sync1_ShiftTimeSizer"])
+        
+        # sync1 cycletime box contents
+        self.SizerDic["Sync1_CycleTimeSizer"].AddMany([
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"], 
+            self.ComboBoxDic["Sync1UnitCycleChoice"],
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"],
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"]
+        ])
+
+        # sync0 cycletime box
+        self.SizerDic["Sync1CycleTimeBox"].Add(self.SizerDic["Sync1_CycleTimeSizer"])
+
+        self.SizerDic["Sync1_InnerSizer"].AddMany([
+            self.CheckBoxDic["Sync1Enable"], self.SizerDic["Sync1CycleTimeBox"], 
+            self.SizerDic["Sync1ShiftTimeBox"]
+        ])
+
+        # sync1 box
+        self.SizerDic["Sync1Box"].Add(self.SizerDic["Sync1_InnerSizer"])
+
+        # sync0 shifttime box contents
+        self.SizerDic["Sync0_ShiftTimeSizer"].AddMany([
+            self.StaticTextDic["Sync0ShiftTimeUserDefinedLabel"],
+            self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"]
+        ])
+
+        # sync0 shifttime box
+        self.SizerDic["Sync0ShiftTimeBox"].Add(self.SizerDic["Sync0_ShiftTimeSizer"])
+        
+        # sync0 cycletime box contents
+        self.SizerDic["Sync0_CycleTimeSizer"].AddMany([
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"], 
+            self.ComboBoxDic["Sync0UnitCycleChoice"],
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"],
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"]
+        ])
+
+        # sync0 cycletime box
+        self.SizerDic["Sync0CycleTimeBox"].Add(self.SizerDic["Sync0_CycleTimeSizer"])
+
+        self.SizerDic["Sync0_InnerSizer"].AddMany([
+            self.CheckBoxDic["Sync0Enable"], self.SizerDic["Sync0CycleTimeBox"], 
+            self.SizerDic["Sync0ShiftTimeBox"]
+        ])
+
+        # sync0 box
+        self.SizerDic["Sync0Box"].Add(self.SizerDic["Sync0_InnerSizer"])
+
+        # sync0, sync1 box
+        self.SizerDic["SyncMode_InnerSizer"].AddMany([
+            self.SizerDic["Sync0Box"], self.SizerDic["Sync1Box"]
+        ])
+
+        # CyclicMode Box
+        self.SizerDic["CheckEnable_InnerSizer"].AddMany([
+            self.StaticTextDic["SyncUnitCycleLabel"], 
+            self.TextCtrlDic["SyncUnitCycle_Ctl"]
+        ])
+
+        self.SizerDic["OperationMode_InnerSizer"].AddMany([
+            self.StaticTextDic["OperationModeLabel"], 
+            self.ComboBoxDic["OperationModeChoice"],
+            self.CheckBoxDic["DCEnable"], self.SizerDic["CheckEnable_InnerSizer"]
+        ])
+
+        self.SizerDic["CyclicMode_InnerSizer"].AddMany([
+            self.SizerDic["OperationMode_InnerSizer"], 
+            self.SizerDic["SyncMode_InnerSizer"]
+        ])
+
+        self.SizerDic["CyclicModeBox"].Add(self.SizerDic["CyclicMode_InnerSizer"])
+
+        # Main Sizer
+        self.SizerDic["DCConfig_inner_main_sizer"].AddMany([
+            self.StaticTextDic["MainLabel"], self.ApplyButton,
+            self.SizerDic["CyclicModeBox"]
+        ])
+        
+        self.SizerDic["DCConfig_main_sizer"].Add(self.SizerDic["DCConfig_inner_main_sizer"])
+        
+        self.SetSizer(self.SizerDic["DCConfig_main_sizer"])
+        
+        self.Centre()
+
+        self.UIOnOffSet(False)
+        self.LoadProjectDCData()
+
+    def UIOnOffSet(self, activate):
+        if activate :
+            for object in self.RadioButtonDic:
+                self.RadioButtonDic[object].Enable()
+
+            for object in self.ComboBoxDic:
+                if object == "OperationModeChoice":
+                    continue
+                self.ComboBoxDic[object].Enable()
+
+            for object in self.TextCtrlDic:
+                if object in ["SyncUnitCycle_Ctl", "InputReference_Ctl"]:
+                    continue
+                self.TextCtrlDic[object].Enable()
+
+            for object in self.CheckBoxDic:
+                if object == "DCEnable":
+                    continue
+                self.CheckBoxDic[object].Enable()
+
+        # initial set or DC enable uncheck
+        else :
+            for object in self.RadioButtonDic:
+                self.RadioButtonDic[object].Disable()
+
+            for object in self.ComboBoxDic:
+                if object == "OperationModeChoice":
+                    continue
+                self.ComboBoxDic[object].Disable()
+
+            for object in self.TextCtrlDic:
+                if object == "SyncUnitCycle_Ctl":
+                    continue
+                self.TextCtrlDic[object].Disable()
+
+            for object in self.CheckBoxDic:
+                if object == "DCEnable":
+                    continue
+                self.CheckBoxDic[object].Disable()
+
+            for data in self.ESI_DC_Data:
+                index = self.Controler.ExtractHexDecValue(data["assign_activate"])
+                if index == 0:
+                    config_name = data["desc"]
+                    self.ComboBoxDic["OperationModeChoice"].SetStringSelection(config_name)
+
+    def CheckSync0Enable(self, evt):
+        if evt.GetInt():
+            self.ComboBoxDic["Sync0UnitCycleChoice"].Enable()
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].Enable()
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].Enable()
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Enable()
+            self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].Enable()
+        else :
+            self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].Disable()
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].Disable()
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+            self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].Disable()
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue("")
+            self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue("")
+
+    def CheckSync1Enable(self, evt):
+        if evt.GetInt():
+            self.ComboBoxDic["Sync1UnitCycleChoice"].Enable()
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Enable()
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Enable()
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Enable()
+            self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Enable()
+        else :
+            self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+            self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue("")
+            self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue("")
+
+    def CheckDCEnable(self, evt):
+        ns_mode = 1
+        task_cycle_ns = self.GetInterval(ns_mode)
+        sync0_cycle_factor = None
+        sync1_cycle_factor = None
+
+        #task_cycle_ns = self.Controler.GetCTRoot()._Ticktime
+        if (task_cycle_ns > 0):
+            self.UIOnOffSet(evt.GetInt())
+
+            if evt.GetInt():
+                # default select DC enable sync0
+                default_list_num = 0
+                config_name = self.ESI_DC_Data[default_list_num]["desc"]
+                assign_act = self.ESI_DC_Data[default_list_num]["assign_activate"]
+                sync0_cycle_time_ns = self.ESI_DC_Data[default_list_num]["cycletime_sync0"]
+                if sync0_cycle_time_ns == 0 :
+                    sync0_cycle_factor = self.ESI_DC_Data[default_list_num]["cycletime_sync0_factor"]
+                sync0_shift_time_ns = self.ESI_DC_Data[default_list_num]["shifttime_sync0"]
+                sync1_cycle_time_ns = self.ESI_DC_Data[default_list_num]["cycletime_sync1"]
+                if sync1_cycle_time_ns == 0 :
+                    sync1_cycle_factor = self.ESI_DC_Data[default_list_num]["cycletime_sync1_factor"]
+                sync1_shift_time_ns = self.ESI_DC_Data[default_list_num]["shifttime_sync1"]
+
+                cal_assign_act = self.Controler.ExtractHexDecValue(assign_act)
+                sync0_cycle_time_us = str(int(sync0_cycle_time_ns) / 1000)
+                sync0_shift_time_us = str(int(sync0_shift_time_ns) / 1000)
+                sync1_cycle_time_us = str(int(sync1_cycle_time_ns) / 1000)
+                sync1_shift_time_us = str(int(sync1_shift_time_ns) / 1000)
+
+                task_cycle_to_us = str(int(task_cycle_ns) / 1000)
+
+                # DC sync0 mode
+                if cal_assign_act == 768:
+                    # Disable About Sync1 Objects
+                    self.CheckBoxDic["Sync1Enable"].SetValue(False)
+                    self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+                    self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+                    self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+                    self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+                    self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+
+                else :
+                    self.CheckBoxDic["Sync1Enable"].SetValue(True)
+                    if sync1_cycle_factor is not None:
+                        self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(True)
+                        self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+                        self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+                        self.SetSyncUnitCycle(sync1_cycle_factor, 
+                            self.ComboBoxDic["Sync1UnitCycleChoice"])
+                    else :
+                        self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+                        self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(True)
+                        self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+                        self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue(sync1_cycle_time_us)
+
+                    self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue(sync1_shift_time_us)
+
+                # Set Sync0 Objects
+                self.CheckBoxDic["Sync0Enable"].SetValue(True)
+                if sync0_cycle_factor is not None:
+                    self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(True)
+                    self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+                    self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+                    self.SetSyncUnitCycle(sync0_cycle_factor, 
+                        self.ComboBoxDic["Sync0UnitCycleChoice"])
+                else :
+                    self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+                    self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(True)
+                    self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+                    self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue(sync0_cycle_time_us)
+
+                self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue(sync0_shift_time_us)
+
+                self.ComboBoxDic["OperationModeChoice"].SetStringSelection(config_name)
+                self.TextCtrlDic["SyncUnitCycle_Ctl"].SetValue(task_cycle_to_us)
+            else :
+                self.CheckBoxDic["Sync0Enable"].SetValue(False)
+                self.CheckBoxDic["Sync1Enable"].SetValue(False)
+                self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+                self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+                self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+                self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+                self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue("")
+                self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue("")
+                self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue("")
+                self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue("")
+
+        else :
+            self.UIOnOffSet(False)
+            #error_str = "DC Enable is not possble, please set task interval"
+            error_str = "Can't Set DC Enable"
+            self.Controler.CommonMethod.CreateErrorDialog(error_str)
+
+    def SetSyncUnitCycle(self, factor, object):
+        # factor > 0 ==> * factor, factor < 0 ==> / factor
+        factor_to_int = int(factor)
+        if factor_to_int > 0:
+            lists = object.GetStrings()
+
+            for token in lists:
+                temp = token.split(" ")
+                if (temp[0] == "x") and (int(temp[1]) == factor_to_int):
+                    object.SetStringSelection(token)
+                    return True
+
+        else : 
+            lists = object.GetStrings()
+
+            for token in lists:
+                temp = token.split(" ")
+                if (temp[0] == "/") and (int(temp[1]) == factor_to_int):
+                    object.SetStringSelection(token)
+                    return True
+
+        return False
+
+    def GetInterval(self, mode):
+        project_infos = self.Controler.GetCTRoot().GetProjectInfos()
+        for project_info_list in project_infos["values"]:
+            if project_info_list["name"] == "Resources" :
+                token = project_info_list["values"][0]["tagname"]
+       
+        tasks, instances = self.Controler.GetCTRoot().GetEditedResourceInfos(token)
+        try:
+            task_cycle_ns = self.ParseTime(tasks[0]["Interval"])
+        except :
+            task_cycle_ns = 0
+        task_cycle_us = int(task_cycle_ns) / 1000
+
+        # mode == 1 ==> return ns
+        # mode == 2 ==> return us
+
+        if mode == 1:
+            return task_cycle_ns
+        if mode == 2:
+            return str(task_cycle_us)
+
+    def ParseTime(self, input):
+        # input example : 't#1ms'
+        # temp.split('#') -> ['t', '1ms']
+        temp = input.split('#')
+         
+        # temp[1] : '1ms'
+        # temp[-2:] : 'ms'
+        # temp[:-2] : '1'
+        if temp[1][-2:] == "ms":
+           # convert nanosecond unit
+           result = int(temp[1][:-2]) * 1000000
+        elif temp[1][-2:] == "us":
+           result = int(temp[1][:-2]) * 1000
+
+        return str(result)
+
+    def SelectSync0CycleTime(self, evt):
+        selected_object = evt.GetEventObject()
+
+        if selected_object.GetLabel() == "User Defined" :
+            self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Enable()
+            self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+        elif selected_object.GetLabel() == "Sync Unit Cycle" :
+            self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+            self.ComboBoxDic["Sync0UnitCycleChoice"].Enable()
+            self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+
+    def SelectSync1CycleTime(self, evt):
+        selected_object = evt.GetEventObject()
+
+        if selected_object.GetLabel() == "User Defined" :
+            self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Enable()
+            self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+        elif selected_object.GetLabel() == "Sync Unit Cycle" :
+            self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+            self.ComboBoxDic["Sync1UnitCycleChoice"].Enable()
+            self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+
+    def GetCycle(self, period, section):
+        temp = section.split(" ")
+
+        if temp[0] == "x":
+            result = int(period) * int(temp[1])
+        elif temp[0] == "/" :
+            result = int(period) / int(temp[1])
+        else :
+            result = ""
+
+        return result
+
+    def OnClickApplyButton(self, evt):
+        us_mode = 2
+        dc_enable = self.CheckBoxDic["DCEnable"].GetValue()
+        dc_desc = self.ComboBoxDic["OperationModeChoice"].GetStringSelection()
+        dc_assign_activate = self.ESI_DC_Data[0]["assign_activate"]
+        dc_assign_activate_mod = dc_assign_activate.split('x')[1]
+
+        if self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].GetValue():
+            temp = self.ComboBoxDic["Sync0UnitCycleChoice"].GetStringSelection()
+            dc_sync0_cycle = "1_" + str(self.GetCycle(self.GetInterval(us_mode), temp))
+        elif  self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].GetValue():
+            dc_sync0_cycle = "2_" + self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].GetValue()
+        else :
+            dc_sync0_cycle = ""
+
+        if self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].GetValue():
+            temp = self.ComboBoxDic["Sync1UnitCycleChoice"].GetStringSelection()
+            dc_sync1_cycle = "1_" + self.GetCycle(self.GetInterval(us_mode), temp)
+        elif  self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].GetValue():
+            dc_sync1_cycle = "2_" + self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].GetValue()
+        else :
+            dc_sync1_cycle = ""
+
+        dc_sync0_shift = self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].GetValue()
+        dc_sync1_shift = self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].GetValue()
+
+        self.Controler.BaseParams.setDC_Enable(dc_enable)
+        self.Controler.BaseParams.setDC_Desc(dc_desc)
+        self.Controler.BaseParams.setDC_Assign_Activate(dc_assign_activate_mod)
+        if dc_sync0_cycle:
+            self.Controler.BaseParams.setDC_Sync0_Cycle_Time(dc_sync0_cycle)
+        if dc_sync0_shift:
+            self.Controler.BaseParams.setDC_Sync0_Shift_Time(dc_sync0_shift)
+        if dc_sync1_cycle:
+            self.Controler.BaseParams.setDC_Sync1_Cycle_Time(dc_sync1_cycle)
+        if dc_sync1_shift:
+            self.Controler.BaseParams.setDC_Sync1_Shift_Time(dc_sync1_shift)
+        project_infos = self.Controler.GetCTRoot().CTNRequestSave()
+
+    def GetSymbol(self, period, cycle):
+        cmp1 = int(period)
+        cmp2 = int(cycle)
+
+        if cmp1 == cmp2 :
+            return "x 1"
+        elif cmp2 > cmp1 :
+            temp = cmp2 / cmp1
+            result = "x " + str(temp)
+        else :
+            temp = cmp1 / cmp2
+            result = "/ " + str(temp)
+
+        return result
+
+    def SetSyncCycle(self, period, sync0_cycle, sync1_cycle):
+        if sync0_cycle != "None":
+            self.CheckBoxDic["Sync0Enable"].SetValue(True)               
+            temp = sync0_cycle.split("_")
+            if temp[0] == "1":
+                symbol = self.GetSymbol(period, temp[1])
+                self.ComboBoxDic["Sync0UnitCycleChoice"].SetStringSelection(symbol)
+                self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+                self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(True)
+            else :
+                self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue(temp[1])
+                self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+                self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(True)
+
+        if sync1_cycle != "None":
+            self.CheckBoxDic["Sync1Enable"].SetValue(True)
+            temp = sync1_cycle.split("_")
+            if temp[0] == "1":
+                symbol = self.GetSymbol(period, temp[1])
+                self.ComboBoxDic["Sync1UnitChoice"].SetStringSelection(symbol)
+                self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+                self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(True)
+            else :
+                self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue(temp[1])
+                self.ComboBoxDic["Sync1UnitChoice"].Disable()
+                self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(True)
+
+    def LoadProjectDCData(self):
+        ns_mode = 1
+        task_cycle_ns = self.GetInterval(ns_mode)
+        task_cycle_to_us = int(task_cycle_ns) / 1000
+        dc_enable = self.Controler.BaseParams.getDC_Enable()
+        dc_desc = self.Controler.BaseParams.getDC_Desc()
+        dc_assign_activate = self.Controler.BaseParams.getDC_Assign_Activate()
+        dc_sync0_cycle = self.Controler.BaseParams.getDC_Sync0_Cycle_Time()
+        dc_sync0_shift = self.Controler.BaseParams.getDC_Sync0_Shift_Time()
+        dc_sync1_cycle = self.Controler.BaseParams.getDC_Sync1_Cycle_Time()
+        dc_sync1_shift = self.Controler.BaseParams.getDC_Sync1_Shift_Time()
+
+        self.UIOnOffSet(dc_enable)
+
+        if dc_enable:
+            self.CheckBoxDic["DCEnable"].SetValue(dc_enable)
+            self.ComboBoxDic["OperationModeChoice"].SetStringSelection(dc_desc)
+            self.TextCtrlDic["SyncUnitCycle_Ctl"].SetValue(str(task_cycle_to_us))
+            self.SetSyncCycle(str(task_cycle_to_us), dc_sync0_cycle, dc_sync1_cycle)
+            if dc_sync0_shift != "None":
+                self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue(dc_sync0_shift)
+            if dc_sync1_shift != "None":
+                self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue(dc_sync1_shift)
+
+            if dc_assign_activate == "300":
+                self.CheckBoxDic["Sync1Enable"].SetValue(False)
+                self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+                self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+                self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+                self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+                self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+
+
+
+
+
+
+
+
+
+        
--- a/etherlab/EthercatCFileGenerator.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/EthercatCFileGenerator.py	Fri Dec 25 17:12:02 2020 +0000
@@ -70,6 +70,37 @@
     }
 """
 
+SLAVE_OUTPUT_PDO_DEFAULT_VALUE_BIT = """
+    {
+        uint8_t value[%(data_size)d];
+        if (ecrt_master_sdo_upload(master, %(slave)d, 0x%(index).4x, 0x%(subindex).2x, (uint8_t *)value, %(data_size)d, &result_size, &abort_code)) {
+            SLOGF(LOG_CRITICAL, "EtherCAT failed to get default value for output PDO in slave %(device_type)s at alias %(alias)d and position %(position)d. Error: %%ud", abort_code);
+            goto ecat_failed;
+        }
+        %(real_var)s = EC_READ_%(data_type)s((uint8_t *)value, %(subindex)d);
+    }
+"""
+
+
+SLAVE_INPUT_PDO_DEFAULT_VALUE = """
+    {
+        uint8_t value[%(data_size)d];
+        if (ecrt_master_sdo_upload(master, %(slave)d, 0x%(index).4x, 0x%(subindex).2x, (uint8_t *)value, %(data_size)d, &result_size, &abort_code)) {
+            SLOGF(LOG_CRITICAL, "EtherCAT failed to get default value for input PDO in slave %(device_type)s at alias %(alias)d and position %(position)d. Error: %%ud", abort_code);
+            goto ecat_failed;
+        }
+        %(real_var)s = EC_READ_%(data_type)s((uint8_t *)value);
+    }
+"""
+
+DC_VARIABLE ="""
+#define DC_ENABLE       %(dc_flag)d
+"""
+
+CONFIG_DC = """
+    ecrt_slave_config_dc (slave%(slave)d, 0x0%(assign_activate)ld, %(sync0_cycle_time)d, %(sync0_shift_time)d, %(sync1_cycle_time)d, %(sync1_shift_time)d);
+"""
+
 
 def ConfigureVariable(entry_infos, str_completion):
     entry_infos["data_type"] = DATATYPECONVERSION.get(entry_infos["var_type"], None)
@@ -192,10 +223,15 @@
             "pdos_configuration_declaration": "",
             "slaves_declaration": "",
             "slaves_configuration": "",
+            # add jblee
+            "slaves_input_pdos_default_values_extraction": "",
             "slaves_output_pdos_default_values_extraction": "",
             "slaves_initialization": "",
             "retrieve_variables": [],
             "publish_variables": [],
+            #-----------This Code templete for dc -------------------#
+            "dc_variable" : "",
+            "config_dc": ""
         }
 
         # Initialize variable storing variable mapping state
@@ -208,6 +244,9 @@
         # Initialize dictionary storing alias auto-increment position values
         alias = {}
 
+        # add jblee
+        slotNumber = 1
+        
         # Generating code for each slave
         for (slave_idx, slave_alias, slave) in self.Slaves:
             type_infos = slave.getType()
@@ -244,11 +283,14 @@
                 # If device support CanOpen over Ethernet, adding code for calling
                 # init commands when initializing slave in master code template strings
                 initCmds = []
+                
                 for initCmd in device_coe.getInitCmd():
                     initCmds.append({
                         "Index": ExtractHexDecValue(initCmd.getIndex()),
                         "Subindex": ExtractHexDecValue(initCmd.getSubIndex()),
-                        "Value": initCmd.getData().getcontent()})
+                        #"Value": initCmd.getData().getcontent()})
+                        "Value": int(initCmd.getData().text, 16)})
+                
                 initCmds.extend(slave.getStartupCommands())
                 for initCmd in initCmds:
                     index = initCmd["Index"]
@@ -314,12 +356,106 @@
                 pdos_index = []
                 exclusive_pdos = {}
                 selected_pdos = []
-                for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
-                                      [(pdo, "Outputs") for pdo in device.getRxPdo()]):
-
+
+                # add jblee
+                TxPdoData = []
+                RxPdoData = []
+                PdoData = []
+
+                # add jblee
+                if len(device.getTxPdo() + device.getRxPdo()) > 0:
+                    for pdo in device.getTxPdo():
+                        PdoData.append((pdo, "Inputs"))
+                    for pdo in device.getRxPdo():
+                        PdoData.append((pdo, "Outputs"))               
+
+                # mod jblee
+                #for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
+                #                      [(pdo, "Outputs") for pdo in device.getRxPdo()]):
+                #for pdo, pdo_type in (TxPdoData + RxPdoData):
+                data_files = os.listdir(self.Controler.CTNPath())
+                PDODataList = []
+                MDPData = []
+                RxPDOData = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getRxPDO()
+                TxPDOData = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getTxPDO()
+                PDOList = RxPDOData.split() + TxPDOData.split()
+                for PDOIndex in PDOList:
+                    if PDOIndex in ["RxPDO", "TxPDO", "None"]:
+                        continue
+                    PDODataList.append(int(PDOIndex, 0))
+
+                # add jblee for DC Configuration
+                dc_enable = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Enable()
+                sync0_cycle_time = 0
+                sync0_shift_time = 0
+                sync1_cycle_time = 0
+                sync1_shift_time = 0
+                if dc_enable :
+                    sync0_cycle_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync0_Cycle_Time()
+                    if sync0_cycle_token != "None":
+                        sync0_cycle_time = int(sync0_cycle_token.split("_")[1]) * 1000
+                    sync0_shift_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync0_Shift_Time()
+                    if sync0_shift_token != "None":
+                        sync0_shift_time = int(sync0_shift_token) * 1000
+                    sync1_cycle_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync1_Cycle_Time()
+                    if sync1_cycle_token != "None":
+                        sync1_cycle_time = int(sync1_cycle_token.split("_")[1]) * 1000
+                    sync1_shift_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync1_Shift_Time()
+                    if sync1_shift_token != "None":
+                        sync1_shift_time = int(sync1_shift_token) * 1000
+                    
+                    dc_config_data = {
+                        "slave" : slave_idx,
+                        "assign_activate" : int(self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Assign_Activate()),
+                        "sync0_cycle_time" : sync0_cycle_time,
+                        "sync0_shift_time" : sync0_shift_time,
+                        "sync1_cycle_time" : sync1_cycle_time,
+                        "sync1_shift_time" : sync1_shift_time,
+                    }
+
+                    if dc_enable and not str_completion["dc_variable"] :
+                        str_completion["dc_variable"] += DC_VARIABLE % {"dc_flag" : dc_enable}
+                    str_completion["config_dc"] += CONFIG_DC % dc_config_data
+
+                for data_file in data_files:
+                    slave_path = os.path.join(self.Controler.CTNPath(), data_file)
+                    if os.path.isdir(slave_path):
+                        CheckConfNodePath = os.path.join(slave_path, "baseconfnode.xml")
+                        confNodeFile = open(CheckConfNodePath, 'r')
+                        checklines = confNodeFile.readlines()
+                        confNodeFile.close()
+                        # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+                        # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema",
+                        #                           IEC_Channel="0", Name="EthercatSlave_0"/>]
+                        # checklines[1].split()[2] : IEC_Channel="0"
+                        # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+                        pos_check = int(checklines[1].split()[2].split("\"")[1])
+                        if slave_idx == pos_check:
+                            MDPDataFilePath = os.path.join(slave_path, "DataForMDP.txt")
+                            if os.path.isfile(MDPDataFilePath):
+                                MDPDataFile = open(MDPDataFilePath, 'r')
+                                MDPData = MDPDataFile.readlines()
+                                MDPDataFile.close()
+
+                        for MDPLine in MDPData:
+                            if MDPLine == "\n":
+                                continue
+                            module_pos = int(MDPLine.split()[-1])
+                            module = self.Controler.CTNParent.GetSelectModule(module_pos)
+                            for pdo in module.getTxPdo():
+                                PdoData.append((pdo, "Inputs"))
+                                PDODataList.append(ExtractHexDecValue(pdo.getIndex().getcontent()))
+                            for pdo in module.getRxPdo():
+                                PdoData.append((pdo, "Outputs"))
+                                PDODataList.append(ExtractHexDecValue(pdo.getIndex().getcontent()))
+
+                for pdo, pdo_type in PdoData:
                     pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
                     pdos_index.append(pdo_index)
 
+                    if PDODataList and (pdo_index in PDODataList):
+                        continue
+
                     excluded_list = pdo.getExclude()
                     if len(excluded_list) > 0:
                         exclusion_list = [pdo_index]
@@ -330,12 +466,14 @@
                         exclusion_scope = exclusive_pdos.setdefault(tuple(exclusion_list), [])
 
                         entries = pdo.getEntry()
+
                         pdo_mapping_match = {
                             "index": pdo_index,
                             "matching": 0,
                             "count": len(entries),
                             "assigned": pdo.getSm() is not None
                         }
+
                         exclusion_scope.append(pdo_mapping_match)
 
                         for entry in entries:
@@ -364,30 +502,57 @@
                         for pdo in exclusion_scope[start_excluding_index:]
                         if PdoAssign or not pdo["assigned"]])
 
-                for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
-                                      [(pdo, "Outputs") for pdo in device.getRxPdo()]):
-                    entries = pdo.getEntry()
-
+                # mod jblee
+                #for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
+                #                      [(pdo, "Outputs") for pdo in device.getRxPdo()]):
+                #for pdo, pdo_type in (TxPdoData + RxPdoData):
+                entry_check_list = []
+                index_padding = 1
+                for pdo, pdo_type in PdoData:
+                    entries = pdo.getEntry()                   
+                    
                     pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
                     if pdo_index in excluded_pdos:
                         continue
-
-                    pdo_needed = pdo_index in selected_pdos
-
-                    entries_infos = []
-
+                    if PDODataList and (pdo_index not in PDODataList):
+                        continue
+                    
+                    #pdo_needed = pdo_index in selected_pdos
+                    pdo_needed = pdo_index in PDODataList
+
+                    if len(MDPData) > 0:
+                        pdo_index += index_padding
+                        index_padding += 1
+
+                    entries_infos = []                   
                     for entry in entries:
                         index = ExtractHexDecValue(entry.getIndex().getcontent())
                         subindex = ExtractHexDecValue(entry.getSubIndex())
+
+                        # add jblee
+                        if len(MDPData) > 0:
+                            increse = self.Controler.CTNParent.GetMDPInfos(type_infos)                          
+                            if increse and index != 0:
+                                index += int(increse[0][2]) * slotNumber
+                                
                         entry_infos = {
                             "index": index,
                             "subindex": subindex,
                             "name": ExtractName(entry.getName()),
                             "bitlen": entry.getBitLen(),
                         }
+
+                        entry_infos.update(type_infos)
+                        #temp_data = "    {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos
+                        check_data = "{0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}" % entry_infos
+                        if entry_check_list and check_data in entry_check_list:
+                            if (entry_infos["index"] == 0) or (entry_infos["name"] == None):
+                                pass
+                            else: 
                         entry_infos.update(type_infos)
                         entries_infos.append("    {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos)
-
+                        entry_check_list.append(check_data)
+                        
                         entry_declaration = slave_variables.get((index, subindex), None)
                         if entry_declaration is not None and not entry_declaration["mapped"]:
                             pdo_needed = True
@@ -421,9 +586,26 @@
 
                             ConfigureVariable(entry_infos, str_completion)
 
-                            str_completion["slaves_output_pdos_default_values_extraction"] += \
-                                SLAVE_OUTPUT_PDO_DEFAULT_VALUE % entry_infos
-
+                            if entry_infos["data_type"] == "BIT" :
+                                str_completion["slaves_output_pdos_default_values_extraction"] += \
+                                    SLAVE_OUTPUT_PDO_DEFAULT_VALUE_BIT % entry_infos
+                            else :
+                                str_completion["slaves_output_pdos_default_values_extraction"] += \
+                                    SLAVE_OUTPUT_PDO_DEFAULT_VALUE % entry_infos
+
+                        elif pdo_type == "Inputs" and entry.getDataType() is not None and device_coe is not None:
+                            data_type = entry.getDataType().getcontent()
+                            entry_infos["dir"] = "I"
+                            entry_infos["data_size"] = max(1, entry_infos["bitlen"] / 8)
+                            entry_infos["data_type"] = DATATYPECONVERSION.get(data_type)
+                            entry_infos["var_type"] = data_type
+                            entry_infos["real_var"] = "slave%(slave)d_%(index).4x_%(subindex).2x_default" % entry_infos
+                            
+                            ConfigureVariable(entry_infos, str_completion)
+                            
+                            str_completion["slaves_input_pdos_default_values_extraction"] += \
+                                SLAVE_INPUT_PDO_DEFAULT_VALUE % entry_infos
+                    
                     if pdo_needed:
                         for excluded in pdo.getExclude():
                             excluded_index = ExtractHexDecValue(excluded.getcontent())
@@ -448,6 +630,9 @@
                              "entries_number": len(entries_infos),
                              "fixed": pdo.getFixed() is True})
 
+                    # for MDP
+                    slotNumber += 1
+            
                 if PdoConfig and PdoAssign:
                     dynamic_pdos = {}
                     dynamic_pdos_number = 0
@@ -541,8 +726,8 @@
 
                 pdo_offset = 0
                 entry_offset = 0
+                slotNumber = 1
                 for sync_manager_infos in sync_managers:
-
                     for pdo_infos in sync_manager_infos["pdos"]:
                         pdo_infos["offset"] = entry_offset
                         pdo_entries = pdo_infos["entries"]
@@ -564,17 +749,17 @@
                     pdos_infos[element] = "\n".join(pdos_infos[element])
 
                 str_completion["pdos_configuration_declaration"] += SLAVE_PDOS_CONFIGURATION_DECLARATION % pdos_infos
-
-            for (index, subindex), entry_declaration in slave_variables.iteritems():
-                if not entry_declaration["mapped"]:
-                    message = _("Entry index 0x{a1:.4x}, subindex 0x{a2:.2x} not mapped for device {a3}").\
-                              format(a1=index, a2=subindex, a3=type_infos["device_type"])
-                    self.Controler.GetCTRoot().logger.write_warning(_("Warning: ") + message + "\n")
-
-        for element in ["used_pdo_entry_offset_variables_declaration",
-                        "used_pdo_entry_configuration",
-                        "located_variables_declaration",
-                        "retrieve_variables",
+            
+            #for (index, subindex), entry_declaration in slave_variables.iteritems():
+            #    if not entry_declaration["mapped"]:
+            #        message = _("Entry index 0x%4.4x, subindex 0x%2.2x not mapped for device %s") % \
+            #                        (index, subindex, type_infos["device_type"])
+            #        self.Controler.GetCTRoot().logger.write_warning(_("Warning: ") + message + "\n")
+                    
+        for element in ["used_pdo_entry_offset_variables_declaration", 
+                        "used_pdo_entry_configuration", 
+                        "located_variables_declaration", 
+                        "retrieve_variables", 
                         "publish_variables"]:
             str_completion[element] = "\n".join(str_completion[element])
 
--- a/etherlab/EthercatCIA402Slave.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/EthercatCIA402Slave.py	Fri Dec 25 17:12:02 2020 +0000
@@ -201,11 +201,69 @@
             ("%%IW%s.402" % ".".join(map(str, self.GetCurrentLocation())),
              "location", "AXIS_REF", self.CTNName(), ""))
 
+    # add jblee
+    """
+    def LoadPDOSelectData(self):
+        ReadData = []
+        files = os.listdir(self.CTNPath())
+        filepath = os.path.join(self.CTNPath(), "DataForPDO.txt")
+        if os.path.isfile(filepath):
+            PDODataRead = open(filepath, 'r')
+            ReadData = PDODataRead.readlines()
+            PDODataRead.close()
+
+        if len(ReadData) > 1:
+            for data in ReadData[0].split() :
+                if data == "RxPDO":
+                    continue
+                self.SelectedRxPDOIndex.append(int(data, 0))
+
+            for data in ReadData[1].split() :
+                if data == "TxPDO":
+                    continue
+                self.SelectedTxPDOIndex.append(int(data, 0))
+    """
+
+    def LoadPDOSelectData(self):
+        RxPDOData = self.BaseParams.getRxPDO()
+        RxPDOs = []
+        if RxPDOData != "None":
+            RxPDOs = RxPDOData.split()
+        if RxPDOs :
+            for RxPDO in RxPDOs :
+                self.SelectedRxPDOIndex.append(int(RxPDO, 0))
+
+        TxPDOData = self.BaseParams.getTxPDO()
+        TxPDOs = []
+        if TxPDOData != "None":
+            TxPDOs = TxPDOData.split()
+        if TxPDOs :
+            for TxPDO in TxPDOs :
+                self.SelectedTxPDOIndex.append(int(TxPDO, 0))
+
+    def LoadDefaultPDOSet(self):
+        ReturnData = []
+        rx_pdo_entries = self.CommonMethod.GetRxPDOCategory()
+        if len(rx_pdo_entries):
+            for i in range(len(rx_pdo_entries)):
+                if rx_pdo_entries[i]['sm'] is not None:
+                    ReturnData.append(rx_pdo_entries[i]['pdo_index'])
+
+        tx_pdo_entries = self.CommonMethod.GetTxPDOCategory()
+        if len(tx_pdo_entries):
+            for i in range(len(tx_pdo_entries)):
+                if tx_pdo_entries[i]['sm'] is not None:
+                    ReturnData.append(tx_pdo_entries[i]['pdo_index'])
+
+        if ReturnData :
+            return ReturnData
+        else :
+            return [5632, 6656]
+        
     def CTNGenerate_C(self, buildpath, locations):
         current_location = self.GetCurrentLocation()
 
         location_str = "_".join(map(str, current_location))
-        slave_pos = self.GetSlavePos()
 
         # Open CIA402 node code template file
         plc_cia402node_filepath = os.path.join(os.path.split(__file__)[0],
@@ -213,160 +271,204 @@
         plc_cia402node_file = open(plc_cia402node_filepath, 'r')
         plc_cia402node_code = plc_cia402node_file.read()
         plc_cia402node_file.close()
-
-        # Init list of generated strings for each code template file section
-        fieldbus_interface_declaration = []
-        fieldbus_interface_definition = []
-        init_axis_params = []
-        extra_variables_retrieve = []
-        extra_variables_publish = []
-        extern_located_variables_declaration = []
-        entry_variables = []
-        init_entry_variables = []
-
-        # Fieldbus interface code sections
+        # HSAHN 150726
+        # add "default_variables_retrieve": [], "default_variables_publish": [],
+		# As PDO mapping object, it will add auto-complete code.
+        # add "modeofop_homing_method", "modeofop_computation_mode" by jblee
+        str_completion = {
+            "slave_pos": self.GetSlavePos(),
+            "location": location_str,
+            "MCL_headers": Headers,
+            "extern_located_variables_declaration": [],
+            "fieldbus_interface_declaration": [],
+            "fieldbus_interface_definition": [],
+            "entry_variables": [],
+            "init_axis_params": [],
+            "init_entry_variables": [],
+            "default_variables_retrieve": [],
+            "default_variables_publish": [],
+            "extra_variables_retrieve": [],
+            "extra_variables_publish": [],
+            "modeofop_homing_method": [],
+            "modeofop_computation_mode": []
+        }
+        
         for blocktype_infos in FIELDBUS_INTERFACE_GLOBAL_INSTANCES:
-            blocktype = blocktype_infos["blocktype"]
-            ucase_blocktype = blocktype.upper()
-            blockname = "_".join([ucase_blocktype, location_str])
-
-            extract_inputs = "\n".join([
-                """\
-                __SET_VAR(%s->, %s,, %s);""" % (blockname, input_name, input_value)
-                for (input_name, input_value) in
-                [("EXECUTE", "__GET_VAR(data__->EXECUTE)")] + [
-                    (input["name"].upper(),
-                     "__GET_VAR(data__->%s)" % input["name"].upper())
-                    for input in blocktype_infos["inputs"]
-                ]])
-
-            return_outputs = "\n".join([
-                """\
-                __SET_VAR(data__->,%(output_name)s,,
-                __GET_VAR(%(blockname)s->%(output_name)s));""" % {
-                    "output_name": output_name,
-                    "blockname": blockname
+            texts = {
+                "blocktype": blocktype_infos["blocktype"],
+                "ucase_blocktype": blocktype_infos["blocktype"].upper(),
+                "location": "_".join(map(str, current_location))
+            }
+            texts["blockname"] = "%(ucase_blocktype)s_%(location)s" % texts
+            
+            inputs = [{"input_name": "POS", "input_value": str(self.GetSlavePos())},
+                      {"input_name": "EXECUTE", "input_value": "__GET_VAR(data__->EXECUTE)"}] +\
+                     [{"input_name": input["name"].upper(), 
+                       "input_value": "__GET_VAR(data__->%s)" % input["name"].upper()}
+                      for input in blocktype_infos["inputs"]]
+            input_texts = []
+            for input_infos in inputs:
+                input_infos.update(texts)
+                input_texts.append(BLOCK_INPUT_TEMPLATE % input_infos)
+            texts["extract_inputs"] = "\n".join(input_texts)
+            
+            outputs = [{"output_name": output} for output in ["DONE", "BUSY", "ERROR"]] + \
+                      [{"output_name": output["name"].upper()} for output in blocktype_infos["outputs"]]
+            output_texts = []
+            for output_infos in outputs:
+                output_infos.update(texts)
+                output_texts.append(BLOCK_OUTPUT_TEMPLATE % output_infos)
+            texts["return_outputs"] = "\n".join(output_texts)
+            
+            str_completion["fieldbus_interface_declaration"].append(
+                    BLOCK_FUNCTION_TEMPLATE % texts)
+            
+            str_completion["fieldbus_interface_definition"].append(
+                    BLOCK_FUNTION_DEFINITION_TEMPLATE % texts)
+
+        variables = NODE_VARIABLES[:]
+
+#HSAHN
+#2015. 7. 24 PDO Variable
+        #if PDO is not selected, use 1st PDO set
+        self.LoadPDOSelectData()
+        if not self.SelectedRxPDOIndex and not self.SelectedTxPDOIndex :
+            self.SelectedPDOIndex = self.LoadDefaultPDOSet()
+        else :
+            self.SelectedPDOIndex = self.SelectedRxPDOIndex + self.SelectedTxPDOIndex
+
+        add_idx = []
+        for i in range(len(ADD_NODE_VARIABLES)):
+            add_idx.append(ADD_NODE_VARIABLES[i]['index'])
+
+        self.CommonMethod.RequestPDOInfo()
+        pdo_info = self.CommonMethod.GetRxPDOCategory() + self.CommonMethod.GetTxPDOCategory()
+        pdo_entry = self.CommonMethod.GetRxPDOInfo() + self.CommonMethod.GetTxPDOInfo()
+        list_index = 0
+        ModeOfOpFlag = False
+        ModeOfOpDisplayFlag = False
+        for i in range(len(pdo_info)):
+            #if pdo_index is in the SelectedPDOIndex: put the PDO mapping information intto the "used" object
+            if pdo_info[i]['pdo_index'] in self.SelectedPDOIndex:
+                used = pdo_entry[list_index:list_index + pdo_info[i]['number_of_entry']]
+                for used_data in used:
+                    # 24672 -> 0x6060, Mode of Operation
+                    if used_data['entry_index'] == 24672:
+                        ModeOfOpFlag = True
+                    # 24673 -> 0x6061, Mode of Operation Display
+                    elif used_data["entry_index"] == 24673:
+                        ModeOfOpDisplayFlag = True
+
+                    if used_data['entry_index'] in add_idx:
+                        idx = add_idx.index(used_data['entry_index'])
+                        adder = list([ADD_NODE_VARIABLES[idx]['name'], ADD_NODE_VARIABLES[idx]['index'], \
+                                     ADD_NODE_VARIABLES[idx]['sub-index'], ADD_NODE_VARIABLES[idx]['type'], \
+                                     ADD_NODE_VARIABLES[idx]['direction']])
+                        variables.append(adder)
+                        if ADD_NODE_VARIABLES[idx]['direction'] == "Q":                           
+                            parsed_string = ADD_NODE_VARIABLES[idx]['name'].replace("Target", "")
+                            # add jblee
+                            check_q_data = "    *(AxsPub.Target%s) = AxsPub.axis->Raw%sSetPoint;" %(parsed_string, parsed_string)
+                            if check_q_data not in str_completion["default_variables_publish"]:
+                                str_completion["default_variables_publish"].append(check_q_data)
+                        elif ADD_NODE_VARIABLES[idx]['direction'] == "I":
+                            parsed_string = ADD_NODE_VARIABLES[idx]['name'].replace("Actual", "")
+                            # add jblee
+                            check_i_data = "    AxsPub.axis->ActualRaw%s = *(AxsPub.Actual%s);" %(parsed_string, parsed_string)
+                            if check_i_data not in str_completion["default_variables_retrieve"]:
+                                str_completion["default_variables_retrieve"].append(check_i_data)
+            list_index += pdo_info[i]['number_of_entry']
+#HSAHN END
+
+        params = self.CTNParams[1].getElementInfos(self.CTNParams[0])
+        for param in params["children"]:
+            if param["name"] in EXTRA_NODE_VARIABLES_DICT:
+                if param["value"]:
+                    extra_variables = EXTRA_NODE_VARIABLES_DICT.get(param["name"])
+                    for variable_infos in extra_variables:
+                        var_infos = {
+                            "location": location_str,
+                            "name": variable_infos["description"][0]
+                        }
+                        variables.append(variable_infos["description"])
+                        retrieve_template = variable_infos.get("retrieve", DEFAULT_RETRIEVE)
+                        publish_template = variable_infos.get("publish", DEFAULT_PUBLISH)
+                        
+                        if retrieve_template is not None:
+                            str_completion["extra_variables_retrieve"].append(
+                                retrieve_template % var_infos)
+                        if publish_template is not None:
+                            str_completion["extra_variables_publish"].append(
+                                publish_template % var_infos)
+
+            #elif param["value"] is not None:
+            if param["value"] is not None:
+                param_infos = {
+                    "location": location_str,
+                    "param_name": param["name"],
                 }
-                for output_name in ["DONE", "BUSY", "ERROR"] + [
-                    output["name"].upper()
-                    for output in blocktype_infos["outputs"]]
-            ])
-
-            loc_dict = {
-                "ucase_blocktype": ucase_blocktype,
-                "blocktype": blocktype,
-                "blockname": blockname,
-                "location_str": location_str,
-                "extract_inputs": extract_inputs,
-                "return_outputs": return_outputs,
-            }
-
-            fieldbus_interface_declaration.append("""
-extern void ETHERLAB%(ucase_blocktype)s_body__(ETHERLAB%(ucase_blocktype)s* data__);
-void __%(blocktype)s_%(location_str)s(MC_%(ucase_blocktype)s *data__) {
-__DECLARE_GLOBAL_PROTOTYPE(ETHERLAB%(ucase_blocktype)s, %(blockname)s);
-ETHERLAB%(ucase_blocktype)s* %(blockname)s = __GET_GLOBAL_%(blockname)s();
-__SET_VAR(%(blockname)s->, POS,, AxsPub.axis->NetworkPosition);
-%(extract_inputs)s
-ETHERLAB%(ucase_blocktype)s_body__(%(blockname)s);
-%(return_outputs)s
-}""" % loc_dict)
-
-            fieldbus_interface_definition.append("""\
-        AxsPub.axis->__mcl_func_MC_%(blocktype)s = __%(blocktype)s_%(location_str)s;\
-""" % loc_dict)
-
-        # Get a copy list of default variables to map
-        variables = NODE_VARIABLES[:]
-
-        # Set AxisRef public struct members value
-        node_params = self.CTNParams[1].getElementInfos(self.CTNParams[0])
-        for param in node_params["children"]:
-            param_name = param["name"]
-
-            # Param is optional variables section enable flag
-            extra_node_variable_infos = EXTRA_NODE_VARIABLES_DICT.get(param_name)
-            if extra_node_variable_infos is not None:
-                param_name = param_name.replace("Enable", "") + "Enabled"
-
-                if not param["value"]:
-                    continue
-
-                # Optional variables section is enabled
-                for variable_infos in extra_node_variable_infos:
-                    var_name = variable_infos["description"][0]
-
-                    # Add each variables defined in section description to the
-                    # list of variables to map
-                    variables.append(variable_infos["description"])
-
-                    # Add code to publish or retrive variable
-                    coded = [
-                        ("retrieve",
-                         extra_variables_retrieve,
-                         "    AxsPub.axis->%(var_name)s = *(AxsPub.%(var_name)s);"),
-                        ("publish",
-                         extra_variables_publish,
-                         "    *(AxsPub.%(var_name)s) = AxsPub.axis->%(var_name)s;")
-                    ]
-                    for var_exchange_dir, _str_list, default_template in coded:
-                        template = variable_infos.get(var_exchange_dir, default_template)
-                        if template is not None:
-                            extra_variables_publish.append(template % locals())
-
-            # Set AxisRef public struct member value if defined
-            if param["value"] is not None:
-                param_value = ({True: "1", False: "0"}[param["value"]]
-                               if param["type"] == "boolean"
-                               else str(param["value"]))
-
-                init_axis_params.append("""\
-                AxsPub.axis->%(param_name)s = %(param_value)s;""" % {
-                    "param_value": param_value,
-                    "param_name": param_name,
-                })
-
-        # Add each variable in list of variables to map to master list of
-        # variables to add to network configuration
-        for name, index, subindex, var_type, dir in variables:
-            var_size = self.GetSizeOfType(var_type)
-            loc_dict = {
-                "var_size": var_size,
-                "var_type": var_type,
-                "name:": name,
-                "location_str": location_str,
-                "index": index,
-                "subindex": subindex,
-            }
-            var_name = """\
-__%(dir)s%(var_size)s%(location_str)s_%(index)d_%(subindex)d""" % loc_dict
-            loc_dict["var_name"] = var_name
-
-            extern_located_variables_declaration.append(
-                "IEC_%(var_type)s *%(var_name)s;" % loc_dict)
-            entry_variables.append(
-                "    IEC_%(var_type)s *%(name)s;" % loc_dict)
-            init_entry_variables.append(
-                "    AxsPub.%(name)s = %(var_name)s;" % loc_dict)
-
+                if param["type"] == "boolean":
+                    param_infos["param_value"] = {True: "1", False: "0"}[param["value"]]
+                    param_infos["param_name"] = param["name"].replace("Enable", "") + "Enabled"
+                    if param["value"] == False:
+                        continue
+                else:
+                    param_infos["param_value"] = str(param["value"])
+                # param_name = param_name.replace("Enable", "") + "Enabled"
+                str_completion["init_axis_params"].append(
+                    "        __CIA402Node_%(location)s.axis->%(param_name)s = %(param_value)s;" % param_infos)
+        
+        check_variable = []
+        for variable in variables:
+            # add jblee
+            if variable in check_variable:
+                continue
+
+            var_infos = dict(zip(["name", "index", "subindex", "var_type", "dir"], variable))
+            var_infos["location"] = location_str
+            var_infos["var_size"] = self.GetSizeOfType(var_infos["var_type"])
+            var_infos["var_name"] = "__%(dir)s%(var_size)s%(location)s_%(index)d_%(subindex)d" % var_infos
+
+            # add jblee
+            if var_infos["index"] in [24672] and ModeOfOpFlag:
+                str_completion["modeofop_homing_method"].append(MODEOFOP_HOMING_METHOD_TEMPLATE)
+                str_completion["modeofop_computation_mode"].append(MODEOFOP_COMPUTATION_MODE_TEMPLATE)
+
+            # add jblee
+            if var_infos["index"] in [24672, 24673] and (not ModeOfOpFlag or not ModeOfOpDisplayFlag):
+                continue
+
+            str_completion["extern_located_variables_declaration"].append(
+                    "IEC_%(var_type)s *%(var_name)s;" % var_infos)
+            str_completion["entry_variables"].append(
+                    "    IEC_%(var_type)s *%(name)s;" % var_infos)
+            str_completion["init_entry_variables"].append(
+                    "    __CIA402Node_%(location)s.%(name)s = %(var_name)s;" % var_infos)
+            
             self.CTNParent.FileGenerator.DeclareVariable(
-                slave_pos, index, subindex, var_type, dir, var_name)
-
-        # Add newline between string in list of generated strings for sections
-        [fieldbus_interface_declaration, fieldbus_interface_definition,
-         init_axis_params, extra_variables_retrieve, extra_variables_publish,
-         extern_located_variables_declaration, entry_variables,
-         init_entry_variables] = map("\n".join, [
-             fieldbus_interface_declaration, fieldbus_interface_definition,
-             init_axis_params, extra_variables_retrieve, extra_variables_publish,
-             extern_located_variables_declaration, entry_variables,
-             init_entry_variables])
-
-        # Write generated content to CIA402 node file
-        Gen_CIA402Nodefile_path = os.path.join(buildpath,
-                                               "cia402node_%s.c" % location_str)
+                    self.GetSlavePos(), var_infos["index"], var_infos["subindex"], 
+                    var_infos["var_type"], var_infos["dir"], var_infos["var_name"])
+
+            # add jblee
+            check_variable.append(variable)
+        
+        for element in ["extern_located_variables_declaration", 
+                        "fieldbus_interface_declaration",
+                        "fieldbus_interface_definition",
+                        "entry_variables", 
+                        "init_axis_params", 
+                        "init_entry_variables",
+                        "default_variables_retrieve",
+                        "default_variables_publish",
+                        "extra_variables_retrieve",
+                        "extra_variables_publish",
+                        "modeofop_homing_method",
+                        "modeofop_computation_mode"]:
+            str_completion[element] = "\n".join(str_completion[element])
+        
+        Gen_CIA402Nodefile_path = os.path.join(buildpath, "cia402node_%s.c"%location_str)
         cia402nodefile = open(Gen_CIA402Nodefile_path, 'w')
-        cia402nodefile.write(plc_cia402node_code % locals())
+        cia402nodefile.write(plc_cia402node_code % str_completion)
         cia402nodefile.close()
 
         return [(Gen_CIA402Nodefile_path, '"-I%s"' % os.path.abspath(self.GetCTRoot().GetIECLibPath()))], "", True
--- a/etherlab/EthercatMaster.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/EthercatMaster.py	Fri Dec 25 17:12:02 2020 +0000
@@ -243,20 +243,20 @@
         config_filepath = self.ConfigFileName()
         config_is_saved = False
         self.Config = None
-        if os.path.isfile(config_filepath):
-            config_xmlfile = open(config_filepath, 'r')
-            try:
-                self.Config, error = \
-                    EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
-                if error is None:
-                    config_is_saved = True
-            except Exception as e:
-                error = str(e)
-            config_xmlfile.close()
-
-            if error is not None:
-                self.GetCTRoot().logger.write_error(
-                    _("Couldn't load %s network configuration file.") % self.CTNName())
+        # if os.path.isfile(config_filepath):
+        #     config_xmlfile = open(config_filepath, 'r')
+        #     try:
+        #         self.Config, error = \
+        #             EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
+        #         if error is None:
+        #             config_is_saved = True
+        #     except Exception as e:
+        #         error = str(e)
+        #     config_xmlfile.close()
+
+        #     if error is not None:
+        #         self.GetCTRoot().logger.write_error(
+        #             _("Couldn't load %s network configuration file.") % self.CTNName())
 
         if self.Config is None:
             self.Config = EtherCATConfigParser.CreateElement("EtherCATConfig")
@@ -282,12 +282,28 @@
         if self.ProcessVariables is None:
             self.ProcessVariables = ProcessVariablesParser.CreateElement("ProcessVariables")
 
-        if config_is_saved and process_is_saved:
+        #if config_is_saved and process_is_saved:
+        if process_is_saved:
             self.CreateBuffer(True)
         else:
             self.CreateBuffer(False)
             self.OnCTNSave()
 
+        if os.path.isfile(config_filepath):
+            config_xmlfile = open(config_filepath, 'r')
+            try:
+                self.Config, error = \
+                    EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
+                if error is None:
+                    config_is_saved = True
+            except Exception, e:
+                error = e.message
+            config_xmlfile.close()
+            
+            if error is not None:
+                self.GetCTRoot().logger.write_error(
+                    _("Couldn't load %s network configuration file.") % CTNName)
+
         # ----------- call ethercat mng. function --------------
         self.CommonMethod = _CommonSlave(self)
 
@@ -306,7 +322,7 @@
             type_infos = dialog.GetValueInfos()
             device, _module_extra_params = self.GetModuleInfos(type_infos)
             if device is not None:
-                if HAS_MCL and _EthercatCIA402SlaveCTN.NODE_PROFILE in device.GetProfileNumbers():
+                if HAS_MCL and str(_EthercatCIA402SlaveCTN.NODE_PROFILE) in device.GetProfileNumbers():
                     ConfNodeType = "EthercatCIA402Slave"
                 else:
                     ConfNodeType = "EthercatSlave"
@@ -577,14 +593,62 @@
                 return infos
         return None
 
-    def GetSlaveVariables(self, slave_pos=None, limits=None, device=None):
+    def GetSlaveVariables(self, slave_pos=None, limits=None, device=None, module=None):
+        # add jblee
+        files = os.listdir(self.CTNPath())
+        moduleNames = []
+        modulePos = 1
+        for file in files:
+            filepath = os.path.join(self.CTNPath(), file)
+            if os.path.isdir(filepath):
+                MDPFilePath = os.path.join(filepath, "DataForMDP.txt")
+                CheckConfNodePath = os.path.join(filepath, "baseconfnode.xml")
+
+                try :
+                    moduleDataFile = open(MDPFilePath, 'r')
+                    confNodeFile = open(CheckConfNodePath, 'r')
+
+                    lines = moduleDataFile.readlines()
+                    checklines = confNodeFile.readlines()
+
+                    moduleDataFile.close()
+                    confNodeFile.close()
+
+                    module_info = self.GetModuleEntryList()
+                    # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+                    # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                    #                           IEC_Channel="0", Name="EthercatSlave_0"/>]
+                    # checklines[1].split()[2] : IEC_Channel="0"
+                    # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+                    pos_check = int(checklines[1].split()[2].split("\"")[1])
+
+                    if slave_pos != pos_check:
+                        continue
+
+                    for line in lines:
+                        if line == "\n":
+                            continue
+                        # module_name : ST-1214, ST-2314, ...
+                        # if user add module => ST-1214 3EA, ST-2314 3EA
+                        # each result_module_name : 
+                        #    (ST-1214, Module 1), (ST-1214, Module 2), (ST-1214, Module 3)
+                        #    (ST-2314, Module 4), (ST-2314, Module 5), (ST-2314, Module 6)  
+                        module_name = line.split()[0]
+                        result_module_name = module_name + ", Module %d" % modulePos
+                        moduleNames.append(result_module_name)
+                        modulePos += 1
+                except :
+                    pass
+
         if device is None and slave_pos is not None:
             slave = self.GetSlave(slave_pos)
             if slave is not None:
                 type_infos = slave.getType()
                 device, _module_extra_params = self.GetModuleInfos(type_infos)
         if device is not None:
+            # Test OD
             entries = device.GetEntriesList(limits)
+            #entries = self.CTNParent.GetEntriesList()
             entries_list = entries.items()
             entries_list.sort()
             entries = []
@@ -602,6 +666,49 @@
                     current_entry["children"].append(entry)
                 else:
                     entries.append(entry)
+
+            increment = self.CTNParent.GetModuleIncrement()[0]
+            count = 1
+            #print module_info
+            # moduleNameAndPos : (ST-1214, Module 1), (ST-1214, Module 2), ... ,
+            # moduleNameAndPos.split(",") : ["ST-1214", " Module 1"]
+            # moduleNameAndPos.split(",")[0] : "ST-1214"
+            for moduleNameAndPos in moduleNames:
+                moduleName = moduleNameAndPos.split(",")[0]
+                modulePosName = moduleNameAndPos.split(",")[1]
+                idx_increment = int(increment) * count
+                
+                for MDP_entry in module_info.get(moduleName):
+                    LocalMDPEntry = []
+                    #print MDP_entry
+                    local_idx = MDP_entry["Index"]
+                    if ExtractHexDecValue(local_idx) == 0: #and local_idx[0] == "#":
+                        temp_index = ExtractHexDecValue(local_idx)
+                    else :
+                        temp_index = ExtractHexDecValue(MDP_entry["Index"]) + idx_increment
+                    #temp_index = ExtractHexDecValue(MDP_entry["Index"]) + idx_increment
+                    entry_index = hex(temp_index)
+                    entry_subidx = MDP_entry["SubIndex"]
+                    entry_name = MDP_entry["Name"] + ", " + " " + \
+                                 moduleName + " - " + modulePosName
+                    entry_type = MDP_entry["Type"]
+                    entry_bitsize = MDP_entry["BitSize"]
+                    entry_access = MDP_entry["Access"]
+                    mapping_type = MDP_entry["PDOMapping"]
+
+                    LocalMDPEntry.append({
+                        "Index": entry_index,
+                        "SubIndex": entry_subidx,
+                        "Name": entry_name,
+                        "Type": entry_type,
+                        "BitSize": entry_bitsize,
+                        "Access": entry_access, 
+                        "PDOMapping": mapping_type,
+                        "children": ""})
+                    entries.append(LocalMDPEntry[0])
+                count += 1
+            
+            #print entries
             return entries
         return []
 
@@ -634,6 +741,10 @@
     def GetModuleInfos(self, type_infos):
         return self.CTNParent.GetModuleInfos(type_infos)
 
+    # add jblee
+    def GetModuleEntryList(self):
+        return self.CTNParent.GetModuleEntryList()
+    
     def GetSlaveTypesLibrary(self, profile_filter=None):
         return self.CTNParent.GetModulesLibrary(profile_filter)
 
@@ -643,6 +754,53 @@
     def GetDeviceLocationTree(self, slave_pos, current_location, device_name):
         slave = self.GetSlave(slave_pos)
         vars = []
+
+        # add jblee
+        files = os.listdir(self.CTNPath())
+        moduleNames = []
+        modulePos = 1
+        for file in files:
+            filepath = os.path.join(self.CTNPath(), file)
+            if os.path.isdir(filepath):
+                MDPFilePath = os.path.join(filepath, "DataForMDP.txt")
+                CheckConfNodePath = os.path.join(filepath, "baseconfnode.xml")
+
+                try :
+                    moduleDataFile = open(MDPFilePath, 'r')
+                    confNodeFile = open(CheckConfNodePath, 'r')
+
+                    lines = moduleDataFile.readlines()
+                    checklines = confNodeFile.readlines()
+
+                    moduleDataFile.close()
+                    confNodeFile.close()
+
+                    module_info = self.GetModuleEntryList()
+                    # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+                    # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                    #                           IEC_Channel="0", Name="EthercatSlave_0"/>]
+                    # checklines[1].split()[2] : IEC_Channel="0"
+                    # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+                    pos_check = int(checklines[1].split()[2].split("\"")[1])
+
+                    if slave_pos != pos_check:
+                        continue
+
+                    for line in lines:
+                        if line == "\n":
+                            continue
+                        # module_name : ST-1214, ST-2314, ...
+                        # if user add module => ST-1214 3EA, ST-2314 3EA
+                        # each result_module_name : 
+                        #    (ST-1214, Module 1), (ST-1214, Module 2), (ST-1214, Module 3)
+                        #    (ST-2314, Module 4), (ST-2314, Module 5), (ST-2314, Module 6)  
+                        module_name = line.split()[0]
+                        result_module_name = module_name + ", Module %d" % modulePos
+                        moduleNames.append(result_module_name)
+                        modulePos += 1
+                except :
+                    pass
+
         if slave is not None:
             type_infos = slave.getType()
 
@@ -681,6 +839,39 @@
                                 "children": [],
                             })
 
+                # add jblee for MDP
+                if not entries :
+                    increment = self.CTNParent.GetModuleIncrement()[0]
+                    count = 1
+                    for moduleNameAndPos in moduleNames:
+                        moduleName = moduleNameAndPos.split(",")[0]
+                        idx_increment = int(increment) * count
+                        for MDP_entry in module_info.get(moduleName):
+                            local_idx = MDP_entry["Index"]
+                            if ExtractHexDecValue(local_idx) != 0 and local_idx[0] == "#":
+                                index = ExtractHexDecValue(local_idx) + idx_increment
+                            else :
+                                index = ExtractHexDecValue(MDP_entry["Index"])
+                            subindex = int(MDP_entry["SubIndex"])
+                            var_class = VARCLASSCONVERSION.get(MDP_entry["PDOMapping"], None)
+                            if var_class is not None:
+                                if var_class == LOCATION_VAR_INPUT:
+                                    var_dir = "%I"
+                                else:
+                                    var_dir = "%Q"
+                            var_size = self.GetSizeOfType(MDP_entry["Type"])
+                            result_name = MDP_entry["Name"] + ", " + moduleNameAndPos
+                            vars.append({"name": "0x%4.4x-0x%2.2x: %s" % (index, subindex, result_name),
+                                         "type": var_class,
+                                         "size": var_size,
+                                         "IEC_type": MDP_entry["Type"],
+                                         "var_name": "%s_%4.4x_%2.2x" % ("_".join(moduleName.split()), index, subindex),
+                                         "location": "%s%s%s"%(var_dir, var_size, ".".join(map(str, current_location + 
+                                                                                                    (index, subindex)))),
+                                         "description": "",
+                                         "children": []})
+                        count += 1
+
         return vars
 
     def CTNTestModified(self):
@@ -688,7 +879,6 @@
 
     def OnCTNSave(self, from_project_path=None):
         config_filepath = self.ConfigFileName()
-
         config_xmlfile = open(config_filepath, "w")
         config_xmlfile.write(etree.tostring(
             self.Config,
--- a/etherlab/EthercatSlave.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/EthercatSlave.py	Fri Dec 25 17:12:02 2020 +0000
@@ -73,6 +73,8 @@
     def __init__(self):
         # ----------- call ethercat mng. function --------------
         self.CommonMethod = _CommonSlave(self)
+        self.SelectedRxPDOIndex = []
+        self.SelectedTxPDOIndex = []
 
     def GetIconName(self):
         return "Slave"
--- a/etherlab/entries_list.xslt	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/entries_list.xslt	Fri Dec 25 17:12:02 2020 +0000
@@ -1,1 +1,1 @@
-<xsl:stylesheet xmlns:func="http://exslt.org/functions" xmlns:dyn="http://exslt.org/dynamic" xmlns:str="http://exslt.org/strings" xmlns:math="http://exslt.org/math" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="ns" xmlns:yml="http://fdik.org/yml" xmlns:set="http://exslt.org/sets" version="1.0" xmlns:ns="entries_list_ns" exclude-result-prefixes="ns" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml"/><xsl:variable name="space" select="'                                                                                                                                                                                                        '"/><xsl:param name="autoindent" select="4"/><xsl:param name="min_index"/><xsl:param name="max_index"/><xsl:template match="text()"><xsl:param name="_indent" select="0"/></xsl:template><xsl:template match="Device"><xsl:param name="_indent" select="0"/><xsl:apply-templates select="Profile/Dictionary/Objects/Object"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/></xsl:apply-templates><xsl:for-each select="RxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Receive'"/></xsl:call-template></xsl:for-each><xsl:for-each select="TxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Transmit'"/></xsl:call-template></xsl:for-each></xsl:template><xsl:template match="Object"><xsl:param name="_indent" select="0"/><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="entry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:choose><xsl:when test="$index &gt;= $min_index and $index &lt;= $max_index"><xsl:variable name="datatype_name"><xsl:value-of select="Type/text()"/></xsl:variable><xsl:choose><xsl:when test="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:apply-templates select="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/><xsl:with-param name="index"><xsl:value-of select="$index"/></xsl:with-param><xsl:with-param name="entry_name"><xsl:value-of select="$entry_name"/></xsl:with-param></xsl:apply-templates></xsl:when><xsl:otherwise><xsl:variable name="subindex"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())"/></xsl:variable></xsl:otherwise></xsl:choose></xsl:when></xsl:choose></xsl:template><xsl:template match="DataType"><xsl:param name="_indent" select="0"/><xsl:param name="index"/><xsl:param name="entry_name"/><xsl:for-each select="SubItem"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIdx/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="$entry_name"/><xsl:text> - </xsl:text><xsl:value-of select="ns:EntryName(DisplayName, Name/text())"/></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())"/></xsl:variable></xsl:for-each></xsl:template><xsl:template name="pdo_entries"><xsl:param name="_indent" select="0"/><xsl:param name="direction"/><xsl:variable name="pdo_index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="pdo_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:for-each select="Entry"><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:choose><xsl:when test="$index &gt;= $min_index and $index &lt;= $max_index"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIndex/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:variable name="access"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>ro</xsl:text></xsl:when><xsl:otherwise><xsl:text>wo</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="pdo_mapping"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>T</xsl:text></xsl:when><xsl:otherwise><xsl:text>R</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, DataType/text(), BitLen/text(), $access, $pdo_mapping, $pdo_index, $pdo_name, $direction)"/></xsl:variable></xsl:when></xsl:choose></xsl:for-each></xsl:template></xsl:stylesheet>
\ No newline at end of file
+<xsl:stylesheet xmlns:func="http://exslt.org/functions" xmlns:dyn="http://exslt.org/dynamic" xmlns:str="http://exslt.org/strings" xmlns:math="http://exslt.org/math" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="ns" xmlns:yml="http://fdik.org/yml" xmlns:set="http://exslt.org/sets" version="1.0" xmlns:ns="entries_list_ns" exclude-result-prefixes="ns" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml"/><xsl:variable name="space" select="'                                                                                                                                                                                                        '"/><xsl:param name="autoindent" select="4"/><xsl:param name="min_index"/><xsl:param name="max_index"/><xsl:template match="text()"><xsl:param name="_indent" select="0"/></xsl:template><xsl:template match="Device"><xsl:param name="_indent" select="0"/><xsl:apply-templates select="Profile/Dictionary/Objects/Object"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/></xsl:apply-templates><xsl:for-each select="RxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Receive'"/></xsl:call-template></xsl:for-each><xsl:for-each select="TxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Transmit'"/></xsl:call-template></xsl:for-each></xsl:template><xsl:template match="Object"><xsl:param name="_indent" select="0"/><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="entry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:choose><xsl:when test="$index &gt;= $min_index and $index &lt;= $max_index"><xsl:variable name="datatype_name"><xsl:value-of select="Type/text()"/></xsl:variable><xsl:variable name="default_value"><xsl:value-of select="Info/DefaultData/text()"/></xsl:variable><xsl:choose><xsl:when test="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:apply-templates select="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/><xsl:with-param name="index"><xsl:value-of select="$index"/></xsl:with-param><xsl:with-param name="entry_name"><xsl:value-of select="$entry_name"/></xsl:with-param><xsl:with-param name="sub_default_value"><xsl:value-of select="Info"/></xsl:with-param></xsl:apply-templates></xsl:when><xsl:otherwise><xsl:variable name="subindex"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="sub_entry_flag"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $default_value, $sub_entry_flag)"/></xsl:variable></xsl:otherwise></xsl:choose></xsl:when></xsl:choose></xsl:template><xsl:template match="DataType"><xsl:param name="_indent" select="0"/><xsl:param name="index"/><xsl:param name="entry_name"/><xsl:param name="sub_default_value"/><xsl:for-each select="SubItem"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIdx/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="$entry_name"/><xsl:text> - </xsl:text><xsl:value-of select="ns:EntryName(DisplayName, Name/text())"/></xsl:variable><xsl:variable name="sub_entry_flag"><xsl:text>1</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $sub_default_value, $sub_entry_flag)"/></xsl:variable></xsl:for-each></xsl:template><xsl:template name="pdo_entries"><xsl:param name="_indent" select="0"/><xsl:param name="direction"/><xsl:variable name="pdo_index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="pdo_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:for-each select="Entry"><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:choose><xsl:when test="$index &gt;= $min_index and $index &lt;= $max_index"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIndex/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:variable name="access"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>ro</xsl:text></xsl:when><xsl:otherwise><xsl:text>wo</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="pdo_mapping"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>T</xsl:text></xsl:when><xsl:otherwise><xsl:text>R</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, DataType/text(), BitLen/text(), $access, $pdo_mapping, $pdo_index, $pdo_name, $direction)"/></xsl:variable></xsl:when></xsl:choose></xsl:for-each></xsl:template></xsl:stylesheet>
\ No newline at end of file
--- a/etherlab/entries_list.ysl2	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/entries_list.ysl2	Fri Dec 25 17:12:02 2020 +0000
@@ -38,17 +38,20 @@
         choose {
             when "$index >= $min_index and $index <= $max_index" {
                 variable "datatype_name" > «Type/text()»
+                variable "default_value" > «Info/DefaultData/text()»
                 choose {
                     when "ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]" {
                         apply "ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]" {
                             with "index" > «$index»
                             with "entry_name" > «$entry_name»
+                            with "sub_default_value" > «Info/SubItem/Info/DefaultData/text()»
                         }
                     }
                     otherwise {
                         variable "subindex" > 0
+                        variable "sub_entry_flag" > 0
                         variable "entry" {
-                            > «ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())»
+                            > «ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $default_value, $sub_entry_flag)»
                         }
                     }
                 }
@@ -59,11 +62,13 @@
     template "DataType" {
         param "index";
         param "entry_name";
+        param "sub_default_value"
         foreach "SubItem" {
             variable "subindex" > «ns:HexDecValue(SubIdx/text())»
             variable "subentry_name" > «$entry_name» - «ns:EntryName(DisplayName, Name/text())»
+            variable "sub_entry_flag" > 1
             variable "entry" {
-                > «ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())»
+                > «ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $sub_default_value, $sub_entry_flag)»
             }
         }
     }
--- a/etherlab/etherlab.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/etherlab.py	Fri Dec 25 17:12:02 2020 +0000
@@ -39,6 +39,13 @@
     return etree.XPath(xpath)
 
 
+EtherCATBaseParser = GenerateParserFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATBase.xsd"))
+
+
+def EtherCATBase_XPath(xpath):
+    return etree.XPath(xpath)
+
+
 def HexDecValue(context, *args):
     return str(ExtractHexDecValue(args[0][0]))
 
@@ -59,6 +66,18 @@
     ("PDO name", str, ""),
     ("PDO type", str, "")]
 
+# Read DefaultValue from ESI file
+# Add by jblee 151229
+ENTRY_INFOS_KEYS_FOR_DV = [
+    ("Index", lambda x: "#x%4.4X" % int(x), "#x0000"),
+    ("SubIndex", str, "0"),
+    ("Name", str, ""),
+    ("Type", str, ""),
+    ("BitSize", int, 0),
+    ("Access", str, ""),
+    ("PDOMapping", str, ""),
+    ("DefaultValue", str, ""),
+    ("Sub_entry_flag", str, "0")]
 
 class EntryListFactory(object):
 
@@ -67,11 +86,17 @@
 
     def AddEntry(self, context, *args):
         index, subindex = map(lambda x: int(x[0]), args[:2])
-        new_entry_infos = {
+        if len(args) > 9:
+		    new_entry_infos = {
             key: translate(arg[0]) if len(arg) > 0 else default
             for (key, translate, default), arg
             in zip(ENTRY_INFOS_KEYS, args)}
-
+        else:
+            new_entry_infos = {
+            key: translate(arg[0]) if len(arg) > 0 else default
+            for (key, translate, default), arg
+            in zip(ENTRY_INFOS_KEYS_FOR_DV, args)}			
+			
         if (index, subindex) != (0, 0):
             entry_infos = self.Entries.get((index, subindex))
             if entry_infos is not None:
@@ -88,7 +113,8 @@
 
 cls = EtherCATInfoParser.GetElementClass("DeviceType")
 if cls:
-
+    cls.DataTypes = None
+    
     profile_numbers_xpath = EtherCATInfo_XPath("Profile/ProfileNo")
 
     def GetProfileNumbers(self):
@@ -102,25 +128,151 @@
         return None
     setattr(cls, "getCoE", getCoE)
 
+    # Modify by jblee
+    def ExtractDataTypes(self):
+        #self.DataTypes = {}
+        #self.DT = {}
+        DT = {}
+        objects = []
+
+        # get Profile Field
+        for profile in self.getProfile():
+            # get each (ProfileNo, Dictionary) Field as child
+            for child in profile.getchildren():
+                # child.text is not None -> ProfileNo, is None -> Dictionary
+                if child.text is None:
+                    # get each (DataTypes, Objects) Field 
+                    dataTypes = child.getDataTypes()
+                    objects = child.getObjects()
+                                
+                    for dataType in dataTypes.getDataType():
+                        #if dataType.getName() is not None:
+                        #    print dataType.getName(), dataType
+                        DT[dataType.getName()] = dataType
+
+        return DT, objects
+    setattr(cls, "ExtractDataTypes", ExtractDataTypes)
+
     def GetEntriesList(self, limits=None):
+        DataTypes, objects = self.ExtractDataTypes()
+
         entries = {}
 
-        factory = EntryListFactory(entries)
-
-        entries_list_xslt_tree = etree.XSLT(
-            entries_list_xslt, extensions={
-                ("entries_list_ns", "AddEntry"): factory.AddEntry,
-                ("entries_list_ns", "HexDecValue"): HexDecValue,
-                ("entries_list_ns", "EntryName"): EntryName})
-        entries_list_xslt_tree(self, **dict(zip(
-            ["min_index", "max_index"],
-            map(lambda x: etree.XSLT.strparam(str(x)),
-                limits if limits is not None else [0x0000, 0xFFFF])
-            )))
-
+        # get each Object Field
+        for object in objects:
+            # Object Field mendatory : Index, Name, Type, BitSize
+            # Frequently Use : Info, Flags
+            # Info Field -> DefaultData, SubItem
+            # Flags Field -> Access, Category, PdoMapping
+            object_index = object.getIndex().getcontent()
+            index = ExtractHexDecValue(object_index)
+            if limits is None or limits[0] <= index <= limits[1]:
+                object_type = object.getType()
+                object_name = ExtractName(object.getName())
+                object_size = object.getBitSize()
+                defaultData = ""
+                object_access = ""
+                object_PDOMapping_data = ""
+
+                object_type_infos = DataTypes.get(object_type, None)
+                subItem_infos = object_type_infos.getchildren()
+                countSubIndex = 0
+                if len(subItem_infos) > 2:
+                    for subItem_info in subItem_infos:
+                        if subItem_info.tag == "SubItem" : 
+                            subItemName = subItem_info.getName()
+                            subIdx = subItem_info.getSubIdx()
+                            if subIdx is not None:
+                                object_subidx = ExtractHexDecValue(subIdx)
+                            else:
+                                object_subidx = ExtractHexDecValue(countSubIndex)
+                            subType = subItem_info.getType()
+                            subBitSize = subItem_info.getBitSize()
+                            subFlags = subItem_info.getFlags()
+                            subAccess = ""
+                            subPDOMapping_data = ""
+                            if subFlags is not None:
+                                subAccess = subFlags.getAccess().getcontent()
+                                subPDOMapping = subFlags.getPdoMapping()                                                        
+                                if subPDOMapping is not None:
+                                    subPDOMapping_data = subFlags.getPdoMapping().upper()
+
+                            entries[(index, object_subidx)] = {
+                                "Index": object_index,
+                                "SubIndex": subIdx,
+                                "Name": "%s - %s" % 
+                                    (object_name.decode("utf-8"),
+                                     subItemName.decode("utf-8")),
+                                "Type": subType,
+                                "BitSize": subBitSize,
+                                "Access": subAccess, 
+                                "PDOMapping": subPDOMapping_data}
+
+                            countSubIndex += 1
+
+                    info = object.getInfo()
+                    # subItemTest : check subItem 
+                    countSubIndex = 0
+                    if info is not None:
+                        subItems = info.getchildren()
+                        if len(subItems) > 1:
+                            for subItem in subItems:
+                                defaultdata_subidx = ExtractHexDecValue(countSubIndex)
+                                defaultData = subItem.getchildren()[1].findtext("DefaultData")
+                                entry = entries.get((index, defaultdata_subidx), None)
+                                if entry is not None:
+                                    entry["DefaultData"] = defaultData
+                                countSubIndex += 1
+
+                else :
+                    info = object.getInfo()
+                    if info is not None:
+                        subItems = info.getchildren()
+                        if len(subItems) <= 1:
+                            defaultData = subItems[0].text
+                                
+                    object_flag = object.getFlags()
+                    object_access = object_flag.getAccess().getcontent()
+                    object_PDOMapping = object_flag.getPdoMapping()
+                    if object_PDOMapping is not None:
+                        object_PDOMapping_data = object_flag.getPdoMapping().upper()
+                    entries[(index, 0)] = {
+                        "Index": object_index,
+                        "SubIndex": "0",
+                        "Name": object_name,                                                               
+                        "Type": object_type,
+                        "BitSize": object_size,
+                        "DefaultData" : defaultData,
+                        "Access": object_access, 
+                        "PDOMapping": object_PDOMapping_data}
+
+        for TxPdo in self.getTxPdo():
+            ExtractPdoInfos(TxPdo, "Transmit", entries, limits)
+        for RxPdo in self.getRxPdo():
+            ExtractPdoInfos(RxPdo, "Receive", entries, limits)
+        
         return entries
     setattr(cls, "GetEntriesList", GetEntriesList)
 
+#    def GetEntriesList(self, limits=None):
+#        entries = {}
+        
+#        factory = EntryListFactory(entries)
+        
+#        entries_list_xslt_tree = etree.XSLT(
+#            entries_list_xslt, extensions = {
+#                ("entries_list_ns", "AddEntry"): factory.AddEntry,
+#                ("entries_list_ns", "HexDecValue"): HexDecValue,
+#                ("entries_list_ns", "EntryName"): EntryName})
+#        entries_list_xslt_tree(self, **dict(zip(
+#            ["min_index", "max_index"], 
+#            map(lambda x: etree.XSLT.strparam(str(x)),
+#                limits if limits is not None else [0x0000, 0xFFFF])
+#            )))
+#        
+#        return entries
+#    setattr(cls, "GetEntriesList", GetEntriesList)
+
     def GetSyncManagers(self):
         sync_managers = []
         for sync_manager in self.getSm():
@@ -155,6 +307,40 @@
             SortGroupItems(item)
     group["children"].sort(GroupItemCompare)
 
+def ExtractPdoInfos(pdo, pdo_type, entries, limits=None):
+    pdo_index = pdo.getIndex().getcontent()
+    pdo_name = ExtractName(pdo.getName())
+    exclude = pdo.getExclude()
+    for pdo_entry in pdo.getEntry():
+        entry_index = pdo_entry.getIndex().getcontent()
+        entry_subindex = pdo_entry.getSubIndex()
+        index = ExtractHexDecValue(entry_index)
+        subindex = ExtractHexDecValue(entry_subindex)
+        object_size = pdo_entry.getBitLen()
+
+        if limits is None or limits[0] <= index <= limits[1]:
+            entry = entries.get((index, subindex), None)
+            if entry is not None:
+                entry["PDO index"] = pdo_index
+                entry["PDO name"] = pdo_name
+                entry["PDO type"] = pdo_type
+            else:
+                entry_type = pdo_entry.getDataType()
+                if entry_type is not None:
+                    if pdo_type == "Transmit":
+                        access = "ro"
+                        pdomapping = "T"
+                    else:
+                        access = "wo"
+                        pdomapping = "R"
+                    entries[(index, subindex)] = {
+                        "Index": entry_index,
+                        "SubIndex": entry_subindex,
+                        "Name": ExtractName(pdo_entry.getName()),
+                        "Type": entry_type.getcontent(),
+                        "BitSize": object_size,
+                        "Access": access,
+                        "PDOMapping": pdomapping}
 
 class ModulesLibrary(object):
 
@@ -211,10 +397,21 @@
 
     groups_xpath = EtherCATInfo_XPath("Descriptions/Groups/Group")
     devices_xpath = EtherCATInfo_XPath("Descriptions/Devices/Device")
+    module_xpath = EtherCATBase_XPath("Descriptions/Modules/Module")
 
     def LoadModules(self):
         self.Library = {}
-
+        # add by jblee for Modular Device Profile
+        self.MDPList = []
+        self.ModuleList = []
+        self.MDPEntryList = {}
+        dtDic = {}
+        self.idxIncrement = 0
+        self.slotIncrement = 0
+        # add by jblee for PDO Mapping
+        self.DataTypes = {}
+        self.ObjectDictionary = {}
+        
         files = os.listdir(self.Path)
         for file in files:
             filepath = os.path.join(self.Path, file)
@@ -224,9 +421,9 @@
                 xmlfile = open(filepath, 'r')
                 try:
                     self.modules_infos, error = EtherCATInfoParser.LoadXMLString(xmlfile.read())
-                    if error is not None:
-                        self.GetCTRoot().logger.write_warning(
-                            XSDSchemaErrorMessage % (filepath + error))
+                    # if error is not None:
+                    #     self.GetCTRoot().logger.write_warning(
+                    #         XSDSchemaErrorMessage % (filepath + error))
                 except Exception as exc:
                     self.modules_infos, error = None, text(exc)
                 xmlfile.close()
@@ -241,6 +438,9 @@
 
                     for group in self.groups_xpath(self.modules_infos):
                         group_type = group.getType()
+                        # add for XmlToEeprom Func by jblee.
+                        self.LcId_data = group.getchildren()[1]
+                        self.Image16x14_data = group.getchildren()[2]
 
                         vendor_category["groups"].setdefault(
                             group_type,
@@ -248,8 +448,9 @@
                                 "name": ExtractName(group.getName(), group_type),
                                 "parent": group.getParentGroup(),
                                 "order": group.getSortOrder(),
-                                # "value": group.getcontent()["value"],
                                 "devices": [],
+                                # add jblee for support Moduler Device Profile (MDP)
+                                "modules": []})
                             })
 
                     for device in self.devices_xpath(self.modules_infos):
@@ -259,13 +460,98 @@
                         vendor_category["groups"][device_group]["devices"].append(
                             (device.getType().getcontent(), device))
 
-                else:
-
-                    self.GetCTRoot().logger.write_error(
-                        _("Couldn't load {a1} XML file:\n{a2}").format(a1=filepath, a2=error))
+                        # ------------------ Test Section --------------------#
+                        slots = device.getSlots()
+                        if slots is not None:
+                            for slot in slots.getSlot():
+                                self.idxIncrement = slot.getSlotIndexIncrement()
+                                self.slotIncrement = slot.getSlotPdoIncrement()
+                                for child in slot.getchildren():
+                                    if child.tag == "ModuleClass":
+                                        child_class = child.getClass()
+                                        child_name = child.getName()
+
+                    # -------------------- Test Section ----------------------------------# 
+                        LocalMDPList = []
+                        for module in self.module_xpath(self.modules_infos):
+                            module_type = module.getType().getModuleClass()
+                            module_name = module.getName()
+                            LocalMDPData = ExtractName(module_name) + " (" + module_type + ")"
+                            
+                            self.ModuleList.append(module)
+                            try :
+                                module_pdos = module.getTxPdo()
+                                module_pdos += module.getRxPdo()
+                                for module_pdo in module_pdos:
+                                    device_name = ExtractName(module_name)
+                                    pdo_index = module_pdo.getIndex().getcontent()
+                                    pdo_name = ExtractName(module_pdo.getName())
+                                    pdo_entry = module_pdo.getEntry()
+                                    if module_pdo.tag == "TxPdo":
+                                        mapping_type = "T"
+                                    else :
+                                        mapping_type = "R"
+
+                                    LocalMDPEntry = []
+                                    for entry in pdo_entry:
+                                        entry_index = entry.getIndex().getcontent()
+                                        entry_subidx = entry.getSubIndex()
+                                        entry_name = ExtractName(entry.getName())
+                                        entry_bitsize = entry.getBitLen()
+                                        try :
+                                           entry_type = entry.getDataType().getcontent()
+                                        except :
+                                           entry_type = ""
+
+                                        LocalMDPEntry.append({
+                                            "Index": entry_index,
+                                            "SubIndex": entry_subidx,
+                                            "Name": "%s - %s" % (pdo_name, entry_name),
+                                            "Type": entry_type,
+                                            "BitSize": entry_bitsize,
+                                            "Access": "", 
+                                            "PDOMapping": mapping_type})
+                                
+                                    self.MDPEntryList[device_name] = LocalMDPEntry
+
+                                LocalMDPList.append([LocalMDPData, module, LocalMDPEntry])
+                            except :
+                                LocalMDPList.append([LocalMDPData, module, []])
+                           
+                        if LocalMDPList:
+                            vendor_category["groups"][device_group]["modules"].append(
+                                (device.getType().getcontent(), LocalMDPList, self.idxIncrement, self.slotIncrement))
+                            #self.MDPList.append([device.getType().getcontent(), LocalMDPList,
+                            #                     self.idxIncrement, self.slotIncrement])
+
+                    # --------------------------------------------------------------------- #
+
+                # else:
+                #     self.GetCTRoot().logger.write_error(
+                #         _("Couldn't load {a1} XML file:\n{a2}").format(a1=filepath, a2=error))
 
         return self.Library
 
+    # add jblee
+    def GetMDPList(self):
+        return self.MDPList
+
+    # add jblee
+    def GetSelectModule(self, idx):
+        return self.ModuleList[idx]
+
+    # add jblee
+    def GetModuleEntryList(self):
+        return self.MDPEntryList
+
+    # add jblee
+    def GetModuleIncrement(self):
+        return (self.idxIncrement, self.slotIncrement)
+
+    # add jblee
+    #def GetEntriesList(self):
+    #    return self.ObjectDictionary
+    
     def GetModulesLibrary(self, profile_filter=None):
         if self.Library is None:
             self.LoadModules()
@@ -332,6 +618,18 @@
                         return device_infos, self.GetModuleExtraParams(vendor, product_code, revision_number)
         return None, None
 
+    # add jblee for MDP
+    def GetMDPInfos(self, module_infos):
+        vendor = ExtractHexDecValue(module_infos["vendor"])
+        vendor_infos = self.Library.get(vendor)
+        if vendor_infos is not None:
+            for group_name, group_infos in vendor_infos["groups"].iteritems():
+                return group_infos["modules"]
+                #for device_type, module_list, idx_inc, slot_inc in group_infos["modules"]:
+                #    return module_list, idx_inc, slot_inc
+
+        #return None, None, None
+    
     def ImportModuleLibrary(self, filepath):
         if os.path.isfile(filepath):
             shutil.copy(filepath, self.Path)
@@ -452,8 +750,31 @@
     def GetModulesLibrary(self, profile_filter=None):
         return self.ModulesLibrary.GetModulesLibrary(profile_filter)
 
+    # add jblee
+    def GetMDPList(self):
+        return self.ModulesLibrary.GetMDPList()
+
+    # add jblee
+    def GetSelectModule(self, idx):
+        return self.ModulesLibrary.GetSelectModule(idx)
+
+    # add jblee
+    def GetModuleEntryList(self):
+        return self.ModulesLibrary.GetModuleEntryList()
+
+    # add jblee
+    def GetModuleIncrement(self):
+        return self.ModulesLibrary.GetModuleIncrement()
+
+    # add jblee
+    #def GetEntriesList(self, limits = None):
+    #    return self.ModulesLibrary.GetEntriesList()
+
     def GetVendors(self):
         return self.ModulesLibrary.GetVendors()
 
     def GetModuleInfos(self, module_infos):
         return self.ModulesLibrary.GetModuleInfos(module_infos)
+
+    def GetMDPInfos(self, module_infos):
+        return self.ModulesLibrary.GetMDPInfos(module_infos)
--- a/etherlab/plc_cia402node.c	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/plc_cia402node.c	Fri Dec 25 17:12:02 2020 +0000
@@ -3,6 +3,8 @@
 Template C code used to produce target Ethercat C CIA402 code
 
 Copyright (C) 2011-2014: Laurent BESSARD, Edouard TISSERANT
+                         RTES Lab : CRKim, JBLee, youcu
+                         Higen Motor : Donggu Kang
 
 Distributed under the terms of the GNU Lesser General Public License as
 published by the Free Software Foundation; either version 2 of the License, or
@@ -33,7 +35,25 @@
 xxxx xxxx x0xx 1111 | Fault reaction active
 xxxx xxxx x0xx 1000 | Fault
 */
+
+//ssh_add
+/* From CiA402, Page 63 Statusword for homing mode
+
+		Table 106 - Definition of bit 10, bit 12, bit 13
+
+xx00 x0xx xxxx xxxx | Homing procedure is in progress
+xx00 x1xx xxxx xxxx | Homing procedure is interrupted or not started
+xx01 x0xx xxxx xxxx | Homing is attained, but target is not reached
+xx01 x1xx xxxx xxxx | Homing procedure is completed successfully
+xx10 x0xx xxxx xxxx | Homing error occurred, velocity is not 0
+xx10 x1xx xxxx xxxx | Homing error occurred, velocity is 0
+xx11 xxxx xxxx xxxx | reserved
+*/
+
 #define FSAFromStatusWord(SW) (SW & 0x006f)
+//ssh_add
+#define HomingStatusWord(SW) (SW & 0x3400)
+#define FaultFromStatusWord(SW) (SW & 0x0008)
 #define NotReadyToSwitchOn  0b00000000 FSA_sep 0b00100000
 #define SwitchOnDisabled    0b01000000 FSA_sep 0b01100000
 #define ReadyToSwitchOn     0b00100001
@@ -43,7 +63,16 @@
 #define FaultReactionActive 0b00001111 FSA_sep 0b00101111
 #define Fault               0b00001000 FSA_sep 0b00101000
 
-// SatusWord bits :
+//ssh_add
+#define HomingInProgress	0b0000000000000000
+#define HomingNotRunning	0b0000010000000000
+#define HomingNotReached	0b0001000000000000
+#define Homing_Completed	0b0001010000000000
+#define HomingErrorInVelo	0b0010000000000000
+#define HomingErrorNotVelo	0b0010010000000000
+#define HomingReserved		0b0011000000000000 FSA_sep 0b0011010000000000
+
+// StatusWord bits :
 #define SW_ReadyToSwitchOn     0x0001
 #define SW_SwitchedOn          0x0002
 #define SW_OperationEnabled    0x0004
@@ -56,6 +85,10 @@
 #define SW_TargetReached       0x0400
 #define SW_InternalLimitActive 0x0800
 
+//ssh_add
+#define SW_HomingAttained		0x1000
+#define SW_HomingError			0x2000
+
 // ControlWord bits :
 #define SwitchOn        0x0001
 #define EnableVoltage   0x0002
@@ -64,11 +97,15 @@
 #define FaultReset      0x0080
 #define Halt            0x0100
 
-
-IEC_INT beremiz__IW%(location_str)s = %(slave_pos)s;
-IEC_INT *__IW%(location_str)s = &beremiz__IW%(location_str)s;
-IEC_INT beremiz__IW%(location_str)s_402;
-IEC_INT *__IW%(location_str)s_402 = &beremiz__IW%(location_str)s_402;
+//ssh_add
+//#define Homing_OperationStart 0x0010
+#define Homing_OperationStart_Origin 0x0010
+#define Homing_OperationStart_Edit 0x001F
+
+IEC_INT beremiz__IW%(location)s = %(slave_pos)s;
+IEC_INT *__IW%(location)s = &beremiz__IW%(location)s;
+IEC_INT beremiz__IW%(location)s_402;
+IEC_INT *__IW%(location)s_402 = &beremiz__IW%(location)s_402;
 
 %(MCL_headers)s
 
@@ -104,7 +141,7 @@
     axis_s* axis;
 } __CIA402Node;
 
-#define AxsPub __CIA402Node_%(location_str)s
+#define AxsPub __CIA402Node_%(location)s
 
 static __CIA402Node AxsPub;
 
@@ -112,25 +149,24 @@
 
 %(fieldbus_interface_declaration)s
 
-int __init_%(location_str)s()
+int __init_%(location)s()
 {
     __FirstTick = 1;
 %(init_entry_variables)s
-	*(AxsPub.ModesOfOperation) = 0x08;
     return 0;
 }
 
-void __cleanup_%(location_str)s()
-{
-}
-
-void __retrieve_%(location_str)s()
+void __cleanup_%(location)s()
+{
+}
+
+void __retrieve_%(location)s()
 {
 	if (__FirstTick) {
-		*__IW%(location_str)s_402 = __MK_Alloc_AXIS_REF();
+		*__IW%(location)s_402 = __MK_Alloc_AXIS_REF();
 		AxsPub.axis = 
-            __MK_GetPublic_AXIS_REF(*__IW%(location_str)s_402);
-		AxsPub.axis->NetworkPosition = beremiz__IW%(location_str)s;
+            __MK_GetPublic_AXIS_REF(*__IW%(location)s_402);
+		AxsPub.axis->NetworkPosition = beremiz__IW%(location)s;
 %(init_axis_params)s
 %(fieldbus_interface_definition)s
 		__FirstTick = 0;
@@ -146,15 +182,13 @@
         AxsPub.axis->PowerFeedback = FSA == OperationEnabled;
     }
 #undef FSA_sep 
-	AxsPub.axis->ActualRawPosition = *(AxsPub.ActualPosition);
-	AxsPub.axis->ActualRawVelocity = *(AxsPub.ActualVelocity);
-	AxsPub.axis->ActualRawTorque = *(AxsPub.ActualTorque);
+%(default_variables_retrieve)s
 
 	// Extra variables retrieve
 %(extra_variables_retrieve)s
 }
 
-void __publish_%(location_str)s()
+void __publish_%(location)s()
 {
 	IEC_BOOL power = 
         ((*(AxsPub.StatusWord) & SW_VoltageEnabled) != 0) 
@@ -181,37 +215,61 @@
                 CW |= SwitchOn | EnableVoltage | QuickStop | EnableOperation;
 	    	}
 	    	break;
-	    case Fault :
-            /* TODO reset fault only when MC_Reset */
-            CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
-            CW |= FaultReset;
-	    	break;
+			//ssh_check
+//	    case Fault :
+//            /* TODO reset fault only when MC_Reset */
+//	    	AxsPub.axis->DriveFault = 1;
+//            CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
+//            CW |= FaultReset;
+//	    	break;
 	    default:
 	    	break;
 	}
+	//ssh_add
+	if(FaultFromStatusWord(*(AxsPub.StatusWord)) == SW_Fault)
+		AxsPub.axis->DriveFault = 1;
+	else{
+		AxsPub.axis->DriveFault = 0;
+		AxsPub.axis->DriveFaultReset = 0;
+	}
+	if(AxsPub.axis->DriveFaultReset){
+		CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
+		CW |= FaultReset;
+	}
+
+	//ssh_add
+	switch (HomingStatusWord(*(AxsPub.StatusWord))) {
+		case HomingInProgress:
+			break;
+		case HomingNotRunning:
+			break;
+		case HomingNotReached:
+			break;
+		case Homing_Completed:
+			if(!AxsPub.axis->HomingCompleted)
+				AxsPub.axis->HomingCompleted = 1;
+			break;
+		case HomingErrorInVelo:
+		case HomingErrorNotVelo:
+			if(!AxsPub.axis->HomingCompleted)
+				AxsPub.axis->HomingCompleted = 1;
+			break;
+		case HomingReserved:
+			break;
+	}
 #undef FSA_sep 
-    *(AxsPub.ControlWord) = CW;
+
+	//ssh_add
+%(modeofop_homing_method)s
+
+	*(AxsPub.ControlWord) = CW;
+
 
 	// CIA402 node modes of operation computation according to axis motion mode
-	switch (AxsPub.axis->AxisMotionMode) {
-		case mc_mode_cst:
-			*(AxsPub.ModesOfOperation) = 0x0a;
-			break;
-		case mc_mode_csv:
-			*(AxsPub.ModesOfOperation) = 0x09;
-			break;
-		default:
-			*(AxsPub.ModesOfOperation) = 0x08;
-			break;
-	}
+%(modeofop_computation_mode)s
 
 	// Default variables publish
-	*(AxsPub.TargetPosition) = 
-            AxsPub.axis->RawPositionSetPoint;
-	*(AxsPub.TargetVelocity) = 
-            AxsPub.axis->RawVelocitySetPoint;
-	*(AxsPub.TargetTorque) = 
-            AxsPub.axis->RawTorqueSetPoint;
+%(default_variables_publish)s
 
 	// Extra variables publish
 %(extra_variables_publish)s
--- a/etherlab/plc_etherlab.c	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/plc_etherlab.c	Fri Dec 25 17:12:02 2020 +0000
@@ -32,6 +32,43 @@
 %(used_pdo_entry_configuration)s
     {}
 };
+
+// Distributed Clock variables;
+%(dc_variable)s
+unsigned long long comp_period_ns = 500000ULL;
+
+int comp_count = 1;
+int comp_count_max;
+
+#define DC_FILTER_CNT          1024
+
+// EtherCAT slave-time-based DC Synchronization variables.
+static uint64_t dc_start_time_ns = 0LL;
+static uint64_t dc_time_ns = 0;
+static uint8_t  dc_started = 0;
+static int32_t  dc_diff_ns = 0;
+static int32_t  prev_dc_diff_ns = 0;
+static int64_t  dc_diff_total_ns = 0LL;
+static int64_t  dc_delta_total_ns = 0LL;
+static int      dc_filter_idx = 0;
+static int64_t  dc_adjust_ns;
+static int64_t  system_time_base = 0LL;
+
+static uint64_t dc_first_app_time = 0LL;
+
+unsigned long long frame_period_ns = 0ULL;
+
+int debug_count = 0;
+int slave_dc_used = 0;
+
+void dc_init(void);
+uint64_t system_time_ns(void);
+RTIME system2count(uint64_t time);
+void sync_distributed_clocks(void);
+void update_master_clock(void);
+RTIME calculate_sleeptime(uint64_t wakeup_time);
+uint64_t calculate_first(void);
+
 /*****************************************************************************/
 
 %(pdos_configuration_declaration)s
@@ -50,7 +87,7 @@
     LogMessage(level, sbuf, slen);\
 }
 
-/* Beremiz plugin functions */
+/* EtherCAT plugin functions */
 int __init_%(location)s(int argc,char **argv)
 {
     uint32_t abort_code;
@@ -81,10 +118,34 @@
     ecrt_master_set_send_interval(master, common_ticktime__);
 
     // slaves initialization
+/*
 %(slaves_initialization)s
+*/
+    // configure DC SYNC0/1 Signal
+%(config_dc)s
+
+    // select reference clock
+#if DC_ENABLE
+    {
+        int ret;
+        
+        ret = ecrt_master_select_reference_clock(master, slave0);
+        if (ret <0) {
+            fprintf(stderr, "Failed to select reference clock : %%s\n",
+                strerror(-ret));
+            return ret;
+        }
+    }
+#endif
 
     // extracting default value for not mapped entry in output PDOs
+/*
 %(slaves_output_pdos_default_values_extraction)s
+*/
+
+#if DC_ENABLE
+    dc_init();
+#endif
 
     if (ecrt_master_activate(master)){
         SLOGF(LOG_CRITICAL, "EtherCAT Master activation failed");
@@ -126,17 +187,20 @@
 
 }
 
+/*
 static RTIME _last_occur=0;
 static RTIME _last_publish=0;
 RTIME _current_lag=0;
 RTIME _max_jitter=0;
 static inline RTIME max(RTIME a,RTIME b){return a>b?a:b;}
+*/
 
 void __publish_%(location)s(void)
 {
 %(publish_variables)s
     ecrt_domain_queue(domain1);
     {
+        /*
         RTIME current_time = rt_timer_read();
         // Limit spining max 1/5 of common_ticktime
         RTIME maxdeadline = current_time + (common_ticktime__ / 5);
@@ -162,7 +226,281 @@
             //Consuming security margin ?
             _last_occur = current_time; //Drift forward
         }
-    }
+        */
+    }
+
+#if DC_ENABLE
+    if (comp_count == 0)
+        sync_distributed_clocks();
+#endif
+
     ecrt_master_send(master);
     first_sent = 1;
-}
+
+#if DC_ENABLE
+    if (comp_count == 0)
+        update_master_clock();
+
+    comp_count++;
+    
+    if (comp_count == comp_count_max)
+        comp_count = 0;
+#endif
+
+}
+
+/* Test Function For Parameter (SDO) Set */
+
+/*
+void GetSDOData(void){
+    uint32_t abort_code, test_value;
+    size_t result_size;
+    uint8_t value[4];
+
+    abort_code = 0;
+    result_size = 0;
+    test_value = 0;
+
+    if (ecrt_master_sdo_upload(master, 0, 0x1000, 0x0, (uint8_t *)value, 4, &result_size, &abort_code)) {
+        SLOGF(LOG_CRITICAL, "EtherCAT failed to get SDO Value");
+        }
+        test_value = EC_READ_S32((uint8_t *)value);
+        SLOGF(LOG_INFO, "SDO Value %%d", test_value);
+}
+*/
+
+int GetMasterData(void){
+    master = ecrt_open_master(0);
+    if (!master) {
+        SLOGF(LOG_CRITICAL, "EtherCAT master request failed!");
+        return -1;
+    }
+    return 0;
+}
+
+void ReleaseMasterData(void){
+    ecrt_release_master(master);
+}
+
+uint32_t GetSDOData(uint16_t slave_pos, uint16_t idx, uint8_t subidx, int size){
+    uint32_t abort_code, return_value;
+    size_t result_size;
+    uint8_t value[size];
+
+    abort_code = 0;
+    result_size = 0;
+
+    if (ecrt_master_sdo_upload(master, slave_pos, idx, subidx, (uint8_t *)value, size, &result_size, &abort_code)) {
+        SLOGF(LOG_CRITICAL, "EtherCAT failed to get SDO Value %%d %%d", idx, subidx);
+    }
+
+    return_value = EC_READ_S32((uint8_t *)value);
+    //SLOGF(LOG_INFO, "SDO Value %%d", return_value);
+
+    return return_value;
+}
+
+/*****************************************************************************/
+
+void dc_init(void)
+{
+    slave_dc_used = 1;
+
+    frame_period_ns = common_ticktime__;
+    if (frame_period_ns <= comp_period_ns) {
+        comp_count_max = comp_period_ns / frame_period_ns;
+        comp_count = 0;
+    } else  {
+        comp_count_max = 1;
+        comp_count = 0;
+    }
+
+    /* Set the initial master time */
+    dc_start_time_ns = system_time_ns();
+    dc_time_ns = dc_start_time_ns;
+
+    /* by woonggy */
+    dc_first_app_time = dc_start_time_ns;
+
+    /*
+     * Attention : The initial application time is also used for phase
+     * calculation for the SYNC0/1 interrupts. Please be sure to call it at
+     * the correct phase to the realtime cycle.
+     */
+    ecrt_master_application_time(master, dc_start_time_ns);
+}
+
+/****************************************************************************/
+
+/*
+ * Get the time in ns for the current cpu, adjusted by system_time_base.
+ *
+ * \attention Rather than calling rt_timer_read() directly, all application
+ * time calls should use this method instead.
+ *
+ * \ret The time in ns.
+ */
+uint64_t system_time_ns(void)
+{
+    RTIME time = rt_timer_read();   // wkk
+
+    if (unlikely(system_time_base > (SRTIME) time)) {
+        fprintf(stderr, "%%s() error: system_time_base greater than"
+                " system time (system_time_base: %%ld, time: %%llu\n",
+                __func__, system_time_base, time);
+        return time;
+    }
+    else {
+        return time - system_time_base;
+    }
+}
+
+/****************************************************************************/
+
+// Convert system time to Xenomai time in counts (via the system_time_base).
+RTIME system2count(uint64_t time)
+{
+    RTIME ret;
+
+    if ((system_time_base < 0) &&
+            ((uint64_t) (-system_time_base) > time)) {
+        fprintf(stderr, "%%s() error: system_time_base less than"
+                " system time (system_time_base: %%I64d, time: %%ld\n",
+                __func__, system_time_base, time);
+        ret = time;
+    }
+    else {
+        ret = time + system_time_base;
+    }
+
+    return (RTIME) rt_timer_ns2ticks(ret); // wkk
+}
+
+/*****************************************************************************/
+
+// Synchronise the distributed clocks
+void sync_distributed_clocks(void)
+{
+    uint32_t ref_time = 0;
+    RTIME prev_app_time = dc_time_ns;
+
+    // get reference clock time to synchronize master cycle
+    if(!ecrt_master_reference_clock_time(master, &ref_time)) {
+        dc_diff_ns = (uint32_t) prev_app_time - ref_time;
+    }
+    // call to sync slaves to ref slave
+    ecrt_master_sync_slave_clocks(master);
+    // set master time in nano-seconds
+    dc_time_ns = system_time_ns();
+    ecrt_master_application_time(master, dc_time_ns);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the sign of a number
+ * ie -1 for -ve value, 0 for 0, +1 for +ve value
+ * \ret val the sign of the value
+ */
+#define sign(val) \
+        ({ typeof (val) _val = (val); \
+        ((_val > 0) - (_val < 0)); })
+
+/*****************************************************************************/
+
+/*
+ * Update the master time based on ref slaves time diff
+ * called after the ethercat frame is sent to avoid time jitter in
+ * sync_distributed_clocks()
+ */
+void update_master_clock(void)
+{
+    // calc drift (via un-normalised time diff)
+    int32_t delta = dc_diff_ns - prev_dc_diff_ns;
+    prev_dc_diff_ns = dc_diff_ns;
+
+    // normalise the time diff
+    dc_diff_ns = dc_diff_ns >= 0 ?
+            ((dc_diff_ns + (int32_t)(frame_period_ns / 2)) %%
+                    (int32_t)frame_period_ns) - (frame_period_ns / 2) :
+                    ((dc_diff_ns - (int32_t)(frame_period_ns / 2)) %%
+                            (int32_t)frame_period_ns) - (frame_period_ns / 2) ;
+
+    // only update if primary master
+    if (dc_started) {
+        // add to totals
+        dc_diff_total_ns += dc_diff_ns;
+        dc_delta_total_ns += delta;
+        dc_filter_idx++;
+
+        if (dc_filter_idx >= DC_FILTER_CNT) {
+            dc_adjust_ns += dc_delta_total_ns >= 0 ?
+                    ((dc_delta_total_ns + (DC_FILTER_CNT / 2)) / DC_FILTER_CNT) :
+                    ((dc_delta_total_ns - (DC_FILTER_CNT / 2)) / DC_FILTER_CNT) ;
+
+            // and add adjustment for general diff (to pull in drift)
+            dc_adjust_ns += sign(dc_diff_total_ns / DC_FILTER_CNT);
+
+            // limit crazy numbers (0.1%% of std cycle time)
+            if (dc_adjust_ns < -1000) {
+                dc_adjust_ns = -1000;
+            }
+            if (dc_adjust_ns > 1000) {
+                dc_adjust_ns =  1000;
+            }
+            // reset
+            dc_diff_total_ns = 0LL;
+            dc_delta_total_ns = 0LL;
+            dc_filter_idx = 0;
+        }
+        // add cycles adjustment to time base (including a spot adjustment)
+        system_time_base += dc_adjust_ns + sign(dc_diff_ns);
+    }
+    else {
+        dc_started = (dc_diff_ns != 0);
+
+        if (dc_started) {
+#if DC_ENABLE && DEBUG_MODE
+            // output first diff
+            fprintf(stderr, "First master diff: %%d\n", dc_diff_ns);
+#endif
+            // record the time of this initial cycle
+            dc_start_time_ns = dc_time_ns;
+        }
+    }
+}
+
+/*****************************************************************************/
+
+/*
+ * Calculate the sleeptime
+ */
+RTIME calculate_sleeptime(uint64_t wakeup_time)
+{
+    RTIME wakeup_count = system2count (wakeup_time);
+    RTIME current_count = rt_timer_read();
+
+    if ((wakeup_count < current_count) || (wakeup_count > current_count + (50 * frame_period_ns)))  {
+        fprintf(stderr, "%%s(): unexpected wake time! wc = %%lld\tcc = %%lld\n", __func__, wakeup_count, current_count);
+    }
+
+    return wakeup_count;
+}
+
+/*****************************************************************************/
+
+/*
+ * Calculate the sleeptime
+ */
+uint64_t calculate_first(void)
+{
+    uint64_t dc_remainder = 0LL;
+    uint64_t dc_phase_set_time = 0LL;
+    
+    dc_phase_set_time = system_time_ns()+ frame_period_ns * 10;
+    dc_remainder = (dc_phase_set_time - dc_first_app_time) %% frame_period_ns;
+
+    return dc_phase_set_time + frame_period_ns - dc_remainder;
+}
+
+/*****************************************************************************/
--- a/etherlab/pous.xml	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/pous.xml	Fri Dec 25 17:12:02 2020 +0000
@@ -110,13 +110,13 @@
       VALUE := 'None';
     END_IF;
   1:
-    {if (AcquireSDOLock()) __SET_VAR(data__->,STATE,, 2)}
+    {if (AcquireSDOLock()) __SET_VAR(data__->,STATE, 2)}
   2:
     IF PY0.ACK THEN
       STATE := 3;
     END_IF;
   3:
-    {if (HasAnswer()) __SET_VAR(data__->,STATE,, 4)}
+    {if (HasAnswer()) __SET_VAR(data__->,STATE, 4)}
   4:
     IF PY1.ACK THEN
       ACK := 1;
@@ -231,13 +231,13 @@
       ERROR := 0;
     END_IF;
   1:
-    {if (AcquireSDOLock()) __SET_VAR(data__->,STATE,, 2)}
+    {if (AcquireSDOLock()) __SET_VAR(data__->,STATE, 2)}
   2:
     IF PY0.ACK THEN
       STATE := 3;
     END_IF;
   3:
-    {if (HasAnswer()) __SET_VAR(data__->,STATE,, 4)}
+    {if (HasAnswer()) __SET_VAR(data__->,STATE, 4)}
   4:
     IF PY1.ACK THEN
       ACK := 1;
--- a/etherlab/runtime_etherlab.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/etherlab/runtime_etherlab.py	Fri Dec 25 17:12:02 2020 +0000
@@ -1,3 +1,14 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# this file is part of beremiz
+#
+# copyright (c) 2011-2014: laurent bessard, edouard tisserant
+#                          rtes lab : crkim, jblee, youcu
+#                          higen motor : donggu kang
+#
+# see copying file for copyrights details.
+
 from __future__ import absolute_import
 import os
 import signal
--- a/modbus/mb_runtime.c	Fri Dec 25 17:11:38 2020 +0000
+++ b/modbus/mb_runtime.c	Fri Dec 25 17:12:02 2020 +0000
@@ -25,8 +25,10 @@
 
 #include <stdio.h>
 #include <string.h>  /* required for memcpy() */
+#include <errno.h>
 #include <time.h>
 #include <signal.h>
+#include <unistd.h>  /* required for pause() */
 #include "mb_slave_and_master.h"
 #include "MB_%(locstr)s.h"
 
@@ -294,24 +296,10 @@
 
 static void *__mb_client_thread(void *_index)  {
 	int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
-	struct timespec next_cycle;
-	int period_sec  =  client_nodes[client_node_id].comm_period / 1000;          /* comm_period is in ms */
-	int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */
 
 	// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
 	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
 	
-	// configure the timer for periodic activation
-    {
-      struct itimerspec timerspec;
-      timerspec.it_interval.tv_sec  = period_sec;
-      timerspec.it_interval.tv_nsec = period_nsec;
-      timerspec.it_value            = timerspec.it_interval;
-      
-      if (timer_settime(client_nodes[client_node_id].timer_id, 0 /* flags */, &timerspec, NULL) < 0)
-        fprintf(stderr, "Modbus plugin: Error configuring periodic activation timer for Modbus client %%s.\n", client_nodes[client_node_id].location);          
-    }
-
     /* loop the communication with the client
      * 
          * When the client thread has difficulty communicating with remote client and/or server (network issues, for example),
@@ -341,7 +329,7 @@
 		/*
 		struct timespec cur_time;
 		clock_gettime(CLOCK_MONOTONIC, &cur_time);
-		fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec);
+		fprintf(stderr, "Modbus client thread (%%d) - new cycle (%%ld:%%ld)!\n", client_node_id, cur_time.tv_sec, cur_time.tv_nsec);
 		*/
 		int req;
 		for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){
@@ -358,8 +346,10 @@
             if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0))
                 continue;
             
-            //fprintf(stderr, "Modbus plugin: RUNNING<###> of Modbus request %%d  (periodic = %%d  flag_exec_req = %%d)\n", 
-            //        req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+            /*
+            fprintf(stderr, "Modbus client thread (%%d): RUNNING Modbus request %%d  (periodic = %%d  flag_exec_req = %%d)\n", 
+                    client_node_id, req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+            */
             
 			int res_tmp = __execute_mb_request(req);
 			client_requests[req].tn_error_code = 0; // assume success
@@ -458,47 +448,77 @@
 
 
 
-/* Function to activate a client node's thread */
-/* returns -1 if it could not send the signal */
-static int __signal_client_thread(int client_node_id) {
-    /* We TRY to signal the client thread.
-     * We do this because this function can be called at the end of the PLC scan cycle
-     * and we don't want it to block at that time.
-     */
-    if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) != 0)
-        return -1;
-    client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
-    pthread_cond_signal (&(client_nodes[client_node_id].condv));
-    pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
-    return 0;
-}
-
-
-
-/* Function that will be called whenever a client node's periodic timer expires. */
-/* The client node's thread will be waiting on a condition variable, so this function simply signals that 
- * condition variable.
+
+
+/* Thread that simply implements a periodic 'timer',
+ *  i.e. periodically sends signal to the  thread running __mb_client_thread()
  * 
- * The same callback function is called by the timers of all client nodes. The id of the client node
- * in question will be passed as a parameter to the call back function.
+ * Note that we do not use a posix timer (timer_create() ) because there doesn't seem to be a way
+ * of having the timer notify the thread that is portable across Xenomai and POSIX.
+ * - SIGEV_THREAD    : not supported by Xenomai
+ * - SIGEV_THREAD_ID : Linux specific (i.e. non POSIX)
+ *                     Even so, I did not get it to work under Linux (issues with the header files)
+ * - SIGEV_SIGNAL    : Will not work, as signal is sent to random thread in process!
  */
-void __client_node_timer_callback_function(union sigval sigev_value) {
-    /* signal the client node's condition variable on which the client node's thread should be waiting... */
-    /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
-    //pthread_mutex_lock  (&(client_nodes[sigev_value.sival_int].mutex));
-    if (pthread_mutex_trylock (&(client_nodes[sigev_value.sival_int].mutex)) != 0)
-        /* we never get to signal the thread for activation. But that is OK.
-         * If it still in the communication cycle (during which the mutex is kept locked)
-         * then that means that the communication cycle is falling behing in the periodic 
-         * communication cycle, and we therefore need to skip a period.
+static void *__mb_client_timer_thread(void *_index) {
+	int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
+	struct timespec next_cycle;
+
+	int period_sec  =  client_nodes[client_node_id].comm_period / 1000;          /* comm_period is in ms */
+	int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */
+
+	// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
+	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+    
+    if (client_nodes[client_node_id].comm_period <= 0) {
+        // No periodic activation required => nothing to do! 
+        while (1) pause(); // wait to be canceled when program terminates (shutdown() is called)
+        return NULL;  // not really necessary, just makes it easier to understand the code.
+    }
+
+	// get the current time
+	clock_gettime(CLOCK_MONOTONIC, &next_cycle);
+
+    while(1) {
+        // Determine absolute time instant for starting the next cycle
+        struct timespec prev_cycle, now;
+        prev_cycle = next_cycle;
+        timespec_add(next_cycle, period_sec, period_nsec);
+                
+        /* NOTE:
+         * It is probably un-necessary to check for overflow of timer!
+         * Even in 32 bit systems this will take at least 68 years since the computer booted
+         * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0
+         * every time the system boots). On 64 bit systems, it will take over 
+         * 10^11 years to overflow.
          */
-        return;
-    client_nodes[sigev_value.sival_int].execute_req  = 1; // tell the thread to execute
-    client_nodes[sigev_value.sival_int].periodic_act = 1; // tell the thread the activation was done by periodic timer   
-    pthread_cond_signal (&(client_nodes[sigev_value.sival_int].condv));
-    pthread_mutex_unlock(&(client_nodes[sigev_value.sival_int].mutex));
-}
-
+        clock_gettime(CLOCK_MONOTONIC, &now);
+        if (next_cycle.tv_sec < prev_cycle.tv_sec) {
+           /* Timer overflow. See NOTE B above */
+            next_cycle = now;
+            timespec_add(next_cycle, period_sec, period_nsec);
+        }
+        
+        while (0 != clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL));
+        
+        /* signal the client node's condition variable on which the client node's thread should be waiting... */
+        /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
+        if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) == 0) {
+            client_nodes[client_node_id].execute_req  = 1; // tell the thread to execute
+            client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer   
+            pthread_cond_signal (&(client_nodes[client_node_id].condv));
+            pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+        } else {
+            /* We never get to signal the thread for activation. But that is OK.
+             * If it still in the communication cycle (during which the mutex is kept locked)
+             * then that means that the communication cycle is falling behing in the periodic 
+             * communication cycle, and we therefore need to skip a period.
+             */
+        }
+    }
+
+    return NULL; // humour the compiler -> will never be executed!
+}
 
 
 int __cleanup_%(locstr)s ();
@@ -579,6 +599,7 @@
 		client_nodes[index].init_state = 1; // we have created the node 
 		
 		/* initialize the mutex variable that will be used by the thread handling the client node */
+        bzero(&(client_nodes[index].mutex), sizeof(pthread_mutex_t));
         if (pthread_mutex_init(&(client_nodes[index].mutex), NULL) < 0) {
 			fprintf(stderr, "Modbus plugin: Error creating mutex for modbus client node %%s\n", client_nodes[index].location);
 			goto error_exit;                
@@ -586,6 +607,7 @@
 		client_nodes[index].init_state = 2; // we have created the mutex
 		
 		/* initialize the condition variable that will be used by the thread handling the client node */
+        bzero(&(client_nodes[index].condv), sizeof(pthread_cond_t));
         if (pthread_cond_init(&(client_nodes[index].condv), NULL) < 0) {
 			fprintf(stderr, "Modbus plugin: Error creating condition variable for modbus client node %%s\n", client_nodes[index].location);
 			goto error_exit;                
@@ -593,22 +615,17 @@
         client_nodes[index].execute_req = 0; //variable associated with condition variable
 		client_nodes[index].init_state = 3; // we have created the condition variable
 		
-		/* initialize the timer that will be used to periodically activate the client node */
-        {
-            // start off by reseting the flag that will be set whenever the timer expires
-            client_nodes[index].periodic_act = 0;
-
-            struct sigevent evp;
-            evp.sigev_notify            = SIGEV_THREAD; /* Notification method - call a function in a new thread context */
-            evp.sigev_value.sival_int   = index;        /* Data passed to function upon notification - used to indentify which client node to activate */
-            evp.sigev_notify_function   = __client_node_timer_callback_function; /* function to call upon timer expiration */
-            evp.sigev_notify_attributes = NULL;         /* attributes for new thread in which sigev_notify_function will be called/executed */
-            
-            if (timer_create(CLOCK_MONOTONIC, &evp, &(client_nodes[index].timer_id)) < 0) {
-                fprintf(stderr, "Modbus plugin: Error creating timer for modbus client node %%s\n", client_nodes[index].location);
-                goto error_exit;                
-            }
-        }
+		/* launch a thread to handle this client node timer */
+		{
+			int res = 0;
+			pthread_attr_t attr;
+			res |= pthread_attr_init(&attr);
+			res |= pthread_create(&(client_nodes[index].timer_thread_id), &attr, &__mb_client_timer_thread, (void *)((char *)NULL + index));
+			if (res !=  0) {
+				fprintf(stderr, "Modbus plugin: Error starting timer thread for modbus client node %%s\n", client_nodes[index].location);
+				goto error_exit;
+			}
+		}
         client_nodes[index].init_state = 4; // we have created the timer
 
 		/* launch a thread to handle this client node */
@@ -712,13 +729,26 @@
          */
         if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) {
             int client_node_id = client_requests[index].client_node_id;
-            if (__signal_client_thread(client_node_id) >= 0) {
-                /* - upon success, set flag_exec_started
-                 * - both flags (flag_exec_req and flag_exec_started) will be reset
-                 *   once the transaction has completed.
-                 */
-                client_requests[index].flag_exec_started = 1;    
-            }
+            
+             /* We TRY to signal the client thread.
+              * We do this because this function can be called at the end of the PLC scan cycle
+              * and we don't want it to block at that time.
+              */
+             if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) == 0) {
+                 client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
+                 pthread_cond_signal (&(client_nodes[client_node_id].condv));
+                 pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+                 /* - upon success, set flag_exec_started
+                  * - both flags (flag_exec_req and flag_exec_started) will be reset
+                  *   once the transaction has completed.
+                  */
+                 client_requests[index].flag_exec_started = 1;    
+             } else {
+                 /* The mutex is locked => the client thread is currently executing MB transactions.
+                  * We will try to activate it in the next PLC cycle...
+                  * For now, do nothing.
+                  */
+             }
         }                    
     }
 }
@@ -766,10 +796,12 @@
 
 		close = 0;
 		if (client_nodes[index].init_state >= 4) {
-			// timer was created, so we try to destroy it!
-			close  = timer_delete(client_nodes[index].timer_id);
+			// timer thread was launched, so we try to cancel it!
+			close  = pthread_cancel(client_nodes[index].timer_thread_id);
+			close |= pthread_join  (client_nodes[index].timer_thread_id, NULL);
 			if (close < 0)
-				fprintf(stderr, "Modbus plugin: Error destroying timer for modbus client node %%s\n", client_nodes[index].location);
+				fprintf(stderr, "Modbus plugin: Error closing timer thread for modbus client node %%s\n", client_nodes[index].location);
+
 		}
 		res |= close;
 
@@ -805,6 +837,7 @@
 		client_nodes[index].init_state = 0;
 	}
 	
+//fprintf(stderr, "Modbus plugin: __cleanup_%%s()  5  close=%%d   res=%%d\n", client_nodes[index].location, close, res);
 	/* kill thread and close connections of each modbus server node */
 	for (index=0; index < NUMBER_OF_SERVER_NODES; index++) {
 		close = 0;
--- a/modbus/mb_runtime.h	Fri Dec 25 17:11:38 2020 +0000
+++ b/modbus/mb_runtime.h	Fri Dec 25 17:12:02 2020 +0000
@@ -106,7 +106,7 @@
 	    u64		comm_period;// period to use when periodically sending requests to remote server
 	    int		prev_error; // error code of the last printed error message (0 when no error) 
 	    pthread_t   thread_id;  // thread handling all communication for this client node
-	    timer_t      timer_id;  // timer used to periodically activate this client node's thread
+	    pthread_t	timer_thread_id;  // thread handling periodical timer for this client node
 	    pthread_mutex_t mutex;  // mutex to be used with the following condition variable
         pthread_cond_t  condv;  // used to signal the client thread when to start new modbus transactions
         int       execute_req;  /* used, in association with condition variable,  
--- a/modbus/modbus.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/modbus/modbus.py	Fri Dec 25 17:12:02 2020 +0000
@@ -30,6 +30,7 @@
 from modbus.mb_utils import *
 from ConfigTreeNode import ConfigTreeNode
 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
+import util.paths as paths
 
 base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
 base_folder = os.path.join(base_folder, "..")
@@ -302,6 +303,8 @@
 #
 #
 
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
 class _ModbusTCPclientPlug(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -379,6 +382,8 @@
 #
 #
 
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
 class _ModbusTCPserverPlug(object):
     # NOTE: the Port number is a 'string' and not an 'integer'!
     # This is because the underlying modbus library accepts strings
@@ -466,6 +471,8 @@
 #
 #
 
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
 class _ModbusRTUclientPlug(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -557,6 +564,7 @@
 #
 #
 
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
 
 class _ModbusRTUslavePlug(object):
     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
@@ -1037,4 +1045,18 @@
         # LDFLAGS.append(" -lws2_32 ")  # on windows we need to load winsock
         # library!
 
-        return [(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True
+        websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
+        websettingcode = websettingfile.read()
+        websettingfile.close()
+
+        location_str = "_".join(map(str, self.GetCurrentLocation()))
+        websettingcode = websettingcode % locals()
+
+        runtimefile_path = os.path.join(buildpath, "runtime_modbus_websettings.py")
+        runtimefile = open(runtimefile_path, 'w')
+        runtimefile.write(websettingcode)
+        runtimefile.close()
+
+        return ([(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True,
+                ("runtime_modbus_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modbus/web_settings.py	Fri Dec 25 17:12:02 2020 +0000
@@ -0,0 +1,628 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz runtime.
+#
+# Copyright (C) 2020: Mario de Sousa
+#
+# See COPYING.Runtime file for copyrights details.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+
+
+##############################################################################################
+# This file implements an extension to the web server embedded in the Beremiz_service.py     #
+# runtime manager (webserver is in runtime/NevowServer.py).                                  #
+#                                                                                            #
+# The extension implemented in this file allows for runtime configuration                    #
+# of Modbus plugin parameters                                                                #
+##############################################################################################
+
+
+
+import json
+import os
+import ctypes
+import string
+import hashlib
+
+from formless import annotate, webform
+
+import runtime.NevowServer as NS
+
+# Directory in which to store the persistent configurations
+# Should be a directory that does not get wiped on reboot!
+_ModbusConfFiledir = WorkingDir
+
+# List of all Web Extension Setting nodes we are handling.
+# One WebNode each for:
+#   - Modbus TCP client 
+#   - Modbus TCP server
+#   - Modbus RTU client
+#   - Modbus RTU slave
+# configured in the loaded PLC (i.e. the .so file loaded into memory)
+# Each entry will be a dictionary. See _AddWebNode() for the details
+# of the data structure in each entry.
+_WebNodeList = []
+
+
+
+
+class MB_StrippedString(annotate.String):
+    def __init__(self, *args, **kwargs):
+        annotate.String.__init__(self, strip = True, *args, **kwargs)
+
+
+class MB_StopBits(annotate.Choice):
+    _choices = [0, 1, 2]
+
+    def coerce(self, val, configurable):
+        return int(val)
+    def __init__(self, *args, **kwargs):
+        annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
+
+
+class MB_Baud(annotate.Choice):
+    _choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
+
+    def coerce(self, val, configurable):
+        return int(val)
+    def __init__(self, *args, **kwargs):
+        annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
+
+
+class MB_Parity(annotate.Choice):
+    # For more info on what this class really does, have a look at the code in
+    # file twisted/nevow/annotate.py
+    # grab this code from $git clone https://github.com/twisted/nevow/
+    # 
+    # Warning: do _not_ name this variable choice[] without underscore, as that name is
+    # already used for another similar variable by the underlying class annotate.Choice
+    _choices = [  0,      1,      2  ]
+    _label   = ["none", "odd", "even"]
+    
+    def choice_to_label(self, key):
+        #PLCObject.LogMessage("Modbus web server extension::choice_to_label()  " + str(key))
+        return self._label[key]
+    
+    def coerce(self, val, configurable):
+        """Coerce a value with the help of an object, which is the object
+        we are configuring.
+        """
+        # Basically, make sure the value the user introduced is valid, and transform
+        # into something that is valid if necessary or mark it as an error 
+        # (by raising an exception ??).
+        #
+        # We are simply using this functions to transform the input value (a string)
+        # into an integer. Note that although the available options are all
+        # integers (0, 1 or 2), even though what is shown on the user interface
+        # are actually strings, i.e. the labels), these parameters are for some 
+        # reason being parsed as strings, so we need to map them back to an
+        # integer.
+        #
+        #PLCObject.LogMessage("Modbus web server extension::coerce  " + val )
+        return int(val)
+
+    def __init__(self, *args, **kwargs):
+        annotate.Choice.__init__(self, 
+                                 choices   = self._choices,
+                                 stringify = self.choice_to_label,
+                                 *args, **kwargs)
+
+
+
+# Parameters we will need to get from the C code, but that will not be shown
+# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
+#
+# The annotate type entry is basically useless and is completely ignored.
+# We kee that entry so that this list can later be correctly merged with the
+# following lists...
+General_parameters = [
+    #    param. name       label                        ctype type         annotate type
+    # (C code var name)   (used on web interface)      (C data type)       (web data type)
+    #                                                                      (annotate.String,
+    #                                                                       annotate.Integer, ...)
+    ("config_name"      , _("")                      , ctypes.c_char_p,    annotate.String),
+    ("addr_type"        , _("")                      , ctypes.c_char_p,    annotate.String)
+    ]                                                                      
+                                                                           
+# Parameters we will need to get from the C code, and that _will_ be shown
+# on the web interface.
+TCPclient_parameters = [                                                   
+    #    param. name       label                        ctype type         annotate type
+    # (C code var name)   (used on web interface)      (C data type)       (web data type)
+    #                                                                      (annotate.String,
+    #                                                                       annotate.Integer, ...)
+    ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    MB_StrippedString),
+    ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    MB_StrippedString),
+    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer )
+    ]
+
+RTUclient_parameters = [                                                   
+    #    param. name       label                        ctype type         annotate type
+    # (C code var name)   (used on web interface)      (C data type)       (web data type)
+    #                                                                      (annotate.String,
+    #                                                                       annotate.Integer, ...)
+    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
+    ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
+    ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
+    ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
+    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
+    ]
+
+TCPserver_parameters = [                                                   
+    #    param. name       label                        ctype type         annotate type
+    # (C code var name)   (used on web interface)      (C data type)       (web data type)
+    #                                                                      (annotate.String,
+    #                                                                       annotate.Integer, ...)
+    ("host"             , _("Local IP Address")      , ctypes.c_char_p,    MB_StrippedString),
+    ("port"             , _("Local Port Number")     , ctypes.c_char_p,    MB_StrippedString),
+    ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer )
+    ]
+
+RTUslave_parameters = [                                                   
+    #    param. name       label                        ctype type         annotate type
+    # (C code var name)   (used on web interface)      (C data type)       (web data type)
+    #                                                                      (annotate.String,
+    #                                                                       annotate.Integer, ...)
+    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
+    ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
+    ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
+    ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
+    ("slave_id"         , _("Slave ID")              , ctypes.c_ulonglong, annotate.Integer)
+    ]
+
+
+
+
+# Dictionary containing List of Web viewable parameters
+# Note: the dictionary key must be the same as the string returned by the 
+# __modbus_get_ClientNode_addr_type()
+# __modbus_get_ServerNode_addr_type()
+# functions implemented in C (see modbus/mb_runtime.c)
+_client_WebParamListDict = {}
+_client_WebParamListDict["tcp"  ] = TCPclient_parameters
+_client_WebParamListDict["rtu"  ] = RTUclient_parameters
+_client_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
+
+_server_WebParamListDict = {}
+_server_WebParamListDict["tcp"  ] = TCPserver_parameters
+_server_WebParamListDict["rtu"  ] = RTUslave_parameters
+_server_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
+
+WebParamListDictDict = {}
+WebParamListDictDict['client'] = _client_WebParamListDict
+WebParamListDictDict['server'] = _server_WebParamListDict
+
+
+
+
+
+
+def _SetModbusSavedConfiguration(WebNode_id, newConfig):
+    """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
+    WebNode_entry = _WebNodeList[WebNode_id]
+
+    if WebNode_entry["DefaultConfiguration"] == newConfig:
+
+        _DelModbusSavedConfiguration(WebNode_id)
+        WebNode_entry["ModbusSavedConfiguration"] = None
+
+    else:
+
+        # Add the addr_type and node_type to the data that will be saved to file
+        # This allows us to confirm the saved data contains the correct addr_type
+        # when loading from file
+        save_info = {}
+        save_info["addr_type"] = ["addr_type"]
+        save_info["node_type"] = WebNode_entry["node_type"]
+        save_info["config"   ] = newConfig
+        
+        filename = WebNode_entry["filename"]
+
+        with open(os.path.realpath(filename), 'w') as f:
+            json.dump(save_info, f, sort_keys=True, indent=4)
+            
+        WebNode_entry["ModbusSavedConfiguration"] = newConfig
+
+
+
+
+def _DelModbusSavedConfiguration(WebNode_id):
+    """ Deletes the file cotaining the persistent Modbus configuration """
+    filename = _WebNodeList[WebNode_id]["filename"]
+    
+    if os.path.exists(filename):
+        os.remove(filename)
+
+
+
+
+def _GetModbusSavedConfiguration(WebNode_id):
+    """
+    Returns a dictionary containing the Modbus parameter configuration
+    that was last saved to file. If no file exists, or file contains 
+    wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
+    addr_type of the WebNode_id), then return None
+    """
+    filename = _WebNodeList[WebNode_id]["filename"]
+    try:
+        #if os.path.isfile(filename):
+        save_info = json.load(open(filename))
+    except Exception:    
+        return None
+
+    if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
+        return None
+    if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
+        return None
+    if "config" not in save_info:
+        return None
+    
+    saved_config = save_info["config"]
+    
+    #if _CheckConfiguration(saved_config):
+    #    return saved_config
+    #else:
+    #    return None
+
+    return saved_config
+
+
+
+def _GetModbusPLCConfiguration(WebNode_id):
+    """
+    Returns a dictionary containing the current Modbus parameter configuration
+    stored in the C variables in the loaded PLC (.so file)
+    """
+    current_config = {}
+    C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
+    WebParamList   = _WebNodeList[WebNode_id]["WebParamList"]
+    GetParamFuncs  = _WebNodeList[WebNode_id]["GetParamFuncs"]
+
+    for par_name, x1, x2, x3 in WebParamList:
+        value = GetParamFuncs[par_name](C_node_id)
+        if value is not None:
+            current_config[par_name] = value
+    
+    return current_config
+
+
+
+def _SetModbusPLCConfiguration(WebNode_id, newconfig):
+    """
+    Stores the Modbus parameter configuration into the
+    the C variables in the loaded PLC (.so file)
+    """
+    C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
+    SetParamFuncs  = _WebNodeList[WebNode_id]["SetParamFuncs"]
+
+    for par_name in newconfig:
+        value = newconfig[par_name]
+        if value is not None:
+            SetParamFuncs[par_name](C_node_id, value)
+            
+
+
+
+def _GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument):
+    """
+    Callback function, called by the web interface (NevowServer.py)
+    to fill in the default value of each parameter of the web form
+    
+    Note that the real callback function is a dynamically created function that
+    will simply call this function to do the work. It will also pass the WebNode_id 
+    as a parameter.
+    """    
+    try:
+        return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
+    except Exception:
+        return ""
+
+
+
+def OnModbusButtonSave(**kwargs):
+    """
+    Function called when user clicks 'Save' button in web interface
+    The function will configure the Modbus plugin in the PLC with the values
+    specified in the web interface. However, values must be validated first!
+    
+    Note that this function does not get called directly. The real callback
+    function is the dynamic __OnButtonSave() function, which will add the 
+    "WebNode_id" argument, and call this function to do the work.
+    """
+
+    #PLCObject.LogMessage("Modbus web server extension::OnModbusButtonSave()  Called")
+    
+    newConfig    = {}
+    WebNode_id   =  kwargs.get("WebNode_id", None)
+    WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
+    
+    for par_name, x1, x2, x3 in WebParamList:
+        value = kwargs.get(par_name, None)
+        if value is not None:
+            newConfig[par_name] = value
+
+    # First check if configuration is OK.
+    # Note that this is not currently required, as we use drop down choice menus
+    # for baud, parity and sop bits, so the values should always be correct!
+    #if not _CheckWebConfiguration(newConfig):
+    #    return
+    
+    # store to file the new configuration so that 
+    # we can recoup the configuration the next time the PLC
+    # has a cold start (i.e. when Beremiz_service.py is retarted)
+    _SetModbusSavedConfiguration(WebNode_id, newConfig)
+
+    # Configure PLC with the current Modbus parameters
+    _SetModbusPLCConfiguration(WebNode_id, newConfig)
+
+    # Update the viewable configuration
+    # The PLC may have coerced the values on calling _SetModbusPLCConfiguration()
+    # so we do not set it directly to newConfig
+    _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
+
+
+
+def OnModbusButtonReset(**kwargs):
+    """
+    Function called when user clicks 'Delete' button in web interface
+    The function will delete the file containing the persistent
+    Modbus configution
+    """
+
+    WebNode_id = kwargs.get("WebNode_id", None)
+    
+    # Delete the file
+    _DelModbusSavedConfiguration(WebNode_id)
+
+    # Set the current configuration to the default (hardcoded in C)
+    new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
+    _SetModbusPLCConfiguration(WebNode_id, new_config)
+    
+    #Update the webviewconfiguration
+    _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
+    
+    # Reset ModbusSavedConfiguration
+    _WebNodeList[WebNode_id]["ModbusSavedConfiguration"] = None
+    
+
+
+
+
+def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
+    """
+    Load from the compiled code (.so file, aloready loaded into memmory)
+    the configuration parameters of a specific Modbus plugin node.
+    This function works with both client and server nodes, depending on the
+    Get/SetParamFunc dictionaries passed to it (either the client or the server
+    node versions of the Get/Set functions)
+    """
+    WebNode_entry = {}
+
+    # Get the config_name from the C code...
+    config_name = GetParamFuncs["config_name"](C_node_id)
+    # Get the addr_type from the C code...
+    # addr_type will be one of "tcp", "rtu" or "ascii"
+    addr_type   = GetParamFuncs["addr_type"  ](C_node_id)   
+    # For some operations we cannot use the config name (e.g. filename to store config)
+    # because the user may be using characters that are invalid for that purpose ('/' for
+    # example), so we create a hash of the config_name, and use that instead.
+    config_hash = hashlib.md5(config_name).hexdigest()
+    
+    #PLCObject.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
+
+    # Add the new entry to the global list
+    # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
+    #       WebNode_entry will be stored as a reference, so we can later insert parameters at will.
+    global _WebNodeList
+    _WebNodeList.append(WebNode_entry)
+    WebNode_id = len(_WebNodeList) - 1
+
+    # store all WebNode relevant data for future reference
+    #
+    # Note that "WebParamList" will reference one of:
+    #  - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
+    WebNode_entry["C_node_id"    ] = C_node_id
+    WebNode_entry["config_name"  ] = config_name 
+    WebNode_entry["config_hash"  ] = config_hash
+    WebNode_entry["filename"     ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
+    WebNode_entry["GetParamFuncs"] = GetParamFuncs
+    WebNode_entry["SetParamFuncs"] = SetParamFuncs
+    WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type] 
+    WebNode_entry["addr_type"    ] = addr_type  # 'tcp', 'rtu', or 'ascii' (as returned by C function)
+    WebNode_entry["node_type"    ] = node_type  # 'client', 'server'
+        
+    
+    # Dictionary that contains the Modbus configuration currently being shown
+    # on the web interface
+    # This configuration will almost always be identical to the current
+    # configuration in the PLC (i.e., the current state stored in the 
+    # C variables in the .so file).
+    # The configuration viewed on the web will only be different to the current 
+    # configuration when the user edits the configuration, and when
+    # the user asks to save an edited configuration that contains an error.
+    WebNode_entry["WebviewConfiguration"] = None
+
+    # Upon PLC load, this Dictionary is initialised with the Modbus configuration
+    # hardcoded in the C file
+    # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
+    WebNode_entry["DefaultConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
+    WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
+    
+    # Dictionary that stores the Modbus configuration currently stored in a file
+    # Currently only used to decide whether or not to show the "Delete" button on the
+    # web interface (only shown if "ModbusSavedConfiguration" is not None)
+    SavedConfig = _GetModbusSavedConfiguration(WebNode_id)
+    WebNode_entry["ModbusSavedConfiguration"] = SavedConfig
+    
+    if SavedConfig is not None:
+        _SetModbusPLCConfiguration(WebNode_id, SavedConfig)
+        WebNode_entry["WebviewConfiguration"] = SavedConfig
+        
+    # Define the format for the web form used to show/change the current parameters
+    # We first declare a dynamic function to work as callback to obtain the default values for each parameter
+    # Note: We transform every parameter into a string
+    #       This is not strictly required for parameters of type annotate.Integer that will correctly
+    #           accept the default value as an Integer python object
+    #       This is obviously also not required for parameters of type annotate.String, that are
+    #           always handled as strings.
+    #       However, the annotate.Choice parameters (and all parameters that derive from it,
+    #           sucn as Parity, Baud, etc.) require the default value as a string
+    #           even though we store it as an integer, which is the data type expected
+    #           by the set_***() C functions in mb_runtime.c
+    def __GetWebviewConfigurationValue(ctx, argument):
+        return str(_GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument))
+    
+    webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
+                    for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
+
+    # Configure the web interface to include the Modbus config parameters
+    def __OnButtonSave(**kwargs):
+        OnModbusButtonSave(WebNode_id=WebNode_id, **kwargs)
+
+    WebSettings = NS.newExtensionSetting("Modbus #"+ str(WebNode_id), config_hash)
+
+    WebSettings.addSettings(
+        "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
+        _("Modbus Configuration: ") + config_name,     # description (user visible label)
+        webFormInterface,                              # fields
+        _("Apply"), # button label
+        __OnButtonSave)                                # callback   
+    
+    def __OnButtonReset(**kwargs):
+        return OnModbusButtonReset(WebNode_id = WebNode_id, **kwargs)
+            
+    def getModbusConfigStatus():
+        if WebNode_entry["WebviewConfiguration"] == WebNode_entry["DefaultConfiguration"]:
+            return "Unchanged"
+        return "Modified"
+
+    WebSettings.addSettings(
+        "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
+        _("Modbus Configuration: ") + config_name,  # description (user visible label)
+        [ ("status",
+           annotate.String(label=_("Current state"),
+                           immutable=True,
+                           default=lambda *k:getModbusConfigStatus())),
+        ],                                       # fields  (empty, no parameters required!)
+        _("Reset"), # button label
+        __OnButtonReset)
+
+
+
+
+def _runtime_modbus_websettings_%(location_str)s_init():
+    """
+    Callback function, called (by PLCObject.py) when a new PLC program
+    (i.e. XXX.so file) is transfered to the PLC runtime
+    and loaded into memory
+    """
+
+    #PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
+
+    if PLCObject.PLClibraryHandle is None:
+        # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
+        # Hmm... This shold never occur!! 
+        return  
+    
+    # Get the number of Modbus Client and Servers (Modbus plugin)
+    # configured in the currently loaded PLC project (i.e., the .so file)
+    # If the "__modbus_plugin_client_node_count" 
+    # or the "__modbus_plugin_server_node_count" C variables 
+    # are not present in the .so file we conclude that the currently loaded 
+    # PLC does not have the Modbus plugin included (situation (2b) described above init())
+    try:
+        # XXX TODO : stop reading from PLC .so file. This code is template code
+        #            that can use modbus extension build data, such as client node count.
+        client_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_client_node_count").value
+        server_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_server_node_count").value
+    except Exception:
+        # Loaded PLC does not have the Modbus plugin => nothing to do
+        #   (i.e. do _not_ configure and make available the Modbus web interface)
+        return
+
+    if client_count < 0: client_count = 0
+    if server_count < 0: server_count = 0
+    
+    if (client_count == 0) and (server_count == 0):
+        # The Modbus plugin in the loaded PLC does not have any client and servers configured
+        #  => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
+        return
+    
+    # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
+    # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
+    GetClientParamFuncs = {}
+    SetClientParamFuncs = {}
+    GetServerParamFuncs = {}
+    SetServerParamFuncs = {}
+
+    # XXX TODO : stop reading from PLC .so file. This code is template code
+    #            that can use modbus extension build data
+    for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
+        ParamFuncName                      = "__modbus_get_ClientNode_" + name        
+        GetClientParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+        GetClientParamFuncs[name].restype  = c_dtype
+        GetClientParamFuncs[name].argtypes = [ctypes.c_int]
+        
+    for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
+        ParamFuncName                      = "__modbus_set_ClientNode_" + name
+        SetClientParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+        SetClientParamFuncs[name].restype  = None
+        SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+
+    # XXX TODO : stop reading from PLC .so file. This code is template code
+    #            that can use modbus extension build data
+    for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
+        ParamFuncName                      = "__modbus_get_ServerNode_" + name        
+        GetServerParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+        GetServerParamFuncs[name].restype  = c_dtype
+        GetServerParamFuncs[name].argtypes = [ctypes.c_int]
+        
+    for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
+        ParamFuncName                      = "__modbus_set_ServerNode_" + name
+        SetServerParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+        SetServerParamFuncs[name].restype  = None
+        SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+
+    for node_id in range(client_count):
+        _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
+
+    for node_id in range(server_count):
+        _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
+
+
+
+
+
+def _runtime_modbus_websettings_%(location_str)s_cleanup():
+    """
+    Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
+    """
+
+    #PLCObject.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
+    
+    # Delete the Modbus specific web interface extensions
+    # (Safe to ask to delete, even if it has not been added!)
+    global _WebNodeList
+    for index, WebNode_entry in enumerate(_WebNodeList):
+        config_hash = WebNode_entry["config_hash"]
+        NS.removeExtensionSetting(config_hash)
+        
+    # Dele all entries...
+    _WebNodeList = []
+
--- a/py_ext/PythonFileCTNMixin.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/py_ext/PythonFileCTNMixin.py	Fri Dec 25 17:12:02 2020 +0000
@@ -119,7 +119,7 @@
                 "opts": repr(variable.getopts()),
                 "configname": configname.upper(),
                 "uppername": variable.getname().upper(),
-                "IECtype": variable.gettype(),
+                "IECtype": self.GetCTRoot().GetBaseType(variable.gettype()),
                 "initial": repr(variable.getinitial()),
                 "pyextname": pyextname
             },
@@ -242,8 +242,8 @@
         varpubonchangefmt = """\
     if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
         IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
-        if(__%(name)s_rbuffer != tmp){
-            __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+        if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){
+            __%(name)s_rbuffer = tmp;
             PYTHON_POLL_body__(__%(name)s_notifier);
         }
         AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
--- a/runtime/BACnet_config.py	Fri Dec 25 17:11:38 2020 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,490 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz runtime.
-#
-# Copyright (C) 2020: Mario de Sousa
-#
-# See COPYING.Runtime file for copyrights details.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-
-
-import json
-import os
-import ctypes
-
-from formless import annotate, webform
-
-
-
-# reference to the PLCObject in runtime/PLCObject.py
-# PLCObject is a singleton, created in runtime/__init__.py
-_plcobj = None
-
-# reference to the Nevow web server (a.k.a as NS in Beremiz_service.py)
-# (Note that NS will reference the NevowServer.py _module_, and not an object/class)
-_NS = None
-
-
-# WorkingDir: the directory on which Beremiz_service.py is running, and where 
-#             all the files downloaded to the PLC get stored
-_WorkingDir = None
-
-
-# Will contain references to the C functions 
-# (implemented in beremiz/bacnet/runtime/server.c)
-# used to get/set the BACnet specific configuration paramters
-GetParamFuncs = {}
-SetParamFuncs = {}
-
-
-# Upon PLC load, this Dictionary is initialised with the BACnet configuration
-# hardcoded in the C file
-# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
-_DefaultConfiguration = None
-
-
-# Dictionary that contains the BACnet configuration currently being shown
-# on the web interface
-# This configuration will almost always be identical to the current
-# configuration in the PLC (i.e., the current state stored in the 
-# C variables in the .so file).
-# The configuration viewed on the web will only be different to the current 
-# configuration when the user edits the configuration, and when
-# the user asks to save the edited configuration but it contains an error.
-_WebviewConfiguration = None
-
-
-# Dictionary that stores the BACnet configuration currently stored in a file
-# Currently only used to decide whether or not to show the "Delete" button on the
-# web interface (only shown if _SavedConfiguration is not None)
-_SavedConfiguration = None
-
-
-# File to which the new BACnet configuration gets stored on the PLC
-# Note that the stored configuration is likely different to the
-# configuration hardcoded in C generated code (.so file), so
-# this file should be persistent across PLC reboots so we can
-# re-configure the PLC (change values of variables in .so file)
-# before it gets a chance to start running
-#
-#_BACnetConfFilename = None
-_BACnetConfFilename = "/tmp/BeremizBACnetConfig.json"
-
-
-
-
-class BN_StrippedString(annotate.String):
-    def __init__(self, *args, **kwargs):
-        annotate.String.__init__(self, strip = True, *args, **kwargs)
-
-
-
-BACnet_parameters = [
-    #    param. name             label                                            ctype type      annotate type
-    # (C code var name)         (used on web interface)                          (C data type)    (web data type)
-    #                                                                                             (annotate.String,
-    #                                                                                              annotate.Integer, ...)
-    ("network_interface"      , _("Network Interface")                         , ctypes.c_char_p, BN_StrippedString),
-    ("port_number"            , _("UDP Port Number")                           , ctypes.c_char_p, BN_StrippedString),
-    ("comm_control_passwd"    , _("BACnet Communication Control Password")     , ctypes.c_char_p, annotate.String),
-    ("device_id"              , _("BACnet Device ID")                          , ctypes.c_int,    annotate.Integer),
-    ("device_name"            , _("BACnet Device Name")                        , ctypes.c_char_p, annotate.String),
-    ("device_location"        , _("BACnet Device Location")                    , ctypes.c_char_p, annotate.String),
-    ("device_description"     , _("BACnet Device Description")                 , ctypes.c_char_p, annotate.String),
-    ("device_appsoftware_ver" , _("BACnet Device Application Software Version"), ctypes.c_char_p, annotate.String)
-    ]
-
-
-
-
-
-
-def _CheckPortnumber(port_number):
-    """ check validity of the port number """
-    try:
-        portnum = int(port_number)
-        if (portnum < 0) or (portnum > 65535):
-           raise Exception
-    except Exception:    
-        return False
-        
-    return True    
-    
-
-
-def _CheckDeviceID(device_id):
-    """ 
-    # check validity of the Device ID 
-    # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
-    #       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
-    """
-    try:
-        devid = int(device_id)
-        if (devid < 0) or (devid > 4194302):
-            raise Exception
-    except Exception:    
-        return False
-        
-    return True    
-
-
-
-
-
-def _CheckConfiguration(BACnetConfig):
-    res = True    
-    res = res and _CheckPortnumber(BACnetConfig["port_number"])
-    res = res and _CheckDeviceID  (BACnetConfig["device_id"])
-    return res
-
-
-
-def _CheckWebConfiguration(BACnetConfig):
-    res = True
-    
-    # check the port number
-    if not _CheckPortnumber(BACnetConfig["port_number"]):
-        raise annotate.ValidateError(
-            {"port_number": "Invalid port number: " + str(BACnetConfig["port_number"])},
-            _("BACnet configuration error:"))
-        res = False
-    
-    if not _CheckDeviceID(BACnetConfig["device_id"]):
-        raise annotate.ValidateError(
-            {"device_id": "Invalid device ID: " + str(BACnetConfig["device_id"])},
-            _("BACnet configuration error:"))
-        res = False
-        
-    return res
-
-
-
-
-
-
-def _SetSavedConfiguration(BACnetConfig):
-    """ Stores in a file a dictionary containing the BACnet parameter configuration """
-    with open(os.path.realpath(_BACnetConfFilename), 'w') as f:
-        json.dump(BACnetConfig, f, sort_keys=True, indent=4)
-    global _SavedConfiguration
-    _SavedConfiguration = BACnetConfig
-
-
-def _DelSavedConfiguration():
-    """ Deletes the file cotaining the persistent BACnet configuration """
-    if os.path.exists(_BACnetConfFilename):
-        os.remove(_BACnetConfFilename)
-
-
-def _GetSavedConfiguration():
-    """
-    # Returns a dictionary containing the BACnet parameter configuration
-    # that was last saved to file. If no file exists, then return None
-    """
-    try:
-        #if os.path.isfile(_BACnetConfFilename):
-        saved_config = json.load(open(_BACnetConfFilename))
-    except Exception:    
-        return None
-
-    if _CheckConfiguration(saved_config):
-        return saved_config
-    else:
-        return None
-
-
-def _GetPLCConfiguration():
-    """
-    # Returns a dictionary containing the current BACnet parameter configuration
-    # stored in the C variables in the loaded PLC (.so file)
-    """
-    current_config = {}
-    for par_name, x1, x2, x3 in BACnet_parameters:
-        value = GetParamFuncs[par_name]()
-        if value is not None:
-            current_config[par_name] = value
-    
-    return current_config
-
-
-def _SetPLCConfiguration(BACnetConfig):
-    """
-    # Stores the BACnet parameter configuration into the
-    # the C variables in the loaded PLC (.so file)
-    """
-    for par_name in BACnetConfig:
-        value = BACnetConfig[par_name]
-        #_plcobj.LogMessage("BACnet web server extension::_SetPLCConfiguration()  Setting "
-        #                       + par_name + " to " + str(value) )
-        if value is not None:
-            SetParamFuncs[par_name](value)
-    # update the configuration shown on the web interface
-    global _WebviewConfiguration 
-    _WebviewConfiguration = _GetPLCConfiguration()
-
-
-
-def _GetWebviewConfigurationValue(ctx, argument):
-    """
-    # Callback function, called by the web interface (NevowServer.py)
-    # to fill in the default value of each parameter
-    """
-    try:
-        return _WebviewConfiguration[argument.name]
-    except Exception:
-        return ""
-
-
-# The configuration of the web form used to see/edit the BACnet parameters
-webFormInterface = [(name, web_dtype (label=web_label, default=_GetWebviewConfigurationValue)) 
-                    for name, web_label, c_dtype, web_dtype in BACnet_parameters]
-
-
-
-def _updateWebInterface():
-    """
-    # Add/Remove buttons to/from the web interface depending on the current state
-    #
-    #  - If there is a saved state => add a delete saved state button
-    """
-
-    # Add a "Delete Saved Configuration" button if there is a saved configuration!
-    if _SavedConfiguration is None:
-        _NS.ConfigurableSettings.delSettings("BACnetConfigDelSaved")
-    else:
-        _NS.ConfigurableSettings.addSettings(
-            "BACnetConfigDelSaved",                   # name
-            _("BACnet Configuration"),                # description
-            [],                                       # fields  (empty, no parameters required!)
-            _("Delete Configuration Stored in Persistent Storage"), # button label
-            OnButtonDel,                              # callback    
-            "BACnetConfigParm")                       # Add after entry xxxx
-
-
-def OnButtonSave(**kwargs):
-    """
-    # Function called when user clicks 'Save' button in web interface
-    # The function will configure the BACnet plugin in the PLC with the values
-    # specified in the web interface. However, values must be validated first!
-    """
-
-    #_plcobj.LogMessage("BACnet web server extension::OnButtonSave()  Called")
-    
-    newConfig = {}
-    for par_name, x1, x2, x3 in BACnet_parameters:
-        value = kwargs.get(par_name, None)
-        if value is not None:
-            newConfig[par_name] = value
-
-    global _WebviewConfiguration
-    _WebviewConfiguration = newConfig
-    
-    # First check if configuration is OK.
-    if not _CheckWebConfiguration(newConfig):
-        return
-
-    # store to file the new configuration so that 
-    # we can recoup the configuration the next time the PLC
-    # has a cold start (i.e. when Beremiz_service.py is retarted)
-    _SetSavedConfiguration(newConfig)
-
-    # Configure PLC with the current BACnet parameters
-    _SetPLCConfiguration(newConfig)
-
-    # File has just been created => Delete button must be shown on web interface!
-    _updateWebInterface()
-
-
-
-
-def OnButtonDel(**kwargs):
-    """
-    # Function called when user clicks 'Delete' button in web interface
-    # The function will delete the file containing the persistent
-    # BACnet configution
-    """
-
-    # Delete the file
-    _DelSavedConfiguration()
-    # Set the current configuration to the default (hardcoded in C)
-    _SetPLCConfiguration(_DefaultConfiguration)
-    # Reset global variable
-    global _SavedConfiguration
-    _SavedConfiguration = None
-    # File has just been deleted => Delete button on web interface no longer needed!
-    _updateWebInterface()
-
-
-
-def OnButtonShowCur(**kwargs):
-    """
-    # Function called when user clicks 'Show Current PLC Configuration' button in web interface
-    # The function will load the current PLC configuration into the web form
-    """
-    
-    global _WebviewConfiguration
-    _WebviewConfiguration = _GetPLCConfiguration()
-    # File has just been deleted => Delete button on web interface no longer needed!
-    _updateWebInterface()
-
-
-
-
-def OnLoadPLC():
-    """
-    # Callback function, called (by PLCObject.py) when a new PLC program
-    # (i.e. XXX.so file) is transfered to the PLC runtime
-    # and oaded into memory
-    """
-
-    #_plcobj.LogMessage("BACnet web server extension::OnLoadPLC() Called...")
-
-    if _plcobj.PLClibraryHandle is None:
-        # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
-        # Hmm... This shold never occur!! 
-        return  
-    
-    # Get the location (in the Config. Node Tree of Beremiz IDE) the BACnet plugin
-    # occupies in the currently loaded PLC project (i.e., the .so file)
-    # If the "__bacnet_plugin_location" C variable is not present in the .so file,
-    # we conclude that the currently loaded PLC does not have the BACnet plugin
-    # included (situation (2b) described above init())
-    try:
-        location = ctypes.c_char_p.in_dll(_plcobj.PLClibraryHandle, "__bacnet_plugin_location")
-    except Exception:
-        # Loaded PLC does not have the BACnet plugin => nothing to do
-        #   (i.e. do _not_ configure and make available the BACnet web interface)
-        return
-
-    # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
-    for name, web_label, c_dtype, web_dtype in BACnet_parameters:
-        GetParamFuncName = "__bacnet_" + location.value + "_get_ConfigParam_" + name
-        SetParamFuncName = "__bacnet_" + location.value + "_set_ConfigParam_" + name
-        
-        GetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, GetParamFuncName)
-        GetParamFuncs[name].restype  = c_dtype
-        GetParamFuncs[name].argtypes = None
-        
-        SetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, SetParamFuncName)
-        SetParamFuncs[name].restype  = None
-        SetParamFuncs[name].argtypes = [c_dtype]
-
-    # Default configuration is the configuration done in Beremiz IDE
-    # whose parameters get hardcoded into C, and compiled into the .so file
-    # We read the default configuration from the .so file before the values
-    # get changed by the user using the web server, or by the call (further on)
-    # to _SetPLCConfiguration(SavedConfiguration)
-    global _DefaultConfiguration 
-    _DefaultConfiguration = _GetPLCConfiguration()
-    
-    # Show the current PLC configuration on the web interface        
-    global _WebviewConfiguration
-    _WebviewConfiguration = _GetPLCConfiguration()
- 
-    # Read from file the last used configuration, which is likely
-    # different to the hardcoded configuration.
-    # We Reset the current configuration (i.e., the config stored in the 
-    # variables of .so file) to this saved configuration
-    # so the PLC will start off with this saved configuration instead
-    # of the hardcoded (in Beremiz C generated code) configuration values.
-    #
-    # Note that _SetPLCConfiguration() will also update 
-    # _WebviewConfiguration , if necessary.
-    global _SavedConfiguration
-    _SavedConfiguration  = _GetSavedConfiguration()
-    if _SavedConfiguration is not None:
-        if _CheckConfiguration(_SavedConfiguration):
-            _SetPLCConfiguration(_SavedConfiguration)
-            
-    # Configure the web interface to include the BACnet config parameters
-    _NS.ConfigurableSettings.addSettings(
-        "BACnetConfigParm",                # name
-        _("BACnet Configuration"),         # description
-        webFormInterface,                  # fields
-        _("Save Configuration to Persistent Storage"),  # button label
-        OnButtonSave)                      # callback    
-    
-    # Add a "View Current Configuration" button 
-    _NS.ConfigurableSettings.addSettings(
-        "BACnetConfigViewCur",                    # name
-        _("BACnet Configuration"),                # description
-        [],                                       # fields  (empty, no parameters required!)
-        _("Show Current PLC Configuration"),      # button label
-        OnButtonShowCur)                          # callback    
-
-    # Add the Delete button to the web interface, if required
-    _updateWebInterface()
-
-
-
-
-
-def OnUnLoadPLC():
-    """
-    # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
-    """
-
-    #_plcobj.LogMessage("BACnet web server extension::OnUnLoadPLC() Called...")
-    
-    # Delete the BACnet specific web interface extensions
-    # (Safe to ask to delete, even if it has not been added!)
-    _NS.ConfigurableSettings.delSettings("BACnetConfigParm")
-    _NS.ConfigurableSettings.delSettings("BACnetConfigViewCur")  
-    _NS.ConfigurableSettings.delSettings("BACnetConfigDelSaved")  
-    GetParamFuncs = {}
-    SetParamFuncs = {}
-    _WebviewConfiguration = None
-    _SavedConfiguration   = None
-
-
-
-
-# The Beremiz_service.py service, along with the integrated web server it launches
-# (i.e. Nevow web server, in runtime/NevowServer.py), will go through several states
-# once started:
-#  (1) Web server is started, but no PLC is loaded
-#  (2) PLC is loaded (i.e. the PLC compiled code is loaded)
-#         (a) The loaded PLC includes the BACnet plugin
-#         (b) The loaded PLC does not have the BACnet plugin
-#
-# During (1) and (2a):
-#     we configure the web server interface to not have the BACnet web configuration extension
-# During (2b) 
-#     we configure the web server interface to include the BACnet web configuration extension
-#
-# plcobj    : reference to the PLCObject defined in PLCObject.py
-# NS        : reference to the web server (i.e. the NevowServer.py module)
-# WorkingDir: the directory on which Beremiz_service.py is running, and where 
-#             all the files downloaded to the PLC get stored, including
-#             the .so file with the compiled C generated code
-def init(plcobj, NS, WorkingDir):
-    #plcobj.LogMessage("BACnet web server extension::init(plcobj, NS, " + WorkingDir + ") Called")
-    global _WorkingDir
-    _WorkingDir = WorkingDir
-    global _plcobj
-    _plcobj = plcobj
-    global _NS
-    _NS = NS
-    global _BACnetConfFilename
-    if _BACnetConfFilename is None:
-        _BACnetConfFilename = os.path.join(WorkingDir, "BACnetConfig.json")
-    
-    _plcobj.RegisterCallbackLoad  ("BACnet_Settins_Extension", OnLoadPLC)
-    _plcobj.RegisterCallbackUnLoad("BACnet_Settins_Extension", OnUnLoadPLC)
-    OnUnLoadPLC() # init is called before the PLC gets loaded...  so we make sure we have the correct state
--- a/runtime/Modbus_config.py	Fri Dec 25 17:11:38 2020 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,705 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz runtime.
-#
-# Copyright (C) 2020: Mario de Sousa
-#
-# See COPYING.Runtime file for copyrights details.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
-
-
-
-
-##############################################################################################
-# This file implements an extension to the web server embedded in the Beremiz_service.py     #
-# runtime manager (webserver is in runtime/NevowServer.py).                                  #
-#                                                                                            #
-# The extension implemented in this file allows for runtime configuration                    #
-# of Modbus plugin parameters                                                                #
-##############################################################################################
-
-
-
-import json
-import os
-import ctypes
-import string
-import hashlib
-
-from formless import annotate, webform
-
-
-
-# reference to the PLCObject in runtime/PLCObject.py
-# PLCObject is a singleton, created in runtime/__init__.py
-_plcobj = None
-
-# reference to the Nevow web server (a.k.a as NS in Beremiz_service.py)
-# (Note that NS will reference the NevowServer.py _module_, and not an object/class)
-_NS = None
-
-
-# WorkingDir: the directory on which Beremiz_service.py is running, and where 
-#             all the files downloaded to the PLC get stored
-_WorkingDir = None
-
-# Directory in which to store the persistent configurations
-# Should be a directory that does not get wiped on reboot!
-_ModbusConfFiledir = "/tmp"
-
-# List of all Web Extension Setting nodes we are handling.
-# One WebNode each for:
-#   - Modbus TCP client 
-#   - Modbus TCP server
-#   - Modbus RTU client
-#   - Modbus RTU slave
-# configured in the loaded PLC (i.e. the .so file loaded into memory)
-# Each entry will be a dictionary. See _AddWebNode() for the details
-# of the data structure in each entry.
-_WebNodeList = []
-
-
-
-
-class MB_StrippedString(annotate.String):
-    def __init__(self, *args, **kwargs):
-        annotate.String.__init__(self, strip = True, *args, **kwargs)
-
-
-class MB_StopBits(annotate.Choice):
-    _choices = [0, 1, 2]
-
-    def coerce(self, val, configurable):
-        return int(val)
-    def __init__(self, *args, **kwargs):
-        annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
-
-
-class MB_Baud(annotate.Choice):
-    _choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
-
-    def coerce(self, val, configurable):
-        return int(val)
-    def __init__(self, *args, **kwargs):
-        annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
-
-
-class MB_Parity(annotate.Choice):
-    # For more info on what this class really does, have a look at the code in
-    # file twisted/nevow/annotate.py
-    # grab this code from $git clone https://github.com/twisted/nevow/
-    # 
-    # Warning: do _not_ name this variable choice[] without underscore, as that name is
-    # already used for another similar variable by the underlying class annotate.Choice
-    _choices = [  0,      1,      2  ]
-    _label   = ["none", "odd", "even"]
-    
-    def choice_to_label(self, key):
-        #_plcobj.LogMessage("Modbus web server extension::choice_to_label()  " + str(key))
-        return self._label[key]
-    
-    def coerce(self, val, configurable):
-        """Coerce a value with the help of an object, which is the object
-        we are configuring.
-        """
-        # Basically, make sure the value the user introduced is valid, and transform
-        # into something that is valid if necessary or mark it as an error 
-        # (by raising an exception ??).
-        #
-        # We are simply using this functions to transform the input value (a string)
-        # into an integer. Note that although the available options are all
-        # integers (0, 1 or 2), even though what is shown on the user interface
-        # are actually strings, i.e. the labels), these parameters are for some 
-        # reason being parsed as strings, so we need to map them back to an
-        # integer.
-        #
-        #_plcobj.LogMessage("Modbus web server extension::coerce  " + val )
-        return int(val)
-
-    def __init__(self, *args, **kwargs):
-        annotate.Choice.__init__(self, 
-                                 choices   = self._choices,
-                                 stringify = self.choice_to_label,
-                                 *args, **kwargs)
-
-
-
-# Parameters we will need to get from the C code, but that will not be shown
-# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
-#
-# The annotate type entry is basically useless and is completely ignored.
-# We kee that entry so that this list can later be correctly merged with the
-# following lists...
-General_parameters = [
-    #    param. name       label                        ctype type         annotate type
-    # (C code var name)   (used on web interface)      (C data type)       (web data type)
-    #                                                                      (annotate.String,
-    #                                                                       annotate.Integer, ...)
-    ("config_name"      , _("")                      , ctypes.c_char_p,    annotate.String),
-    ("addr_type"        , _("")                      , ctypes.c_char_p,    annotate.String)
-    ]                                                                      
-                                                                           
-# Parameters we will need to get from the C code, and that _will_ be shown
-# on the web interface.
-TCPclient_parameters = [                                                   
-    #    param. name       label                        ctype type         annotate type
-    # (C code var name)   (used on web interface)      (C data type)       (web data type)
-    #                                                                      (annotate.String,
-    #                                                                       annotate.Integer, ...)
-    ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    MB_StrippedString),
-    ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    MB_StrippedString),
-    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer )
-    ]
-
-RTUclient_parameters = [                                                   
-    #    param. name       label                        ctype type         annotate type
-    # (C code var name)   (used on web interface)      (C data type)       (web data type)
-    #                                                                      (annotate.String,
-    #                                                                       annotate.Integer, ...)
-    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
-    ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
-    ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
-    ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
-    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
-    ]
-
-TCPserver_parameters = [                                                   
-    #    param. name       label                        ctype type         annotate type
-    # (C code var name)   (used on web interface)      (C data type)       (web data type)
-    #                                                                      (annotate.String,
-    #                                                                       annotate.Integer, ...)
-    ("host"             , _("Local IP Address")      , ctypes.c_char_p,    MB_StrippedString),
-    ("port"             , _("Local Port Number")     , ctypes.c_char_p,    MB_StrippedString),
-    ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer )
-    ]
-
-RTUslave_parameters = [                                                   
-    #    param. name       label                        ctype type         annotate type
-    # (C code var name)   (used on web interface)      (C data type)       (web data type)
-    #                                                                      (annotate.String,
-    #                                                                       annotate.Integer, ...)
-    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
-    ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
-    ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
-    ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
-    ("slave_id"         , _("Slave ID")              , ctypes.c_ulonglong, annotate.Integer)
-    ]
-
-
-
-
-# Dictionary containing List of Web viewable parameters
-# Note: the dictionary key must be the same as the string returned by the 
-# __modbus_get_ClientNode_addr_type()
-# __modbus_get_ServerNode_addr_type()
-# functions implemented in C (see modbus/mb_runtime.c)
-_client_WebParamListDict = {}
-_client_WebParamListDict["tcp"  ] = TCPclient_parameters
-_client_WebParamListDict["rtu"  ] = RTUclient_parameters
-_client_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
-
-_server_WebParamListDict = {}
-_server_WebParamListDict["tcp"  ] = TCPserver_parameters
-_server_WebParamListDict["rtu"  ] = RTUslave_parameters
-_server_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
-
-WebParamListDictDict = {}
-WebParamListDictDict['client'] = _client_WebParamListDict
-WebParamListDictDict['server'] = _server_WebParamListDict
-
-
-
-
-
-
-def _SetSavedConfiguration(WebNode_id, newConfig):
-    """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
-    
-    # Add the addr_type and node_type to the data that will be saved to file
-    # This allows us to confirm the saved data contains the correct addr_type
-    # when loading from file
-    save_info = {}
-    save_info["addr_type"] = _WebNodeList[WebNode_id]["addr_type"]
-    save_info["node_type"] = _WebNodeList[WebNode_id]["node_type"]
-    save_info["config"   ] = newConfig
-    
-    filename = _WebNodeList[WebNode_id]["filename"]
-
-    with open(os.path.realpath(filename), 'w') as f:
-        json.dump(save_info, f, sort_keys=True, indent=4)
-        
-    _WebNodeList[WebNode_id]["SavedConfiguration"] = newConfig
-
-
-
-
-def _DelSavedConfiguration(WebNode_id):
-    """ Deletes the file cotaining the persistent Modbus configuration """
-    filename = _WebNodeList[WebNode_id]["filename"]
-    
-    if os.path.exists(filename):
-        os.remove(filename)
-
-
-
-
-def _GetSavedConfiguration(WebNode_id):
-    """
-    Returns a dictionary containing the Modbus parameter configuration
-    that was last saved to file. If no file exists, or file contains 
-    wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
-    addr_type of the WebNode_id), then return None
-    """
-    filename = _WebNodeList[WebNode_id]["filename"]
-    try:
-        #if os.path.isfile(filename):
-        save_info = json.load(open(filename))
-    except Exception:    
-        return None
-
-    if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
-        return None
-    if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
-        return None
-    if "config" not in save_info:
-        return None
-    
-    saved_config = save_info["config"]
-    
-    #if _CheckConfiguration(saved_config):
-    #    return saved_config
-    #else:
-    #    return None
-
-    return saved_config
-
-
-
-def _GetPLCConfiguration(WebNode_id):
-    """
-    Returns a dictionary containing the current Modbus parameter configuration
-    stored in the C variables in the loaded PLC (.so file)
-    """
-    current_config = {}
-    C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
-    WebParamList   = _WebNodeList[WebNode_id]["WebParamList"]
-    GetParamFuncs  = _WebNodeList[WebNode_id]["GetParamFuncs"]
-
-    for par_name, x1, x2, x3 in WebParamList:
-        value = GetParamFuncs[par_name](C_node_id)
-        if value is not None:
-            current_config[par_name] = value
-    
-    return current_config
-
-
-
-def _SetPLCConfiguration(WebNode_id, newconfig):
-    """
-    Stores the Modbus parameter configuration into the
-    the C variables in the loaded PLC (.so file)
-    """
-    C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
-    SetParamFuncs  = _WebNodeList[WebNode_id]["SetParamFuncs"]
-
-    for par_name in newconfig:
-        value = newconfig[par_name]
-        if value is not None:
-            SetParamFuncs[par_name](C_node_id, value)
-            
-
-
-
-def _GetWebviewConfigurationValue(ctx, WebNode_id, argument):
-    """
-    Callback function, called by the web interface (NevowServer.py)
-    to fill in the default value of each parameter of the web form
-    
-    Note that the real callback function is a dynamically created function that
-    will simply call this function to do the work. It will also pass the WebNode_id 
-    as a parameter.
-    """    
-    try:
-        return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
-    except Exception:
-        return ""
-
-
-
-
-def _updateWebInterface(WebNode_id):
-    """
-    Add/Remove buttons to/from the web interface depending on the current state
-       - If there is a saved state => add a delete saved state button
-    """
-
-    config_hash = _WebNodeList[WebNode_id]["config_hash"]
-    config_name = _WebNodeList[WebNode_id]["config_name"]
-    
-    # Add a "Delete Saved Configuration" button if there is a saved configuration!
-    if _WebNodeList[WebNode_id]["SavedConfiguration"] is None:
-        _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
-    else:
-        def __OnButtonDel(**kwargs):
-            return OnButtonDel(WebNode_id = WebNode_id, **kwargs)
-                
-        _NS.ConfigurableSettings.addSettings(
-            "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
-            _("Modbus Configuration: ") + config_name,  # description (user visible label)
-            [],                                         # fields  (empty, no parameters required!)
-            _("Delete Configuration Stored in Persistent Storage"), # button label
-            __OnButtonDel,                              # callback    
-            "ModbusConfigParm"          + config_hash)  # Add after entry xxxx
-
-
-
-def OnButtonSave(**kwargs):
-    """
-    Function called when user clicks 'Save' button in web interface
-    The function will configure the Modbus plugin in the PLC with the values
-    specified in the web interface. However, values must be validated first!
-    
-    Note that this function does not get called directly. The real callback
-    function is the dynamic __OnButtonSave() function, which will add the 
-    "WebNode_id" argument, and call this function to do the work.
-    """
-
-    #_plcobj.LogMessage("Modbus web server extension::OnButtonSave()  Called")
-    
-    newConfig    = {}
-    WebNode_id   =  kwargs.get("WebNode_id", None)
-    WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
-    
-    for par_name, x1, x2, x3 in WebParamList:
-        value = kwargs.get(par_name, None)
-        if value is not None:
-            newConfig[par_name] = value
-
-    # First check if configuration is OK.
-    # Note that this is not currently required, as we use drop down choice menus
-    # for baud, parity and sop bits, so the values should always be correct!
-    #if not _CheckWebConfiguration(newConfig):
-    #    return
-    
-    # store to file the new configuration so that 
-    # we can recoup the configuration the next time the PLC
-    # has a cold start (i.e. when Beremiz_service.py is retarted)
-    _SetSavedConfiguration(WebNode_id, newConfig)
-
-    # Configure PLC with the current Modbus parameters
-    _SetPLCConfiguration(WebNode_id, newConfig)
-
-    # Update the viewable configuration
-    # The PLC may have coerced the values on calling _SetPLCConfiguration()
-    # so we do not set it directly to newConfig
-    _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
-
-    # File has just been created => Delete button must be shown on web interface!
-    _updateWebInterface(WebNode_id)
-
-
-
-
-def OnButtonDel(**kwargs):
-    """
-    Function called when user clicks 'Delete' button in web interface
-    The function will delete the file containing the persistent
-    Modbus configution
-    """
-
-    WebNode_id = kwargs.get("WebNode_id", None)
-    
-    # Delete the file
-    _DelSavedConfiguration(WebNode_id)
-
-    # Set the current configuration to the default (hardcoded in C)
-    new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
-    _SetPLCConfiguration(WebNode_id, new_config)
-    
-    #Update the webviewconfiguration
-    _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
-    
-    # Reset SavedConfiguration
-    _WebNodeList[WebNode_id]["SavedConfiguration"] = None
-    
-    # File has just been deleted => Delete button on web interface no longer needed!
-    _updateWebInterface(WebNode_id)
-
-
-
-
-def OnButtonShowCur(**kwargs):
-    """
-    Function called when user clicks 'Show Current PLC Configuration' button in web interface
-    The function will load the current PLC configuration into the web form
-
-    Note that this function does not get called directly. The real callback
-    function is the dynamic __OnButtonShowCur() function, which will add the 
-    "WebNode_id" argument, and call this function to do the work.
-    """
-    WebNode_id = kwargs.get("WebNode_id", None)
-    
-    _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
-    
-
-
-
-def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
-    """
-    Load from the compiled code (.so file, aloready loaded into memmory)
-    the configuration parameters of a specific Modbus plugin node.
-    This function works with both client and server nodes, depending on the
-    Get/SetParamFunc dictionaries passed to it (either the client or the server
-    node versions of the Get/Set functions)
-    """
-    WebNode_entry = {}
-
-    # Get the config_name from the C code...
-    config_name = GetParamFuncs["config_name"](C_node_id)
-    # Get the addr_type from the C code...
-    # addr_type will be one of "tcp", "rtu" or "ascii"
-    addr_type   = GetParamFuncs["addr_type"  ](C_node_id)   
-    # For some operations we cannot use the config name (e.g. filename to store config)
-    # because the user may be using characters that are invalid for that purpose ('/' for
-    # example), so we create a hash of the config_name, and use that instead.
-    config_hash = hashlib.md5(config_name).hexdigest()
-    
-    #_plcobj.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
-
-    # Add the new entry to the global list
-    # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
-    #       WebNode_entry will be stored as a reference, so we can later insert parameters at will.
-    global _WebNodeList
-    _WebNodeList.append(WebNode_entry)
-    WebNode_id = len(_WebNodeList) - 1
-
-    # store all WebNode relevant data for future reference
-    #
-    # Note that "WebParamList" will reference one of:
-    #  - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
-    WebNode_entry["C_node_id"    ] = C_node_id
-    WebNode_entry["config_name"  ] = config_name 
-    WebNode_entry["config_hash"  ] = config_hash
-    WebNode_entry["filename"     ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
-    WebNode_entry["GetParamFuncs"] = GetParamFuncs
-    WebNode_entry["SetParamFuncs"] = SetParamFuncs
-    WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type] 
-    WebNode_entry["addr_type"    ] = addr_type  # 'tcp', 'rtu', or 'ascii' (as returned by C function)
-    WebNode_entry["node_type"    ] = node_type  # 'client', 'server'
-        
-    
-    # Dictionary that contains the Modbus configuration currently being shown
-    # on the web interface
-    # This configuration will almost always be identical to the current
-    # configuration in the PLC (i.e., the current state stored in the 
-    # C variables in the .so file).
-    # The configuration viewed on the web will only be different to the current 
-    # configuration when the user edits the configuration, and when
-    # the user asks to save an edited configuration that contains an error.
-    WebNode_entry["WebviewConfiguration"] = None
-
-    # Upon PLC load, this Dictionary is initialised with the Modbus configuration
-    # hardcoded in the C file
-    # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
-    WebNode_entry["DefaultConfiguration"] = _GetPLCConfiguration(WebNode_id)
-    WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
-    
-    # Dictionary that stores the Modbus configuration currently stored in a file
-    # Currently only used to decide whether or not to show the "Delete" button on the
-    # web interface (only shown if "SavedConfiguration" is not None)
-    SavedConfig = _GetSavedConfiguration(WebNode_id)
-    WebNode_entry["SavedConfiguration"] = SavedConfig
-    
-    if SavedConfig is not None:
-        _SetPLCConfiguration(WebNode_id, SavedConfig)
-        WebNode_entry["WebviewConfiguration"] = SavedConfig
-        
-    # Define the format for the web form used to show/change the current parameters
-    # We first declare a dynamic function to work as callback to obtain the default values for each parameter
-    # Note: We transform every parameter into a string
-    #       This is not strictly required for parameters of type annotate.Integer that will correctly
-    #           accept the default value as an Integer python object
-    #       This is obviously also not required for parameters of type annotate.String, that are
-    #           always handled as strings.
-    #       However, the annotate.Choice parameters (and all parameters that derive from it,
-    #           sucn as Parity, Baud, etc.) require the default value as a string
-    #           even though we store it as an integer, which is the data type expected
-    #           by the set_***() C functions in mb_runtime.c
-    def __GetWebviewConfigurationValue(ctx, argument):
-        return str(_GetWebviewConfigurationValue(ctx, WebNode_id, argument))
-    
-    webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
-                    for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
-
-    # Configure the web interface to include the Modbus config parameters
-    def __OnButtonSave(**kwargs):
-        OnButtonSave(WebNode_id=WebNode_id, **kwargs)
-
-    _NS.ConfigurableSettings.addSettings(
-        "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
-        _("Modbus Configuration: ") + config_name,     # description (user visible label)
-        webFormInterface,                              # fields
-        _("Save Configuration to Persistent Storage"), # button label
-        __OnButtonSave)                                # callback   
-    
-    # Add a "View Current Configuration" button 
-    def __OnButtonShowCur(**kwargs):
-        OnButtonShowCur(WebNode_id=WebNode_id, **kwargs)
-
-    _NS.ConfigurableSettings.addSettings(
-        "ModbusConfigViewCur"       + config_hash, # name (internal, may not contain spaces, ...)
-        _("Modbus Configuration: ") + config_name,     # description (user visible label)
-        [],                                        # fields  (empty, no parameters required!)
-        _("Show Current PLC Configuration"),       # button label
-        __OnButtonShowCur)                         # callback    
-
-    # Add the Delete button to the web interface, if required
-    _updateWebInterface(WebNode_id)
-
-
-
-
-
-def OnLoadPLC():
-    """
-    Callback function, called (by PLCObject.py) when a new PLC program
-    (i.e. XXX.so file) is transfered to the PLC runtime
-    and loaded into memory
-    """
-
-    #_plcobj.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
-
-    if _plcobj.PLClibraryHandle is None:
-        # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
-        # Hmm... This shold never occur!! 
-        return  
-    
-    # Get the number of Modbus Client and Servers (Modbus plugin)
-    # configured in the currently loaded PLC project (i.e., the .so file)
-    # If the "__modbus_plugin_client_node_count" 
-    # or the "__modbus_plugin_server_node_count" C variables 
-    # are not present in the .so file we conclude that the currently loaded 
-    # PLC does not have the Modbus plugin included (situation (2b) described above init())
-    try:
-        client_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_client_node_count").value
-        server_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_server_node_count").value
-    except Exception:
-        # Loaded PLC does not have the Modbus plugin => nothing to do
-        #   (i.e. do _not_ configure and make available the Modbus web interface)
-        return
-
-    if client_count < 0: client_count = 0
-    if server_count < 0: server_count = 0
-    
-    if (client_count == 0) and (server_count == 0):
-        # The Modbus plugin in the loaded PLC does not have any client and servers configured
-        #  => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
-        return
-    
-    # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
-    # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
-    GetClientParamFuncs = {}
-    SetClientParamFuncs = {}
-    GetServerParamFuncs = {}
-    SetServerParamFuncs = {}
-
-    for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
-        ParamFuncName                      = "__modbus_get_ClientNode_" + name        
-        GetClientParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
-        GetClientParamFuncs[name].restype  = c_dtype
-        GetClientParamFuncs[name].argtypes = [ctypes.c_int]
-        
-    for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
-        ParamFuncName                      = "__modbus_set_ClientNode_" + name
-        SetClientParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
-        SetClientParamFuncs[name].restype  = None
-        SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
-
-    for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
-        ParamFuncName                      = "__modbus_get_ServerNode_" + name        
-        GetServerParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
-        GetServerParamFuncs[name].restype  = c_dtype
-        GetServerParamFuncs[name].argtypes = [ctypes.c_int]
-        
-    for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
-        ParamFuncName                      = "__modbus_set_ServerNode_" + name
-        SetServerParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
-        SetServerParamFuncs[name].restype  = None
-        SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
-
-    for node_id in range(client_count):
-        _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
-
-    for node_id in range(server_count):
-        _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
-
-
-
-
-
-def OnUnLoadPLC():
-    """
-    Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
-    """
-
-    #_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
-    
-    # Delete the Modbus specific web interface extensions
-    # (Safe to ask to delete, even if it has not been added!)
-    global _WebNodeList    
-    for WebNode_entry in _WebNodeList:
-        config_hash = WebNode_entry["config_hash"]
-        _NS.ConfigurableSettings.delSettings("ModbusConfigParm"     + config_hash)
-        _NS.ConfigurableSettings.delSettings("ModbusConfigViewCur"  + config_hash)  
-        _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)  
-        
-    # Dele all entries...
-    _WebNodeList = []
-
-
-
-# The Beremiz_service.py service, along with the integrated web server it launches
-# (i.e. Nevow web server, in runtime/NevowServer.py), will go through several states
-# once started:
-#  (1) Web server is started, but no PLC is loaded
-#  (2) PLC is loaded (i.e. the PLC compiled code is loaded)
-#         (a) The loaded PLC includes the Modbus plugin
-#         (b) The loaded PLC does not have the Modbus plugin
-#
-# During (1) and (2a):
-#     we configure the web server interface to not have the Modbus web configuration extension
-# During (2b) 
-#     we configure the web server interface to include the Modbus web configuration extension
-#
-# PS: reference to the pyroserver  (i.e., the server object of Beremiz_service.py)
-#     (NOTE: PS.plcobj is a reference to PLCObject.py)
-# NS: reference to the web server (i.e. the NevowServer.py module)
-# WorkingDir: the directory on which Beremiz_service.py is running, and where 
-#             all the files downloaded to the PLC get stored, including
-#             the .so file with the compiled C generated code
-def init(plcobj, NS, WorkingDir):
-    #PS.plcobj.LogMessage("Modbus web server extension::init(PS, NS, " + WorkingDir + ") Called")
-    global _WorkingDir
-    _WorkingDir = WorkingDir
-    global _plcobj
-    _plcobj = plcobj
-    global _NS
-    _NS = NS
-
-    _plcobj.RegisterCallbackLoad  ("Modbus_Settins_Extension", OnLoadPLC)
-    _plcobj.RegisterCallbackUnLoad("Modbus_Settins_Extension", OnUnLoadPLC)
-    OnUnLoadPLC() # init is called before the PLC gets loaded...  so we make sure we have the correct state
--- a/runtime/NevowServer.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/runtime/NevowServer.py	Fri Dec 25 17:12:02 2020 +0000
@@ -26,6 +26,7 @@
 from __future__ import absolute_import
 from __future__ import print_function
 import os
+import collections
 import platform as platform_module
 from zope.interface import implements
 from nevow import appserver, inevow, tags, loaders, athena, url, rend
@@ -165,8 +166,7 @@
         setattr(self, 'bind_' + name, _bind)
         self.bindingsNames.append(name)
 
-    def addSettings(self, name, desc, fields, btnlabel, callback, 
-                    addAfterName = None):
+    def addSettings(self, name, desc, fields, btnlabel, callback):
         def _bind(ctx):
             return annotate.MethodBinding(
                 'action_' + name,
@@ -180,32 +180,20 @@
 
         setattr(self, 'action_' + name, callback)
 
-        if addAfterName not in self.bindingsNames:
-            # Just append new setting if not yet present
-            if name not in self.bindingsNames:
-                self.bindingsNames.append(name)
-        else:
-            # We need to insert new setting 
-            # imediately _after_ addAfterName
+        self.bindingsNames.append(name)
             
-            # First remove new setting if already present
-            # to make sure it goes into correct place
-            if name in self.bindingsNames:
-                self.bindingsNames.remove(name)
-            # Now add new setting in correct place
-            self.bindingsNames.insert(
-                self.bindingsNames.index(addAfterName)+1,
-                name)
-            
-
-
-    def delSettings(self, name):
-        if name in self.bindingsNames:
-            self.bindingsNames.remove(name)
-
 
 ConfigurableSettings = ConfigurableBindings()
 
+def newExtensionSetting(display, token):
+    global extensions_settings_od
+    settings = ConfigurableBindings()
+    extensions_settings_od[token] = (settings, display)
+    return settings
+
+def removeExtensionSetting(token):
+    global extensions_settings_od
+    extensions_settings_od.pop(token)
 
 class ISettings(annotate.TypedInterface):
     platform = annotate.String(label=_("Platform"),
@@ -233,6 +221,7 @@
 customSettingsURLs = {
 }
 
+extensions_settings_od = collections.OrderedDict()
 
 class SettingsPage(rend.Page):
     # We deserve a slash
@@ -243,6 +232,27 @@
     child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css')
 
     implements(ISettings)
+   
+    def __getattr__(self, name):
+        global extensions_settings_od
+        if name.startswith('configurable_'):
+            token = name[13:]
+            def configurable_something(ctx):
+                settings, _display = extensions_settings_od[token]
+                return settings
+            return configurable_something
+        raise AttributeError
+    
+    def extensions_settings(self, context, data):
+        """ Project extensions settings
+        Extensions added to Configuration Tree in IDE have their setting rendered here
+        """
+        global extensions_settings_od
+        res = []
+        for token in extensions_settings_od:
+            _settings, display = extensions_settings_od[token]
+            res += [tags.h2[display], webform.renderForms(token)] 
+        return res
 
     docFactory = loaders.stan([tags.html[
         tags.head[
@@ -260,12 +270,16 @@
             webform.renderForms('staticSettings'),
             tags.h1["Extensions settings:"],
             webform.renderForms('dynamicSettings'),
+            extensions_settings
         ]]])
 
     def configurable_staticSettings(self, ctx):
         return configurable.TypedInterfaceConfigurable(self)
 
     def configurable_dynamicSettings(self, ctx):
+        """ Runtime Extensions settings
+        Extensions loaded through Beremiz_service -e or optional runtime features render setting forms here
+        """
         return ConfigurableSettings
 
     def sendLogMessage(self, level, message, **kwargs):
--- a/runtime/PLCObject.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/runtime/PLCObject.py	Fri Dec 25 17:12:02 2020 +0000
@@ -99,9 +99,6 @@
         self.TraceLock = Lock()
         self.Traces = []
         self.DebugToken = 0
-        # Callbacks used by web settings extensions (e.g.: BACnet_config.py, Modbus_config.py)
-        self.LoadCallbacks   = {} # list of functions to call when PLC is   loaded
-        self.UnLoadCallbacks = {} # list of functions to call when PLC is unloaded
 
         self._init_blobs()
 
@@ -170,22 +167,6 @@
             return self._loading_error, 0, 0, 0
         return None
 
-    def RegisterCallbackLoad(self, ExtensionName, ExtensionCallback):
-        """
-        Register function to be called when PLC is loaded
-        ExtensionName: a string with the name of the extension asking to register the callback 
-        ExtensionCallback: the function to be called...
-        """
-        self.LoadCallbacks[ExtensionName] = ExtensionCallback
-
-    def RegisterCallbackUnLoad(self, ExtensionName, ExtensionCallback):
-        """
-        Register function to be called when PLC is unloaded
-        ExtensionName: a string with the name of the extension asking to register the callback 
-        ExtensionCallback: the function to be called...
-        """
-        self.UnLoadCallbacks[ExtensionName] = ExtensionCallback
-
     def _GetMD5FileName(self):
         return os.path.join(self.workingdir, "lasttransferedPLC.md5")
 
@@ -289,8 +270,6 @@
         res = self._LoadPLC()
         if res:
             self.PythonRuntimeInit()
-            for name, callbackFunc in self.LoadCallbacks.items():
-                callbackFunc()
         else:
             self._FreePLC()
 
@@ -299,8 +278,6 @@
     @RunInMain
     def UnLoadPLC(self):
         self.PythonRuntimeCleanup()
-        for name, callbackFunc in self.UnLoadCallbacks.items():
-            callbackFunc()        
         self._FreePLC()
 
     def _InitPLCStubCalls(self):
@@ -474,17 +451,27 @@
         self.PythonThreadCond.notify()
         self.PythonThreadCondLock.release()
 
+    def _fail(self, msg):
+        self.LogMessage(0, msg)
+        self.PLCStatus = PlcStatus.Broken
+        self.StatusChange()
+
+    def PreStartPLC(self):
+        """ 
+        Here goes actions to be taken just before PLC starts, 
+        with all libraries and python object already created.
+        For example : restore saved proprietary parameters
+        """
+        pass
+
     @RunInMain
     def StartPLC(self):
 
-        def fail(msg):
-            self.LogMessage(0, msg)
-            self.PLCStatus = PlcStatus.Broken
-            self.StatusChange()
-
         if self.PLClibraryHandle is None:
             if not self.LoadPLC():
-                fail(_("Problem starting PLC : can't load PLC"))
+                self._fail(_("Problem starting PLC : can't load PLC"))
+
+        self.PreStartPLC()
 
         if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped:
             c_argv = ctypes.c_char_p * len(self.argv)
@@ -495,7 +482,7 @@
                 self.PythonThreadCommand("Activate")
                 self.LogMessage("PLC started")
             else:
-                fail(_("Problem starting PLC : error %d" % res))
+                self._fail(_("Problem starting PLC : error %d" % res))
 
     @RunInMain
     def StopPLC(self):
@@ -533,7 +520,7 @@
     @RunInMain
     def SeedBlob(self, seed):
         blob = (mkstemp(dir=self.tmpdir) + (hashlib.new('md5'),))
-        _fobj, _path, md5sum = blob
+        _fd, _path, md5sum = blob
         md5sum.update(seed)
         newBlobID = md5sum.digest()
         self.blobs[newBlobID] = blob
@@ -546,17 +533,17 @@
         if blob is None:
             return None
 
-        fobj, _path, md5sum = blob
+        fd, _path, md5sum = blob
         md5sum.update(data)
         newBlobID = md5sum.digest()
-        os.write(fobj, data)
+        os.write(fd, data)
         self.blobs[newBlobID] = blob
         return newBlobID
 
     @RunInMain
     def PurgeBlobs(self):
-        for fobj, _path, _md5sum in self.blobs.values():
-            os.close(fobj)
+        for fd, _path, _md5sum in self.blobs.values():
+            os.close(fd)
         self._init_blobs()
 
     def _BlobAsFile(self, blobID, newpath):
@@ -565,8 +552,11 @@
         if blob is None:
             raise Exception(_("Missing data to create file: {}").format(newpath))
 
-        fobj, path, _md5sum = blob
-        os.close(fobj)
+        fd, path, _md5sum = blob
+        fobj = os.fdopen(fd)
+        fobj.flush()
+        os.fsync(fd)
+        fobj.close()
         shutil.move(path, newpath)
 
     def _extra_files_log_path(self):
@@ -622,9 +612,6 @@
                 # Create new PLC file
                 self._BlobAsFile(plc_object, new_PLC_filename)
 
-                # Store new PLC filename based on md5 key
-                open(self._GetMD5FileName(), "w").write(md5sum)
-
                 # Then write the files
                 log = open(extra_files_log, "w")
                 for fname, blobID in extrafiles:
@@ -632,6 +619,12 @@
                     self._BlobAsFile(blobID, fpath)
                     log.write(fname+'\n')
 
+                # Store new PLC filename based on md5 key
+                with open(self._GetMD5FileName(), "w") as f:
+                    f.write(md5sum)
+                    f.flush()
+                    os.fsync(f.fileno())
+
                 # Store new PLC filename
                 self.CurrentPLCFilename = NewFileName
             except Exception:
--- a/tests/python/plc.xml	Fri Dec 25 17:11:38 2020 +0000
+++ b/tests/python/plc.xml	Fri Dec 25 17:12:02 2020 +0000
@@ -1,7 +1,7 @@
 <?xml version='1.0' encoding='utf-8'?>
 <project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
   <fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz:&#10;&#10;   1. How to implement python extensions.&#10;   2. How to implement basic C extension.&#10;   3. How to use C code in IEC POUs.&#10;   4. How to call C functions from python code.&#10;   5. How to avoid race conditions between IEC, C and python code.&#10;   6. How to convert betweet different IEC types.&#10;"/>
-  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2019-09-24T11:49:14">
+  <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-06-17T13:19:14">
     <coordinateInfo>
       <pageSize x="1024" y="1024"/>
       <fbd>
@@ -246,6 +246,25 @@
                 <INT/>
               </type>
             </variable>
+            <variable name="Grumpf">
+              <type>
+                <string/>
+              </type>
+            </variable>
+          </externalVars>
+          <localVars>
+            <variable name="RTC0">
+              <type>
+                <derived name="RTC"/>
+              </type>
+            </variable>
+          </localVars>
+          <externalVars>
+            <variable name="SomeVarName">
+              <type>
+                <DINT/>
+              </type>
+            </variable>
           </externalVars>
         </interface>
         <body>
@@ -255,7 +274,7 @@
               <connectionPointOut>
                 <relPosition x="160" y="15"/>
               </connectionPointOut>
-              <expression>'time.sleep(1)'</expression>
+              <expression>'666'</expression>
             </inVariable>
             <block localId="5" width="125" height="80" typeName="python_eval" instanceName="py1" executionOrderId="0">
               <position x="686" y="400"/>
@@ -1118,23 +1137,12 @@
               </connectionPointOut>
               <expression>Second_Python_Var</expression>
             </inVariable>
-            <outVariable localId="47" height="30" width="130" executionOrderId="0" negated="false">
-              <position x="200" y="1385"/>
-              <connectionPointIn>
-                <relPosition x="0" y="15"/>
-                <connection refLocalId="59">
-                  <position x="200" y="1400"/>
-                  <position x="130" y="1400"/>
-                </connection>
-              </connectionPointIn>
-              <expression>Test_Python_Var</expression>
-            </outVariable>
             <inVariable localId="59" height="30" width="30" executionOrderId="0" negated="false">
               <position x="100" y="1385"/>
               <connectionPointOut>
                 <relPosition x="30" y="15"/>
               </connectionPointOut>
-              <expression>23</expression>
+              <expression>1</expression>
             </inVariable>
             <block localId="61" typeName="function0" executionOrderId="0" height="45" width="111">
               <position x="760" y="1170"/>
@@ -1300,6 +1308,162 @@
 ]]></xhtml:p>
               </content>
             </comment>
+            <outVariable localId="72" executionOrderId="0" height="30" width="60" negated="false">
+              <position x="1065" y="1970"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="76" formalParameter="OUT">
+                  <position x="1065" y="1985"/>
+                  <position x="1025" y="1985"/>
+                  <position x="1025" y="1995"/>
+                  <position x="985" y="1995"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Grumpf</expression>
+            </outVariable>
+            <inVariable localId="73" executionOrderId="0" height="30" width="85" negated="false">
+              <position x="625" y="1940"/>
+              <connectionPointOut>
+                <relPosition x="85" y="15"/>
+              </connectionPointOut>
+              <expression>BOOL#TRUE</expression>
+            </inVariable>
+            <inVariable localId="74" executionOrderId="0" height="30" width="70" negated="false">
+              <position x="625" y="1975"/>
+              <connectionPointOut>
+                <relPosition x="70" y="15"/>
+              </connectionPointOut>
+              <expression>Test_DT</expression>
+            </inVariable>
+            <block localId="75" typeName="RTC" instanceName="RTC0" executionOrderId="0" height="90" width="65">
+              <position x="760" y="1925"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="35"/>
+                    <connection refLocalId="73">
+                      <position x="760" y="1960"/>
+                      <position x="735" y="1960"/>
+                      <position x="735" y="1955"/>
+                      <position x="710" y="1955"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="PDT">
+                  <connectionPointIn>
+                    <relPosition x="0" y="70"/>
+                    <connection refLocalId="74">
+                      <position x="760" y="1995"/>
+                      <position x="727" y="1995"/>
+                      <position x="727" y="1990"/>
+                      <position x="695" y="1990"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="Q">
+                  <connectionPointOut>
+                    <relPosition x="65" y="35"/>
+                  </connectionPointOut>
+                </variable>
+                <variable formalParameter="CDT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="70"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="76" typeName="DT_TO_STRING" executionOrderId="0" height="40" width="110">
+              <position x="875" y="1965"/>
+              <inputVariables>
+                <variable formalParameter="IN">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="75" formalParameter="CDT">
+                      <position x="875" y="1995"/>
+                      <position x="825" y="1995"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="110" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <block localId="77" typeName="ADD" executionOrderId="0" height="60" width="65">
+              <position x="170" y="1370"/>
+              <inputVariables>
+                <variable formalParameter="IN1">
+                  <connectionPointIn>
+                    <relPosition x="0" y="30"/>
+                    <connection refLocalId="59">
+                      <position x="170" y="1400"/>
+                      <position x="130" y="1400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+                <variable formalParameter="IN2">
+                  <connectionPointIn>
+                    <relPosition x="0" y="50"/>
+                    <connection refLocalId="78">
+                      <position x="170" y="1420"/>
+                      <position x="160" y="1420"/>
+                      <position x="160" y="1450"/>
+                      <position x="390" y="1450"/>
+                      <position x="390" y="1400"/>
+                      <position x="380" y="1400"/>
+                    </connection>
+                  </connectionPointIn>
+                </variable>
+              </inputVariables>
+              <inOutVariables/>
+              <outputVariables>
+                <variable formalParameter="OUT">
+                  <connectionPointOut>
+                    <relPosition x="65" y="30"/>
+                  </connectionPointOut>
+                </variable>
+              </outputVariables>
+            </block>
+            <outVariable localId="47" executionOrderId="0" height="30" width="130" negated="false">
+              <position x="625" y="1335"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="79">
+                  <position x="625" y="1350"/>
+                  <position x="590" y="1350"/>
+                </connection>
+              </connectionPointIn>
+              <expression>Test_Python_Var</expression>
+            </outVariable>
+            <inVariable localId="79" executionOrderId="0" height="25" width="30" negated="false">
+              <position x="560" y="1340"/>
+              <connectionPointOut>
+                <relPosition x="30" y="10"/>
+              </connectionPointOut>
+              <expression>23</expression>
+            </inVariable>
+            <inOutVariable localId="78" executionOrderId="0" height="30" width="100" negatedOut="false" negatedIn="false">
+              <position x="280" y="1385"/>
+              <connectionPointIn>
+                <relPosition x="0" y="15"/>
+                <connection refLocalId="77" formalParameter="OUT">
+                  <position x="280" y="1400"/>
+                  <position x="235" y="1400"/>
+                </connection>
+              </connectionPointIn>
+              <connectionPointOut>
+                <relPosition x="100" y="15"/>
+              </connectionPointOut>
+              <expression>SomeVarName</expression>
+            </inOutVariable>
           </FBD>
         </body>
       </pou>
@@ -1447,7 +1611,7 @@
     <configurations>
       <configuration name="config">
         <resource name="res_pytest">
-          <task name="pytest_task" interval="T#1ms" priority="0"/>
+          <task name="pytest_task" priority="0" interval="T#500ms"/>
           <globalVars>
             <variable name="TOTO">
               <type>
--- a/tests/python/py_ext_0@py_ext/pyfile.xml	Fri Dec 25 17:11:38 2020 +0000
+++ b/tests/python/py_ext_0@py_ext/pyfile.xml	Fri Dec 25 17:12:02 2020 +0000
@@ -1,13 +1,17 @@
 <?xml version='1.0' encoding='utf-8'?>
 <PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <variables>
-    <variable name="SomeVarName" type="DINT"/>
-    <variable name="Grumpf" type="STRING"/>
+    <variable name="SomeVarName" type="DINT" onchange="MyFunc"/>
+    <variable name="Grumpf" type="STRING" initial="'mhoo'" onchange="MyFunc"/>
   </variables>
   <globals>
     <xhtml:p><![CDATA[
 print "All python PLC globals variables :", PLCGlobalsDesc
 print "Current extention name :", __ext_name__
+
+def MyFunc(*args):
+    print args
+
 ]]></xhtml:p>
   </globals>
   <init>
--- a/tests/python/python@py_ext/pyfile.xml	Fri Dec 25 17:11:38 2020 +0000
+++ b/tests/python/python@py_ext/pyfile.xml	Fri Dec 25 17:12:02 2020 +0000
@@ -20,6 +20,7 @@
         print "Failed Python_to_C_Call failed"
         res = None
     print "Python read PLC global :",PLCGlobals.Test_Python_Var
+    print "Python read PLC global Grumpf :",PLCGlobals.Grumpf
     PLCGlobals.Second_Python_Var = 789
     sys.stdout.flush()
     return res
--- a/util/ProcessLogger.py	Fri Dec 25 17:11:38 2020 +0000
+++ b/util/ProcessLogger.py	Fri Dec 25 17:12:02 2020 +0000
@@ -31,6 +31,7 @@
 from threading import Timer, Lock, Thread, Semaphore
 import signal
 
+_debug = os.path.exists("BEREMIZ_DEBUG")
 
 class outputThread(Thread):
     """
@@ -77,6 +78,7 @@
                  timeout=None, outlimit=None, errlimit=None,
                  endlog=None, keyword=None, kill_it=False, cwd=None,
                  encoding=None, output_encoding=None):
+        assert(logger)
         self.logger = logger
         if not isinstance(Command, list):
             self.Command_str = Command
@@ -174,8 +176,9 @@
             self.endlog()
 
     def log_the_end(self, ecode, pid):
-        self.logger.write(self.Command_str + "\n")
-        self.logger.write_warning(_("exited with status {a1} (pid {a2})\n").format(a1=str(ecode), a2=str(pid)))
+        if self.logger is not None:
+            self.logger.write(self.Command_str + "\n")
+            self.logger.write_warning(_("exited with status {a1} (pid {a2})\n").format(a1=str(ecode), a2=str(pid)))
 
     def finish(self, pid, ecode):
         # avoid running function before start is finished
@@ -184,7 +187,7 @@
         if self.timeout:
             self.timeout.cancel()
         self.exitcode = ecode
-        if self.exitcode != 0:
+        if _debug or self.exitcode != 0:
             self.log_the_end(ecode, pid)
         if self.finish_callback is not None:
             self.finish_callback(self, ecode, pid)