runtime/Modbus_config.py
changeset 2655 d2b2ee04bfa1
parent 2654 7575050a80c5
child 2656 049b919b7505
equal deleted inserted replaced
2654:7575050a80c5 2655:d2b2ee04bfa1
    59 
    59 
    60 # Directory in which to store the persistent configurations
    60 # Directory in which to store the persistent configurations
    61 # Should be a directory that does not get wiped on reboot!
    61 # Should be a directory that does not get wiped on reboot!
    62 _ModbusConfFiledir = "/tmp"
    62 _ModbusConfFiledir = "/tmp"
    63 
    63 
    64 # Will contain references to the C functions 
    64 # List of all Web Extension Setting nodes we are handling.
    65 # (implemented in beremiz/modbus/mb_runtime.c)
    65 # One WebNode each for:
    66 # used to get/set the Modbus specific configuration paramters
    66 #   - Modbus TCP client 
    67 GetParamFuncs = {}
    67 #   - Modbus TCP server
    68 SetParamFuncs = {}
    68 #   - Modbus RTU client
    69 
    69 #   - Modbus RTU slave
    70 
    70 # configured in the loaded PLC (i.e. the .so file loaded into memory)
    71 # List of all TCP clients configured in the loaded PLC (i.e. the .so file loaded into memory)
    71 # Each entry will be a dictionary. See _AddWebNode() for the details
    72 # Each entry will be a dictionary. See _Add_TCP_Client() for the data structure details...
    72 # of the data structure in each entry.
    73 _TCPclient_list = []
    73 _WebNodeList = []
    74 
    74 
    75 
    75 
    76 
    76 
    77 
    77 
    78 # Paramters we will need to get from the C code, but that will not be shown
    78 # Parameters 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)
    79 # on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
    80 General_parameters = [
    80 General_parameters = [
    81     #    param. name       label                        ctype type         annotate type
    81     #    param. name       label                        ctype type         annotate type
    82     # (C code var name)   (used on web interface)      (C data type)       (web data type)
    82     # (C code var name)   (used on web interface)      (C data type)       (web data type)
    83     #                                                                      (annotate.String,
    83     #                                                                      (annotate.String,
   106     ("parity"           , _("Parity")                , ctypes.c_int,       annotate.Integer),
   106     ("parity"           , _("Parity")                , ctypes.c_int,       annotate.Integer),
   107     ("stop_bits"        , _("Stop Bits")             , 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)
   108     ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
   109     ]
   109     ]
   110 
   110 
   111 
   111 TCPserver_parameters = [                                                   
       
   112     #    param. name       label                        ctype type         annotate type
       
   113     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   114     #                                                                      (annotate.String,
       
   115     #                                                                       annotate.Integer, ...)
       
   116     ("host"             , _("Local IP Address")      , ctypes.c_char_p,    annotate.String),
       
   117     ("port"             , _("Local Port Number")     , ctypes.c_char_p,    annotate.String),
       
   118     ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer)
       
   119     ]
       
   120 
       
   121 RTUslave_parameters = [                                                   
       
   122     #    param. name       label                        ctype type         annotate type
       
   123     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   124     #                                                                      (annotate.String,
       
   125     #                                                                       annotate.Integer, ...)
       
   126     ("device"           , _("Serial Port")           , ctypes.c_char_p,    annotate.String),
       
   127     ("baud"             , _("Baud Rate")             , ctypes.c_int,       annotate.Integer),
       
   128     ("parity"           , _("Parity")                , ctypes.c_int,       annotate.Integer),
       
   129     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       annotate.Integer),
       
   130     ("slave_id"         , _("Slave ID")              , ctypes.c_ulonglong, annotate.Integer)
       
   131     ]
       
   132 
       
   133 
       
   134 # Dictionary containing List of Web viewable parameters
   112 # Note: the dictionary key must be the same as the string returned by the 
   135 # Note: the dictionary key must be the same as the string returned by the 
   113 # __modbus_get_ClientNode_addr_type()
   136 # __modbus_get_ClientNode_addr_type()
   114 # __modbus_get_ServerNode_addr_type()
   137 # __modbus_get_ServerNode_addr_type()
   115 # functions implemented in C (see modbus/mb_runtime.c)
   138 # functions implemented in C (see modbus/mb_runtime.c)
   116 _client_parameters = {}
   139 _client_WebParamListDict = {}
   117 _client_parameters["tcp"  ] = TCPclient_parameters
   140 _client_WebParamListDict["tcp"  ] = TCPclient_parameters
   118 _client_parameters["rtu"  ] = RTUclient_parameters
   141 _client_WebParamListDict["rtu"  ] = RTUclient_parameters
   119 _client_parameters["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
   142 _client_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
       
   143 
       
   144 _server_WebParamListDict = {}
       
   145 _server_WebParamListDict["tcp"  ] = TCPserver_parameters
       
   146 _server_WebParamListDict["rtu"  ] = RTUslave_parameters
       
   147 _server_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
   120 
   148 
   121 
   149 
   122 #def _CheckPortnumber(port_number):
   150 #def _CheckPortnumber(port_number):
   123 #    """ check validity of the port number """
   151 #    """ check validity of the port number """
   124 #    try:
   152 #    try:
   162 
   190 
   163 
   191 
   164 
   192 
   165 
   193 
   166 
   194 
   167 def _SetSavedConfiguration(node_id, newConfig):
   195 def _SetSavedConfiguration(WebNode_id, newConfig):
   168     """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
   196     """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
   169     
   197     
   170     filename = _TCPclient_list[node_id]["filename"]
   198     filename = _WebNodeList[WebNode_id]["filename"]
   171 
   199 
   172     with open(os.path.realpath(filename), 'w') as f:
   200     with open(os.path.realpath(filename), 'w') as f:
   173         json.dump(newConfig, f, sort_keys=True, indent=4)
   201         json.dump(newConfig, f, sort_keys=True, indent=4)
   174         
   202         
   175     _TCPclient_list[node_id]["SavedConfiguration"] = newConfig
   203     _WebNodeList[WebNode_id]["SavedConfiguration"] = newConfig
   176 
   204 
   177 
   205 
   178 
   206 
   179 
   207 
   180 def _DelSavedConfiguration(node_id):
   208 def _DelSavedConfiguration(WebNode_id):
   181     """ Deletes the file cotaining the persistent Modbus configuration """
   209     """ Deletes the file cotaining the persistent Modbus configuration """
   182     filename = _TCPclient_list[node_id]["filename"]
   210     filename = _WebNodeList[WebNode_id]["filename"]
   183     
   211     
   184     if os.path.exists(filename):
   212     if os.path.exists(filename):
   185         os.remove(filename)
   213         os.remove(filename)
   186 
   214 
   187 
   215 
   188 
   216 
   189 
   217 
   190 def _GetSavedConfiguration(node_id):
   218 def _GetSavedConfiguration(WebNode_id):
   191     """
   219     """
   192     Returns a dictionary containing the Modbus parameter configuration
   220     Returns a dictionary containing the Modbus parameter configuration
   193     that was last saved to file. If no file exists, then return None
   221     that was last saved to file. If no file exists, then return None
   194     """
   222     """
   195     filename = _TCPclient_list[node_id]["filename"]
   223     filename = _WebNodeList[WebNode_id]["filename"]
   196     try:
   224     try:
   197         #if os.path.isfile(filename):
   225         #if os.path.isfile(filename):
   198         saved_config = json.load(open(filename))
   226         saved_config = json.load(open(filename))
   199     except Exception:    
   227     except Exception:    
   200         return None
   228         return None
   206 
   234 
   207     return saved_config
   235     return saved_config
   208 
   236 
   209 
   237 
   210 
   238 
   211 def _GetPLCConfiguration(node_id):
   239 def _GetPLCConfiguration(WebNode_id):
   212     """
   240     """
   213     Returns a dictionary containing the current Modbus parameter configuration
   241     Returns a dictionary containing the current Modbus parameter configuration
   214     stored in the C variables in the loaded PLC (.so file)
   242     stored in the C variables in the loaded PLC (.so file)
   215     """
   243     """
   216     current_config = {}
   244     current_config = {}
   217     addr_type = _TCPclient_list[node_id]["addr_type"]
   245     C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
   218 
   246     WebParamList   = _WebNodeList[WebNode_id]["WebParamList"]
   219     for par_name, x1, x2, x3 in _client_parameters[addr_type]:
   247     GetParamFuncs  = _WebNodeList[WebNode_id]["GetParamFuncs"]
   220         value = GetParamFuncs[par_name](node_id)
   248 
       
   249     for par_name, x1, x2, x3 in WebParamList:
       
   250         value = GetParamFuncs[par_name](C_node_id)
   221         if value is not None:
   251         if value is not None:
   222             current_config[par_name] = value
   252             current_config[par_name] = value
   223     
   253     
   224     return current_config
   254     return current_config
   225 
   255 
   226 
   256 
   227 
   257 
   228 def _SetPLCConfiguration(node_id, newconfig):
   258 def _SetPLCConfiguration(WebNode_id, newconfig):
   229     """
   259     """
   230     Stores the Modbus parameter configuration into the
   260     Stores the Modbus parameter configuration into the
   231     the C variables in the loaded PLC (.so file)
   261     the C variables in the loaded PLC (.so file)
   232     """
   262     """
   233     addr_type = _TCPclient_list[node_id]["addr_type"]
   263     C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
   234     
   264     SetParamFuncs  = _WebNodeList[WebNode_id]["SetParamFuncs"]
       
   265 
   235     for par_name in newconfig:
   266     for par_name in newconfig:
   236         value = newconfig[par_name]
   267         value = newconfig[par_name]
   237         if value is not None:
   268         if value is not None:
   238             SetParamFuncs[par_name](node_id, value)
   269             SetParamFuncs[par_name](C_node_id, value)
   239             
   270             
   240 
   271 
   241 
   272 
   242 
   273 
   243 def _GetWebviewConfigurationValue(ctx, node_id, argument):
   274 def _GetWebviewConfigurationValue(ctx, WebNode_id, argument):
   244     """
   275     """
   245     Callback function, called by the web interface (NevowServer.py)
   276     Callback function, called by the web interface (NevowServer.py)
   246     to fill in the default value of each parameter of the web form
   277     to fill in the default value of each parameter of the web form
   247     
   278     
   248     Note that the real callback function is a dynamically created function that
   279     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 
   280     will simply call this function to do the work. It will also pass the WebNode_id 
   250     as a parameter.
   281     as a parameter.
   251     """
   282     """    
   252     try:
   283     try:
   253         return _TCPclient_list[node_id]["WebviewConfiguration"][argument.name]
   284         return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
   254     except Exception:
   285     except Exception:
   255         return ""
   286         return ""
   256 
   287 
   257 
   288 
   258 
   289 
   259 
   290 
   260 def _updateWebInterface(node_id):
   291 def _updateWebInterface(WebNode_id):
   261     """
   292     """
   262     Add/Remove buttons to/from the web interface depending on the current state
   293     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
   294        - If there is a saved state => add a delete saved state button
   264     """
   295     """
   265 
   296 
   266     config_hash = _TCPclient_list[node_id]["config_hash"]
   297     config_hash = _WebNodeList[WebNode_id]["config_hash"]
   267     config_name = _TCPclient_list[node_id]["config_name"]
   298     config_name = _WebNodeList[WebNode_id]["config_name"]
   268     
   299     
   269     # Add a "Delete Saved Configuration" button if there is a saved configuration!
   300     # Add a "Delete Saved Configuration" button if there is a saved configuration!
   270     if _TCPclient_list[node_id]["SavedConfiguration"] is None:
   301     if _WebNodeList[WebNode_id]["SavedConfiguration"] is None:
   271         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
   302         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
   272     else:
   303     else:
   273         def __OnButtonDel(**kwargs):
   304         def __OnButtonDel(**kwargs):
   274             return OnButtonDel(node_id = node_id, **kwargs)
   305             return OnButtonDel(WebNode_id = WebNode_id, **kwargs)
   275                 
   306                 
   276         _NS.ConfigurableSettings.addSettings(
   307         _NS.ConfigurableSettings.addSettings(
   277             "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
   308             "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
   278             _("Modbus Configuration: ") + config_name,  # description (user visible label)
   309             _("Modbus Configuration: ") + config_name,  # description (user visible label)
   279             [],                                         # fields  (empty, no parameters required!)
   310             [],                                         # fields  (empty, no parameters required!)
   289     The function will configure the Modbus plugin in the PLC with the values
   320     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!
   321     specified in the web interface. However, values must be validated first!
   291     
   322     
   292     Note that this function does not get called directly. The real callback
   323     Note that this function does not get called directly. The real callback
   293     function is the dynamic __OnButtonSave() function, which will add the 
   324     function is the dynamic __OnButtonSave() function, which will add the 
   294     "node_id" argument, and call this function to do the work.
   325     "WebNode_id" argument, and call this function to do the work.
   295     """
   326     """
   296 
   327 
   297     #_plcobj.LogMessage("Modbus web server extension::OnButtonSave()  Called")
   328     #_plcobj.LogMessage("Modbus web server extension::OnButtonSave()  Called")
   298     
   329     
   299     newConfig = {}
   330     newConfig    = {}
   300     node_id   = kwargs.get("node_id", None)
   331     WebNode_id   =  kwargs.get("WebNode_id", None)
   301     addr_type = _TCPclient_list[node_id]["addr_type"]
   332     WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
   302     
   333     
   303     for par_name, x1, x2, x3 in _client_parameters[addr_type]:
   334     for par_name, x1, x2, x3 in WebParamList:
   304         value = kwargs.get(par_name, None)
   335         value = kwargs.get(par_name, None)
   305         if value is not None:
   336         if value is not None:
   306             newConfig[par_name] = value
   337             newConfig[par_name] = value
   307 
   338 
   308     _TCPclient_list[node_id]["WebviewConfiguration"] = newConfig
   339     _WebNodeList[WebNode_id]["WebviewConfiguration"] = newConfig
   309     
   340     
   310     # First check if configuration is OK.
   341     # First check if configuration is OK.
   311     ## TODO...
   342     ## TODO...
   312     #if not _CheckWebConfiguration(newConfig):
   343     #if not _CheckWebConfiguration(newConfig):
   313     #    return
   344     #    return
   314 
   345 
   315     # store to file the new configuration so that 
   346     # store to file the new configuration so that 
   316     # we can recoup the configuration the next time the PLC
   347     # we can recoup the configuration the next time the PLC
   317     # has a cold start (i.e. when Beremiz_service.py is retarted)
   348     # has a cold start (i.e. when Beremiz_service.py is retarted)
   318     _SetSavedConfiguration(node_id, newConfig)
   349     _SetSavedConfiguration(WebNode_id, newConfig)
   319 
   350 
   320     # Configure PLC with the current Modbus parameters
   351     # Configure PLC with the current Modbus parameters
   321     _SetPLCConfiguration(node_id, newConfig)
   352     _SetPLCConfiguration(WebNode_id, newConfig)
   322 
   353 
   323     # File has just been created => Delete button must be shown on web interface!
   354     # File has just been created => Delete button must be shown on web interface!
   324     _updateWebInterface(node_id)
   355     _updateWebInterface(WebNode_id)
   325 
   356 
   326 
   357 
   327 
   358 
   328 
   359 
   329 def OnButtonDel(**kwargs):
   360 def OnButtonDel(**kwargs):
   331     Function called when user clicks 'Delete' button in web interface
   362     Function called when user clicks 'Delete' button in web interface
   332     The function will delete the file containing the persistent
   363     The function will delete the file containing the persistent
   333     Modbus configution
   364     Modbus configution
   334     """
   365     """
   335 
   366 
   336     node_id = kwargs.get("node_id", None)
   367     WebNode_id = kwargs.get("WebNode_id", None)
   337     
   368     
   338     # Delete the file
   369     # Delete the file
   339     _DelSavedConfiguration(node_id)
   370     _DelSavedConfiguration(WebNode_id)
   340 
   371 
   341     # Set the current configuration to the default (hardcoded in C)
   372     # Set the current configuration to the default (hardcoded in C)
   342     new_config = _TCPclient_list[node_id]["DefaultConfiguration"]
   373     new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
   343     _SetPLCConfiguration(node_id, new_config)
   374     _SetPLCConfiguration(WebNode_id, new_config)
   344     
   375     
   345     #Update the webviewconfiguration
   376     #Update the webviewconfiguration
   346     _TCPclient_list[node_id]["WebviewConfiguration"] = new_config
   377     _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
   347     
   378     
   348     # Reset SavedConfiguration
   379     # Reset SavedConfiguration
   349     _TCPclient_list[node_id]["SavedConfiguration"] = None
   380     _WebNodeList[WebNode_id]["SavedConfiguration"] = None
   350     
   381     
   351     # File has just been deleted => Delete button on web interface no longer needed!
   382     # File has just been deleted => Delete button on web interface no longer needed!
   352     _updateWebInterface(node_id)
   383     _updateWebInterface(WebNode_id)
   353 
   384 
   354 
   385 
   355 
   386 
   356 
   387 
   357 def OnButtonShowCur(**kwargs):
   388 def OnButtonShowCur(**kwargs):
   359     Function called when user clicks 'Show Current PLC Configuration' button in web interface
   390     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
   391     The function will load the current PLC configuration into the web form
   361 
   392 
   362     Note that this function does not get called directly. The real callback
   393     Note that this function does not get called directly. The real callback
   363     function is the dynamic __OnButtonShowCur() function, which will add the 
   394     function is the dynamic __OnButtonShowCur() function, which will add the 
   364     "node_id" argument, and call this function to do the work.
   395     "WebNode_id" argument, and call this function to do the work.
   365     """
   396     """
   366     node_id = kwargs.get("node_id", None)
   397     WebNode_id = kwargs.get("WebNode_id", None)
   367     
   398     
   368     _TCPclient_list[node_id]["WebviewConfiguration"] = _GetPLCConfiguration(node_id)
   399     _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
   369     
   400     
   370 
   401 
   371 
   402 
   372 
   403 
   373 def _Load_TCP_Client(node_id):
   404 def _AddWebNode(C_node_id, WebParamListDict, GetParamFuncs, SetParamFuncs):
   374     TCPclient_entry = {}
   405     """
   375 
   406     Load from the compiled code (.so file, aloready loaded into memmory)
   376     config_name = GetParamFuncs["config_name"](node_id)
   407     the configuration parameters of a specific Modbus plugin node.
       
   408     This function works with both client and server nodes, depending on the
       
   409     Get/SetParamFunc dictionaries passed to it (either the client or the server
       
   410     node versions of the Get/Set functions)
       
   411     """
       
   412     WebNode_entry = {}
       
   413 
       
   414     config_name = GetParamFuncs["config_name"](C_node_id)
   377     # addr_type will be one of "tcp", "rtu" or "ascii"
   415     # addr_type will be one of "tcp", "rtu" or "ascii"
   378     addr_type   = GetParamFuncs["addr_type"  ](node_id)   
   416     addr_type   = GetParamFuncs["addr_type"  ](C_node_id)   
   379     # For some operations we cannot use the config name (e.g. filename to store config)
   417     # 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
   418     # 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.
   419     # example), so we create a hash of the config_name, and use that instead.
   382     config_hash = hashlib.md5(config_name).hexdigest()
   420     config_hash = hashlib.md5(config_name).hexdigest()
   383     
   421     
   384     _plcobj.LogMessage("Modbus web server extension::_Load_TCP_Client("+str(node_id)+") config_name="+config_name)
   422     #_plcobj.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
   385 
   423 
   386     # Add the new entry to the global list
   424     # 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
   425     # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
   388     #       TCPclient_entry will be stored as a reference, so we can insert parameters at will.
   426     #       WebNode_entry will be stored as a reference, so we can later insert parameters at will.
   389     global _TCPclient_list
   427     global _WebNodeList
   390     _TCPclient_list.append(TCPclient_entry)
   428     _WebNodeList.append(WebNode_entry)
   391 
   429     WebNode_id = len(_WebNodeList) - 1
   392     # store all node_id relevant data for future reference
   430 
   393     TCPclient_entry["node_id"     ] = node_id
   431     # store all WebNode relevant data for future reference
   394     TCPclient_entry["config_name" ] = config_name 
   432     #
   395     TCPclient_entry["addr_type"   ] = addr_type
   433     # Note that "WebParamList" will reference one of:
   396     TCPclient_entry["config_hash" ] = config_hash
   434     #  - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
   397     TCPclient_entry["filename"    ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
   435     WebNode_entry["C_node_id"    ] = C_node_id
       
   436     WebNode_entry["config_name"  ] = config_name 
       
   437     WebNode_entry["config_hash"  ] = config_hash
       
   438     WebNode_entry["filename"     ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
       
   439     WebNode_entry["GetParamFuncs"] = GetParamFuncs
       
   440     WebNode_entry["SetParamFuncs"] = SetParamFuncs
       
   441     WebNode_entry["WebParamList" ] = WebParamListDict[addr_type] 
   398     
   442     
   399     # Dictionary that contains the Modbus configuration currently being shown
   443     # Dictionary that contains the Modbus configuration currently being shown
   400     # on the web interface
   444     # on the web interface
   401     # This configuration will almost always be identical to the current
   445     # This configuration will almost always be identical to the current
   402     # configuration in the PLC (i.e., the current state stored in the 
   446     # configuration in the PLC (i.e., the current state stored in the 
   403     # C variables in the .so file).
   447     # C variables in the .so file).
   404     # The configuration viewed on the web will only be different to the current 
   448     # The configuration viewed on the web will only be different to the current 
   405     # configuration when the user edits the configuration, and when
   449     # configuration when the user edits the configuration, and when
   406     # the user asks to save an edited configuration that contains an error.
   450     # the user asks to save an edited configuration that contains an error.
   407     TCPclient_entry["WebviewConfiguration"] = None
   451     WebNode_entry["WebviewConfiguration"] = None
   408 
   452 
   409     # Upon PLC load, this Dictionary is initialised with the Modbus configuration
   453     # Upon PLC load, this Dictionary is initialised with the Modbus configuration
   410     # hardcoded in the C file
   454     # hardcoded in the C file
   411     # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
   455     # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
   412     TCPclient_entry["DefaultConfiguration"] = _GetPLCConfiguration(node_id)
   456     WebNode_entry["DefaultConfiguration"] = _GetPLCConfiguration(WebNode_id)
   413     TCPclient_entry["WebviewConfiguration"] = TCPclient_entry["DefaultConfiguration"]
   457     WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
   414     
   458     
   415     # Dictionary that stores the Modbus configuration currently stored in a file
   459     # 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
   460     # 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)
   461     # web interface (only shown if "SavedConfiguration" is not None)
   418     SavedConfig = _GetSavedConfiguration(node_id)
   462     SavedConfig = _GetSavedConfiguration(WebNode_id)
   419     TCPclient_entry["SavedConfiguration"] = SavedConfig
   463     WebNode_entry["SavedConfiguration"] = SavedConfig
   420     
   464     
   421     if SavedConfig is not None:
   465     if SavedConfig is not None:
   422         _SetPLCConfiguration(node_id, SavedConfig)
   466         _SetPLCConfiguration(WebNode_id, SavedConfig)
   423         TCPclient_entry["WebviewConfiguration"] = SavedConfig
   467         WebNode_entry["WebviewConfiguration"] = SavedConfig
   424         
   468         
   425     # Define the format for the web form used to show/change the current parameters
   469     # 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
   470     # We first declare a dynamic function to work as callback to obtain the default values for each parameter
   427     def __GetWebviewConfigurationValue(ctx, argument):
   471     def __GetWebviewConfigurationValue(ctx, argument):
   428         return _GetWebviewConfigurationValue(ctx, node_id, argument)
   472         return _GetWebviewConfigurationValue(ctx, WebNode_id, argument)
   429     
   473     
   430     webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
   474     webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
   431                     for name, web_label, c_dtype, web_dtype in _client_parameters[addr_type]]
   475                     for name, web_label, c_dtype, web_dtype in WebParamListDict[addr_type]]
   432 
   476 
   433     # Configure the web interface to include the Modbus config parameters
   477     # Configure the web interface to include the Modbus config parameters
   434     def __OnButtonSave(**kwargs):
   478     def __OnButtonSave(**kwargs):
   435         OnButtonSave(node_id=node_id, **kwargs)
   479         OnButtonSave(WebNode_id=WebNode_id, **kwargs)
   436 
   480 
   437     _NS.ConfigurableSettings.addSettings(
   481     _NS.ConfigurableSettings.addSettings(
   438         "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
   482         "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
   439         _("Modbus Configuration: ") + config_name,     # description (user visible label)
   483         _("Modbus Configuration: ") + config_name,     # description (user visible label)
   440         webFormInterface,                              # fields
   484         webFormInterface,                              # fields
   441         _("Save Configuration to Persistent Storage"), # button label
   485         _("Save Configuration to Persistent Storage"), # button label
   442         __OnButtonSave)                                # callback   
   486         __OnButtonSave)                                # callback   
   443     
   487     
   444     # Add a "View Current Configuration" button 
   488     # Add a "View Current Configuration" button 
   445     def __OnButtonShowCur(**kwargs):
   489     def __OnButtonShowCur(**kwargs):
   446         OnButtonShowCur(node_id=node_id, **kwargs)
   490         OnButtonShowCur(WebNode_id=WebNode_id, **kwargs)
   447 
   491 
   448     _NS.ConfigurableSettings.addSettings(
   492     _NS.ConfigurableSettings.addSettings(
   449         "ModbusConfigViewCur"       + config_hash, # name (internal, may not contain spaces, ...)
   493         "ModbusConfigViewCur"       + config_hash, # name (internal, may not contain spaces, ...)
   450         _("Modbus Configuration: ") + config_name,     # description (user visible label)
   494         _("Modbus Configuration: ") + config_name,     # description (user visible label)
   451         [],                                        # fields  (empty, no parameters required!)
   495         [],                                        # fields  (empty, no parameters required!)
   452         _("Show Current PLC Configuration"),       # button label
   496         _("Show Current PLC Configuration"),       # button label
   453         __OnButtonShowCur)                         # callback    
   497         __OnButtonShowCur)                         # callback    
   454 
   498 
   455     # Add the Delete button to the web interface, if required
   499     # Add the Delete button to the web interface, if required
   456     _updateWebInterface(node_id)
   500     _updateWebInterface(WebNode_id)
       
   501 
   457 
   502 
   458 
   503 
   459 
   504 
   460 
   505 
   461 def OnLoadPLC():
   506 def OnLoadPLC():
   493         # The Modbus plugin in the loaded PLC does not have any client and servers configured
   538         # 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)
   539         #  => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
   495         return
   540         return
   496     
   541     
   497     # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
   542     # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
       
   543     # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
       
   544     GetClientParamFuncs = {}
       
   545     SetClientParamFuncs = {}
       
   546     GetServerParamFuncs = {}
       
   547     SetServerParamFuncs = {}
       
   548 
   498     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
   549     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
   499         GetParamFuncName             = "__modbus_get_ClientNode_" + name        
   550         ParamFuncName                      = "__modbus_get_ClientNode_" + name        
   500         GetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, GetParamFuncName)
   551         GetClientParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
   501         GetParamFuncs[name].restype  = c_dtype
   552         GetClientParamFuncs[name].restype  = c_dtype
   502         GetParamFuncs[name].argtypes = [ctypes.c_int]
   553         GetClientParamFuncs[name].argtypes = [ctypes.c_int]
   503         
   554         
   504     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
   555     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
   505         SetParamFuncName             = "__modbus_set_ClientNode_" + name
   556         ParamFuncName                      = "__modbus_set_ClientNode_" + name
   506         SetParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, SetParamFuncName)
   557         SetClientParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
   507         SetParamFuncs[name].restype  = None
   558         SetClientParamFuncs[name].restype  = None
   508         SetParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
   559         SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
       
   560 
       
   561     for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
       
   562         ParamFuncName                      = "__modbus_get_ServerNode_" + name        
       
   563         GetServerParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
       
   564         GetServerParamFuncs[name].restype  = c_dtype
       
   565         GetServerParamFuncs[name].argtypes = [ctypes.c_int]
       
   566         
       
   567     for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
       
   568         ParamFuncName                      = "__modbus_set_ServerNode_" + name
       
   569         SetServerParamFuncs[name]          = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
       
   570         SetServerParamFuncs[name].restype  = None
       
   571         SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
   509 
   572 
   510     for node_id in range(client_count):
   573     for node_id in range(client_count):
   511         _Load_TCP_Client(node_id)
   574         _AddWebNode(node_id, _client_WebParamListDict ,GetClientParamFuncs, SetClientParamFuncs)
   512 
   575 
       
   576     for node_id in range(server_count):
       
   577         _AddWebNode(node_id, _server_WebParamListDict, GetServerParamFuncs, SetServerParamFuncs)
   513 
   578 
   514 
   579 
   515 
   580 
   516 
   581 
   517 
   582 
   518 def OnUnLoadPLC():
   583 def OnUnLoadPLC():
   519     """
   584     """
   520     # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
   585     Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
   521     """
   586     """
   522 
   587 
   523     #_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
   588     #_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
   524     
   589     
   525     # Delete the Modbus specific web interface extensions
   590     # Delete the Modbus specific web interface extensions
   526     # (Safe to ask to delete, even if it has not been added!)
   591     # (Safe to ask to delete, even if it has not been added!)
   527     global _TCPclient_list    
   592     global _WebNodeList    
   528     for TCPclient_entry in _TCPclient_list:
   593     for WebNode_entry in _WebNodeList:
   529         config_hash = TCPclient_entry["config_hash"]
   594         config_hash = WebNode_entry["config_hash"]
   530         _NS.ConfigurableSettings.delSettings("ModbusConfigParm"     + config_hash)
   595         _NS.ConfigurableSettings.delSettings("ModbusConfigParm"     + config_hash)
   531         _NS.ConfigurableSettings.delSettings("ModbusConfigViewCur"  + config_hash)  
   596         _NS.ConfigurableSettings.delSettings("ModbusConfigViewCur"  + config_hash)  
   532         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)  
   597         _NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)  
   533         
   598         
   534     # Dele all entries...
   599     # Dele all entries...
   535     _TCPclient_list = []
   600     _WebNodeList = []
   536 
   601 
   537 
   602 
   538 
   603 
   539 # The Beremiz_service.py service, along with the integrated web server it launches
   604 # 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
   605 # (i.e. Nevow web server, in runtime/NevowServer.py), will go through several states