runtime/Modbus_config.py
changeset 2654 7575050a80c5
child 2655 d2b2ee04bfa1
equal deleted inserted replaced
2649:db68cb0e6bdc 2654:7575050a80c5
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # This file is part of Beremiz runtime.
       
     5 #
       
     6 # Copyright (C) 2020: Mario de Sousa
       
     7 #
       
     8 # See COPYING.Runtime file for copyrights details.
       
     9 #
       
    10 # This library is free software; you can redistribute it and/or
       
    11 # modify it under the terms of the GNU Lesser General Public
       
    12 # License as published by the Free Software Foundation; either
       
    13 # version 2.1 of the License, or (at your option) any later version.
       
    14 
       
    15 # This library is distributed in the hope that it will be useful,
       
    16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    18 # Lesser General Public License for more details.
       
    19 
       
    20 # You should have received a copy of the GNU Lesser General Public
       
    21 # License along with this library; if not, write to the Free Software
       
    22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
       
    23 
       
    24 
       
    25 
       
    26 
       
    27 ##############################################################################################
       
    28 # This file implements an extension to the web server embedded in the Beremiz_service.py     #
       
    29 # runtime manager (webserver is in runtime/NevowServer.py).                                  #
       
    30 #                                                                                            #
       
    31 # The extension implemented in this file allows for runtime configuration                    #
       
    32 # of Modbus plugin parameters                                                                #
       
    33 ##############################################################################################
       
    34 
       
    35 
       
    36 
       
    37 import json
       
    38 import os
       
    39 import ctypes
       
    40 import string
       
    41 import hashlib
       
    42 
       
    43 from formless import annotate, webform
       
    44 
       
    45 
       
    46 
       
    47 # reference to the PLCObject in runtime/PLCObject.py
       
    48 # PLCObject is a singleton, created in runtime/__init__.py
       
    49 _plcobj = None
       
    50 
       
    51 # reference to the Nevow web server (a.k.a as NS in Beremiz_service.py)
       
    52 # (Note that NS will reference the NevowServer.py _module_, and not an object/class)
       
    53 _NS = None
       
    54 
       
    55 
       
    56 # WorkingDir: the directory on which Beremiz_service.py is running, and where 
       
    57 #             all the files downloaded to the PLC get stored
       
    58 _WorkingDir = None
       
    59 
       
    60 # Directory in which to store the persistent configurations
       
    61 # Should be a directory that does not get wiped on reboot!
       
    62 _ModbusConfFiledir = "/tmp"
       
    63 
       
    64 # Will contain references to the C functions 
       
    65 # (implemented in beremiz/modbus/mb_runtime.c)
       
    66 # used to get/set the Modbus specific configuration paramters
       
    67 GetParamFuncs = {}
       
    68 SetParamFuncs = {}
       
    69 
       
    70 
       
    71 # List of all TCP clients configured in the loaded PLC (i.e. the .so file loaded into memory)
       
    72 # Each entry will be a dictionary. See _Add_TCP_Client() for the data structure details...
       
    73 _TCPclient_list = []
       
    74 
       
    75 
       
    76 
       
    77 
       
    78 # Paramters we will need to get from the C code, but that will not be shown
       
    79 # on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
       
    80 General_parameters = [
       
    81     #    param. name       label                        ctype type         annotate type
       
    82     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
    83     #                                                                      (annotate.String,
       
    84     #                                                                       annotate.Integer, ...)
       
    85     ("config_name"      , _("")                      , ctypes.c_char_p,    annotate.String),
       
    86     ("addr_type"        , _("")                      , ctypes.c_char_p,    annotate.String)
       
    87     ]                                                                      
       
    88                                                                            
       
    89 TCPclient_parameters = [                                                   
       
    90     #    param. name       label                        ctype type         annotate type
       
    91     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
    92     #                                                                      (annotate.String,
       
    93     #                                                                       annotate.Integer, ...)
       
    94     ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    annotate.String),
       
    95     ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    annotate.String),
       
    96     ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
       
    97     ]
       
    98 
       
    99 RTUclient_parameters = [                                                   
       
   100     #    param. name       label                        ctype type         annotate type
       
   101     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   102     #                                                                      (annotate.String,
       
   103     #                                                                       annotate.Integer, ...)
       
   104     ("device"           , _("Serial Port")           , ctypes.c_char_p,    annotate.String),
       
   105     ("baud"             , _("Baud Rate")             , ctypes.c_int,       annotate.Integer),
       
   106     ("parity"           , _("Parity")                , ctypes.c_int,       annotate.Integer),
       
   107     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       annotate.Integer),
       
   108     ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
       
   109     ]
       
   110 
       
   111 
       
   112 # Note: the dictionary key must be the same as the string returned by the 
       
   113 # __modbus_get_ClientNode_addr_type()
       
   114 # __modbus_get_ServerNode_addr_type()
       
   115 # functions implemented in C (see modbus/mb_runtime.c)
       
   116 _client_parameters = {}
       
   117 _client_parameters["tcp"  ] = TCPclient_parameters
       
   118 _client_parameters["rtu"  ] = RTUclient_parameters
       
   119 _client_parameters["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
       
   120 
       
   121 
       
   122 #def _CheckPortnumber(port_number):
       
   123 #    """ check validity of the port number """
       
   124 #    try:
       
   125 #        portnum = int(port_number)
       
   126 #        if (portnum < 0) or (portnum > 65535):
       
   127 #           raise Exception
       
   128 #    except Exception:    
       
   129 #        return False
       
   130 #        
       
   131 #    return True
       
   132 
       
   133 
       
   134 
       
   135 
       
   136 #def _CheckConfiguration(BACnetConfig):
       
   137 #    res = True    
       
   138 #    res = res and _CheckPortnumber(BACnetConfig["port_number"])
       
   139 #    res = res and _CheckDeviceID  (BACnetConfig["device_id"])
       
   140 #    return res
       
   141 
       
   142 
       
   143 
       
   144 #def _CheckWebConfiguration(BACnetConfig):
       
   145 #    res = True
       
   146 #    
       
   147 #    # check the port number
       
   148 #    if not _CheckPortnumber(BACnetConfig["port_number"]):
       
   149 #        raise annotate.ValidateError(
       
   150 #            {"port_number": "Invalid port number: " + str(BACnetConfig["port_number"])},
       
   151 #            _("Modbus configuration error:"))
       
   152 #        res = False
       
   153 #    
       
   154 #    if not _CheckDeviceID(BACnetConfig["device_id"]):
       
   155 #        raise annotate.ValidateError(
       
   156 #            {"device_id": "Invalid device ID: " + str(BACnetConfig["device_id"])},
       
   157 #            _("Modbus configuration error:"))
       
   158 #        res = False
       
   159 #        
       
   160 #    return res
       
   161 
       
   162 
       
   163 
       
   164 
       
   165 
       
   166 
       
   167 def _SetSavedConfiguration(node_id, newConfig):
       
   168     """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
       
   169     
       
   170     filename = _TCPclient_list[node_id]["filename"]
       
   171 
       
   172     with open(os.path.realpath(filename), 'w') as f:
       
   173         json.dump(newConfig, f, sort_keys=True, indent=4)
       
   174         
       
   175     _TCPclient_list[node_id]["SavedConfiguration"] = newConfig
       
   176 
       
   177 
       
   178 
       
   179 
       
   180 def _DelSavedConfiguration(node_id):
       
   181     """ Deletes the file cotaining the persistent Modbus configuration """
       
   182     filename = _TCPclient_list[node_id]["filename"]
       
   183     
       
   184     if os.path.exists(filename):
       
   185         os.remove(filename)
       
   186 
       
   187 
       
   188 
       
   189 
       
   190 def _GetSavedConfiguration(node_id):
       
   191     """
       
   192     Returns a dictionary containing the Modbus parameter configuration
       
   193     that was last saved to file. If no file exists, then return None
       
   194     """
       
   195     filename = _TCPclient_list[node_id]["filename"]
       
   196     try:
       
   197         #if os.path.isfile(filename):
       
   198         saved_config = json.load(open(filename))
       
   199     except Exception:    
       
   200         return None
       
   201 
       
   202     #if _CheckConfiguration(saved_config):
       
   203     #    return saved_config
       
   204     #else:
       
   205     #    return None
       
   206 
       
   207     return saved_config
       
   208 
       
   209 
       
   210 
       
   211 def _GetPLCConfiguration(node_id):
       
   212     """
       
   213     Returns a dictionary containing the current Modbus parameter configuration
       
   214     stored in the C variables in the loaded PLC (.so file)
       
   215     """
       
   216     current_config = {}
       
   217     addr_type = _TCPclient_list[node_id]["addr_type"]
       
   218 
       
   219     for par_name, x1, x2, x3 in _client_parameters[addr_type]:
       
   220         value = GetParamFuncs[par_name](node_id)
       
   221         if value is not None:
       
   222             current_config[par_name] = value
       
   223     
       
   224     return current_config
       
   225 
       
   226 
       
   227 
       
   228 def _SetPLCConfiguration(node_id, newconfig):
       
   229     """
       
   230     Stores the Modbus parameter configuration into the
       
   231     the C variables in the loaded PLC (.so file)
       
   232     """
       
   233     addr_type = _TCPclient_list[node_id]["addr_type"]
       
   234     
       
   235     for par_name in newconfig:
       
   236         value = newconfig[par_name]
       
   237         if value is not None:
       
   238             SetParamFuncs[par_name](node_id, value)
       
   239             
       
   240 
       
   241 
       
   242 
       
   243 def _GetWebviewConfigurationValue(ctx, node_id, argument):
       
   244     """
       
   245     Callback function, called by the web interface (NevowServer.py)
       
   246     to fill in the default value of each parameter of the web form
       
   247     
       
   248     Note that the real callback function is a dynamically created function that
       
   249     will simply call this function to do the work. It will also pass the node_id 
       
   250     as a parameter.
       
   251     """
       
   252     try:
       
   253         return _TCPclient_list[node_id]["WebviewConfiguration"][argument.name]
       
   254     except Exception:
       
   255         return ""
       
   256 
       
   257 
       
   258 
       
   259 
       
   260 def _updateWebInterface(node_id):
       
   261     """
       
   262     Add/Remove buttons to/from the web interface depending on the current state
       
   263        - If there is a saved state => add a delete saved state button
       
   264     """
       
   265 
       
   266     config_hash = _TCPclient_list[node_id]["config_hash"]
       
   267     config_name = _TCPclient_list[node_id]["config_name"]
       
   268     
       
   269     # Add a "Delete Saved Configuration" button if there is a saved configuration!
       
   270     if _TCPclient_list[node_id]["SavedConfiguration"] is None:
       
   271         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
       
   272     else:
       
   273         def __OnButtonDel(**kwargs):
       
   274             return OnButtonDel(node_id = node_id, **kwargs)
       
   275                 
       
   276         _NS.ConfigurableSettings.addSettings(
       
   277             "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
       
   278             _("Modbus Configuration: ") + config_name,  # description (user visible label)
       
   279             [],                                         # fields  (empty, no parameters required!)
       
   280             _("Delete Configuration Stored in Persistent Storage"), # button label
       
   281             __OnButtonDel,                              # callback    
       
   282             "ModbusConfigParm"          + config_hash)  # Add after entry xxxx
       
   283 
       
   284 
       
   285 
       
   286 def OnButtonSave(**kwargs):
       
   287     """
       
   288     Function called when user clicks 'Save' button in web interface
       
   289     The function will configure the Modbus plugin in the PLC with the values
       
   290     specified in the web interface. However, values must be validated first!
       
   291     
       
   292     Note that this function does not get called directly. The real callback
       
   293     function is the dynamic __OnButtonSave() function, which will add the 
       
   294     "node_id" argument, and call this function to do the work.
       
   295     """
       
   296 
       
   297     #_plcobj.LogMessage("Modbus web server extension::OnButtonSave()  Called")
       
   298     
       
   299     newConfig = {}
       
   300     node_id   = kwargs.get("node_id", None)
       
   301     addr_type = _TCPclient_list[node_id]["addr_type"]
       
   302     
       
   303     for par_name, x1, x2, x3 in _client_parameters[addr_type]:
       
   304         value = kwargs.get(par_name, None)
       
   305         if value is not None:
       
   306             newConfig[par_name] = value
       
   307 
       
   308     _TCPclient_list[node_id]["WebviewConfiguration"] = newConfig
       
   309     
       
   310     # First check if configuration is OK.
       
   311     ## TODO...
       
   312     #if not _CheckWebConfiguration(newConfig):
       
   313     #    return
       
   314 
       
   315     # store to file the new configuration so that 
       
   316     # we can recoup the configuration the next time the PLC
       
   317     # has a cold start (i.e. when Beremiz_service.py is retarted)
       
   318     _SetSavedConfiguration(node_id, newConfig)
       
   319 
       
   320     # Configure PLC with the current Modbus parameters
       
   321     _SetPLCConfiguration(node_id, newConfig)
       
   322 
       
   323     # File has just been created => Delete button must be shown on web interface!
       
   324     _updateWebInterface(node_id)
       
   325 
       
   326 
       
   327 
       
   328 
       
   329 def OnButtonDel(**kwargs):
       
   330     """
       
   331     Function called when user clicks 'Delete' button in web interface
       
   332     The function will delete the file containing the persistent
       
   333     Modbus configution
       
   334     """
       
   335 
       
   336     node_id = kwargs.get("node_id", None)
       
   337     
       
   338     # Delete the file
       
   339     _DelSavedConfiguration(node_id)
       
   340 
       
   341     # Set the current configuration to the default (hardcoded in C)
       
   342     new_config = _TCPclient_list[node_id]["DefaultConfiguration"]
       
   343     _SetPLCConfiguration(node_id, new_config)
       
   344     
       
   345     #Update the webviewconfiguration
       
   346     _TCPclient_list[node_id]["WebviewConfiguration"] = new_config
       
   347     
       
   348     # Reset SavedConfiguration
       
   349     _TCPclient_list[node_id]["SavedConfiguration"] = None
       
   350     
       
   351     # File has just been deleted => Delete button on web interface no longer needed!
       
   352     _updateWebInterface(node_id)
       
   353 
       
   354 
       
   355 
       
   356 
       
   357 def OnButtonShowCur(**kwargs):
       
   358     """
       
   359     Function called when user clicks 'Show Current PLC Configuration' button in web interface
       
   360     The function will load the current PLC configuration into the web form
       
   361 
       
   362     Note that this function does not get called directly. The real callback
       
   363     function is the dynamic __OnButtonShowCur() function, which will add the 
       
   364     "node_id" argument, and call this function to do the work.
       
   365     """
       
   366     node_id = kwargs.get("node_id", None)
       
   367     
       
   368     _TCPclient_list[node_id]["WebviewConfiguration"] = _GetPLCConfiguration(node_id)
       
   369     
       
   370 
       
   371 
       
   372 
       
   373 def _Load_TCP_Client(node_id):
       
   374     TCPclient_entry = {}
       
   375 
       
   376     config_name = GetParamFuncs["config_name"](node_id)
       
   377     # addr_type will be one of "tcp", "rtu" or "ascii"
       
   378     addr_type   = GetParamFuncs["addr_type"  ](node_id)   
       
   379     # For some operations we cannot use the config name (e.g. filename to store config)
       
   380     # because the user may be using characters that are invalid for that purpose ('/' for
       
   381     # example), so we create a hash of the config_name, and use that instead.
       
   382     config_hash = hashlib.md5(config_name).hexdigest()
       
   383     
       
   384     _plcobj.LogMessage("Modbus web server extension::_Load_TCP_Client("+str(node_id)+") config_name="+config_name)
       
   385 
       
   386     # Add the new entry to the global list
       
   387     # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in TCPclient_entry
       
   388     #       TCPclient_entry will be stored as a reference, so we can insert parameters at will.
       
   389     global _TCPclient_list
       
   390     _TCPclient_list.append(TCPclient_entry)
       
   391 
       
   392     # store all node_id relevant data for future reference
       
   393     TCPclient_entry["node_id"     ] = node_id
       
   394     TCPclient_entry["config_name" ] = config_name 
       
   395     TCPclient_entry["addr_type"   ] = addr_type
       
   396     TCPclient_entry["config_hash" ] = config_hash
       
   397     TCPclient_entry["filename"    ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
       
   398     
       
   399     # Dictionary that contains the Modbus configuration currently being shown
       
   400     # on the web interface
       
   401     # This configuration will almost always be identical to the current
       
   402     # configuration in the PLC (i.e., the current state stored in the 
       
   403     # C variables in the .so file).
       
   404     # The configuration viewed on the web will only be different to the current 
       
   405     # configuration when the user edits the configuration, and when
       
   406     # the user asks to save an edited configuration that contains an error.
       
   407     TCPclient_entry["WebviewConfiguration"] = None
       
   408 
       
   409     # Upon PLC load, this Dictionary is initialised with the Modbus configuration
       
   410     # hardcoded in the C file
       
   411     # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
       
   412     TCPclient_entry["DefaultConfiguration"] = _GetPLCConfiguration(node_id)
       
   413     TCPclient_entry["WebviewConfiguration"] = TCPclient_entry["DefaultConfiguration"]
       
   414     
       
   415     # Dictionary that stores the Modbus configuration currently stored in a file
       
   416     # Currently only used to decide whether or not to show the "Delete" button on the
       
   417     # web interface (only shown if _SavedConfiguration is not None)
       
   418     SavedConfig = _GetSavedConfiguration(node_id)
       
   419     TCPclient_entry["SavedConfiguration"] = SavedConfig
       
   420     
       
   421     if SavedConfig is not None:
       
   422         _SetPLCConfiguration(node_id, SavedConfig)
       
   423         TCPclient_entry["WebviewConfiguration"] = SavedConfig
       
   424         
       
   425     # Define the format for the web form used to show/change the current parameters
       
   426     # We first declare a dynamic function to work as callback to obtain the default values for each parameter
       
   427     def __GetWebviewConfigurationValue(ctx, argument):
       
   428         return _GetWebviewConfigurationValue(ctx, node_id, argument)
       
   429     
       
   430     webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
       
   431                     for name, web_label, c_dtype, web_dtype in _client_parameters[addr_type]]
       
   432 
       
   433     # Configure the web interface to include the Modbus config parameters
       
   434     def __OnButtonSave(**kwargs):
       
   435         OnButtonSave(node_id=node_id, **kwargs)
       
   436 
       
   437     _NS.ConfigurableSettings.addSettings(
       
   438         "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
       
   439         _("Modbus Configuration: ") + config_name,     # description (user visible label)
       
   440         webFormInterface,                              # fields
       
   441         _("Save Configuration to Persistent Storage"), # button label
       
   442         __OnButtonSave)                                # callback   
       
   443     
       
   444     # Add a "View Current Configuration" button 
       
   445     def __OnButtonShowCur(**kwargs):
       
   446         OnButtonShowCur(node_id=node_id, **kwargs)
       
   447 
       
   448     _NS.ConfigurableSettings.addSettings(
       
   449         "ModbusConfigViewCur"       + config_hash, # name (internal, may not contain spaces, ...)
       
   450         _("Modbus Configuration: ") + config_name,     # description (user visible label)
       
   451         [],                                        # fields  (empty, no parameters required!)
       
   452         _("Show Current PLC Configuration"),       # button label
       
   453         __OnButtonShowCur)                         # callback    
       
   454 
       
   455     # Add the Delete button to the web interface, if required
       
   456     _updateWebInterface(node_id)
       
   457 
       
   458 
       
   459 
       
   460 
       
   461 def OnLoadPLC():
       
   462     """
       
   463     Callback function, called (by PLCObject.py) when a new PLC program
       
   464     (i.e. XXX.so file) is transfered to the PLC runtime
       
   465     and loaded into memory
       
   466     """
       
   467 
       
   468     #_plcobj.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
       
   469 
       
   470     if _plcobj.PLClibraryHandle is None:
       
   471         # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
       
   472         # Hmm... This shold never occur!! 
       
   473         return  
       
   474     
       
   475     # Get the number of Modbus Client and Servers (Modbus plugin)
       
   476     # configured in the currently loaded PLC project (i.e., the .so file)
       
   477     # If the "__modbus_plugin_client_node_count" 
       
   478     # or the "__modbus_plugin_server_node_count" C variables 
       
   479     # are not present in the .so file we conclude that the currently loaded 
       
   480     # PLC does not have the Modbus plugin included (situation (2b) described above init())
       
   481     try:
       
   482         client_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_client_node_count").value
       
   483         server_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_server_node_count").value
       
   484     except Exception:
       
   485         # Loaded PLC does not have the Modbus plugin => nothing to do
       
   486         #   (i.e. do _not_ configure and make available the Modbus web interface)
       
   487         return
       
   488 
       
   489     if client_count < 0: client_count = 0
       
   490     if server_count < 0: server_count = 0
       
   491     
       
   492     if (client_count == 0) and (server_count == 0):
       
   493         # The Modbus plugin in the loaded PLC does not have any client and servers configured
       
   494         #  => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
       
   495         return
       
   496     
       
   497     # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
       
   498     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
       
   499         GetParamFuncName             = "__modbus_get_ClientNode_" + name        
       
   500         GetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, GetParamFuncName)
       
   501         GetParamFuncs[name].restype  = c_dtype
       
   502         GetParamFuncs[name].argtypes = [ctypes.c_int]
       
   503         
       
   504     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
       
   505         SetParamFuncName             = "__modbus_set_ClientNode_" + name
       
   506         SetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, SetParamFuncName)
       
   507         SetParamFuncs[name].restype  = None
       
   508         SetParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
       
   509 
       
   510     for node_id in range(client_count):
       
   511         _Load_TCP_Client(node_id)
       
   512 
       
   513 
       
   514 
       
   515 
       
   516 
       
   517 
       
   518 def OnUnLoadPLC():
       
   519     """
       
   520     # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
       
   521     """
       
   522 
       
   523     #_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
       
   524     
       
   525     # Delete the Modbus specific web interface extensions
       
   526     # (Safe to ask to delete, even if it has not been added!)
       
   527     global _TCPclient_list    
       
   528     for TCPclient_entry in _TCPclient_list:
       
   529         config_hash = TCPclient_entry["config_hash"]
       
   530         _NS.ConfigurableSettings.delSettings("ModbusConfigParm"     + config_hash)
       
   531         _NS.ConfigurableSettings.delSettings("ModbusConfigViewCur"  + config_hash)  
       
   532         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)  
       
   533         
       
   534     # Dele all entries...
       
   535     _TCPclient_list = []
       
   536 
       
   537 
       
   538 
       
   539 # The Beremiz_service.py service, along with the integrated web server it launches
       
   540 # (i.e. Nevow web server, in runtime/NevowServer.py), will go through several states
       
   541 # once started:
       
   542 #  (1) Web server is started, but no PLC is loaded
       
   543 #  (2) PLC is loaded (i.e. the PLC compiled code is loaded)
       
   544 #         (a) The loaded PLC includes the Modbus plugin
       
   545 #         (b) The loaded PLC does not have the Modbus plugin
       
   546 #
       
   547 # During (1) and (2a):
       
   548 #     we configure the web server interface to not have the Modbus web configuration extension
       
   549 # During (2b) 
       
   550 #     we configure the web server interface to include the Modbus web configuration extension
       
   551 #
       
   552 # PS: reference to the pyroserver  (i.e., the server object of Beremiz_service.py)
       
   553 #     (NOTE: PS.plcobj is a reference to PLCObject.py)
       
   554 # NS: reference to the web server (i.e. the NevowServer.py module)
       
   555 # WorkingDir: the directory on which Beremiz_service.py is running, and where 
       
   556 #             all the files downloaded to the PLC get stored, including
       
   557 #             the .so file with the compiled C generated code
       
   558 def init(plcobj, NS, WorkingDir):
       
   559     #PS.plcobj.LogMessage("Modbus web server extension::init(PS, NS, " + WorkingDir + ") Called")
       
   560     global _WorkingDir
       
   561     _WorkingDir = WorkingDir
       
   562     global _plcobj
       
   563     _plcobj = plcobj
       
   564     global _NS
       
   565     _NS = NS
       
   566 
       
   567     _plcobj.RegisterCallbackLoad  ("Modbus_Settins_Extension", OnLoadPLC)
       
   568     _plcobj.RegisterCallbackUnLoad("Modbus_Settins_Extension", OnUnLoadPLC)
       
   569     OnUnLoadPLC() # init is called before the PLC gets loaded...  so we make sure we have the correct state