modbus/web_settings.py
changeset 2669 be233279d179
parent 2666 5f48d5e60a81
child 2670 fd348d79a1f3
equal deleted inserted replaced
2668:cca3e5d7d6f3 2669:be233279d179
       
     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 import runtime.NevowServer as NS
       
    46 
       
    47 # Directory in which to store the persistent configurations
       
    48 # Should be a directory that does not get wiped on reboot!
       
    49 # TODO FIXME WTF
       
    50 _ModbusConfFiledir = "/tmp"
       
    51 
       
    52 # List of all Web Extension Setting nodes we are handling.
       
    53 # One WebNode each for:
       
    54 #   - Modbus TCP client 
       
    55 #   - Modbus TCP server
       
    56 #   - Modbus RTU client
       
    57 #   - Modbus RTU slave
       
    58 # configured in the loaded PLC (i.e. the .so file loaded into memory)
       
    59 # Each entry will be a dictionary. See _AddWebNode() for the details
       
    60 # of the data structure in each entry.
       
    61 _WebNodeList = []
       
    62 
       
    63 
       
    64 
       
    65 
       
    66 class MB_StrippedString(annotate.String):
       
    67     def __init__(self, *args, **kwargs):
       
    68         annotate.String.__init__(self, strip = True, *args, **kwargs)
       
    69 
       
    70 
       
    71 class MB_StopBits(annotate.Choice):
       
    72     _choices = [0, 1, 2]
       
    73 
       
    74     def coerce(self, val, configurable):
       
    75         return int(val)
       
    76     def __init__(self, *args, **kwargs):
       
    77         annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
       
    78 
       
    79 
       
    80 class MB_Baud(annotate.Choice):
       
    81     _choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
       
    82 
       
    83     def coerce(self, val, configurable):
       
    84         return int(val)
       
    85     def __init__(self, *args, **kwargs):
       
    86         annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
       
    87 
       
    88 
       
    89 class MB_Parity(annotate.Choice):
       
    90     # For more info on what this class really does, have a look at the code in
       
    91     # file twisted/nevow/annotate.py
       
    92     # grab this code from $git clone https://github.com/twisted/nevow/
       
    93     # 
       
    94     # Warning: do _not_ name this variable choice[] without underscore, as that name is
       
    95     # already used for another similar variable by the underlying class annotate.Choice
       
    96     _choices = [  0,      1,      2  ]
       
    97     _label   = ["none", "odd", "even"]
       
    98     
       
    99     def choice_to_label(self, key):
       
   100         #PLCObject.LogMessage("Modbus web server extension::choice_to_label()  " + str(key))
       
   101         return self._label[key]
       
   102     
       
   103     def coerce(self, val, configurable):
       
   104         """Coerce a value with the help of an object, which is the object
       
   105         we are configuring.
       
   106         """
       
   107         # Basically, make sure the value the user introduced is valid, and transform
       
   108         # into something that is valid if necessary or mark it as an error 
       
   109         # (by raising an exception ??).
       
   110         #
       
   111         # We are simply using this functions to transform the input value (a string)
       
   112         # into an integer. Note that although the available options are all
       
   113         # integers (0, 1 or 2), even though what is shown on the user interface
       
   114         # are actually strings, i.e. the labels), these parameters are for some 
       
   115         # reason being parsed as strings, so we need to map them back to an
       
   116         # integer.
       
   117         #
       
   118         #PLCObject.LogMessage("Modbus web server extension::coerce  " + val )
       
   119         return int(val)
       
   120 
       
   121     def __init__(self, *args, **kwargs):
       
   122         annotate.Choice.__init__(self, 
       
   123                                  choices   = self._choices,
       
   124                                  stringify = self.choice_to_label,
       
   125                                  *args, **kwargs)
       
   126 
       
   127 
       
   128 
       
   129 # Parameters we will need to get from the C code, but that will not be shown
       
   130 # on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
       
   131 #
       
   132 # The annotate type entry is basically useless and is completely ignored.
       
   133 # We kee that entry so that this list can later be correctly merged with the
       
   134 # following lists...
       
   135 General_parameters = [
       
   136     #    param. name       label                        ctype type         annotate type
       
   137     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   138     #                                                                      (annotate.String,
       
   139     #                                                                       annotate.Integer, ...)
       
   140     ("config_name"      , _("")                      , ctypes.c_char_p,    annotate.String),
       
   141     ("addr_type"        , _("")                      , ctypes.c_char_p,    annotate.String)
       
   142     ]                                                                      
       
   143                                                                            
       
   144 # Parameters we will need to get from the C code, and that _will_ be shown
       
   145 # on the web interface.
       
   146 TCPclient_parameters = [                                                   
       
   147     #    param. name       label                        ctype type         annotate type
       
   148     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   149     #                                                                      (annotate.String,
       
   150     #                                                                       annotate.Integer, ...)
       
   151     ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    MB_StrippedString),
       
   152     ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    MB_StrippedString),
       
   153     ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer )
       
   154     ]
       
   155 
       
   156 RTUclient_parameters = [                                                   
       
   157     #    param. name       label                        ctype type         annotate type
       
   158     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   159     #                                                                      (annotate.String,
       
   160     #                                                                       annotate.Integer, ...)
       
   161     ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
       
   162     ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
       
   163     ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
       
   164     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
       
   165     ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
       
   166     ]
       
   167 
       
   168 TCPserver_parameters = [                                                   
       
   169     #    param. name       label                        ctype type         annotate type
       
   170     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   171     #                                                                      (annotate.String,
       
   172     #                                                                       annotate.Integer, ...)
       
   173     ("host"             , _("Local IP Address")      , ctypes.c_char_p,    MB_StrippedString),
       
   174     ("port"             , _("Local Port Number")     , ctypes.c_char_p,    MB_StrippedString),
       
   175     ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer )
       
   176     ]
       
   177 
       
   178 RTUslave_parameters = [                                                   
       
   179     #    param. name       label                        ctype type         annotate type
       
   180     # (C code var name)   (used on web interface)      (C data type)       (web data type)
       
   181     #                                                                      (annotate.String,
       
   182     #                                                                       annotate.Integer, ...)
       
   183     ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
       
   184     ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
       
   185     ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
       
   186     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
       
   187     ("slave_id"         , _("Slave ID")              , ctypes.c_ulonglong, annotate.Integer)
       
   188     ]
       
   189 
       
   190 
       
   191 
       
   192 
       
   193 # Dictionary containing List of Web viewable parameters
       
   194 # Note: the dictionary key must be the same as the string returned by the 
       
   195 # __modbus_get_ClientNode_addr_type()
       
   196 # __modbus_get_ServerNode_addr_type()
       
   197 # functions implemented in C (see modbus/mb_runtime.c)
       
   198 _client_WebParamListDict = {}
       
   199 _client_WebParamListDict["tcp"  ] = TCPclient_parameters
       
   200 _client_WebParamListDict["rtu"  ] = RTUclient_parameters
       
   201 _client_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
       
   202 
       
   203 _server_WebParamListDict = {}
       
   204 _server_WebParamListDict["tcp"  ] = TCPserver_parameters
       
   205 _server_WebParamListDict["rtu"  ] = RTUslave_parameters
       
   206 _server_WebParamListDict["ascii"] = []  # (Note: ascii not yet implemented in Beremiz modbus plugin)
       
   207 
       
   208 WebParamListDictDict = {}
       
   209 WebParamListDictDict['client'] = _client_WebParamListDict
       
   210 WebParamListDictDict['server'] = _server_WebParamListDict
       
   211 
       
   212 
       
   213 
       
   214 
       
   215 
       
   216 
       
   217 def _SetSavedConfiguration(WebNode_id, newConfig):
       
   218     """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
       
   219     
       
   220     # Add the addr_type and node_type to the data that will be saved to file
       
   221     # This allows us to confirm the saved data contains the correct addr_type
       
   222     # when loading from file
       
   223     save_info = {}
       
   224     save_info["addr_type"] = _WebNodeList[WebNode_id]["addr_type"]
       
   225     save_info["node_type"] = _WebNodeList[WebNode_id]["node_type"]
       
   226     save_info["config"   ] = newConfig
       
   227     
       
   228     filename = _WebNodeList[WebNode_id]["filename"]
       
   229 
       
   230     with open(os.path.realpath(filename), 'w') as f:
       
   231         json.dump(save_info, f, sort_keys=True, indent=4)
       
   232         
       
   233     _WebNodeList[WebNode_id]["SavedConfiguration"] = newConfig
       
   234 
       
   235 
       
   236 
       
   237 
       
   238 def _DelSavedConfiguration(WebNode_id):
       
   239     """ Deletes the file cotaining the persistent Modbus configuration """
       
   240     filename = _WebNodeList[WebNode_id]["filename"]
       
   241     
       
   242     if os.path.exists(filename):
       
   243         os.remove(filename)
       
   244 
       
   245 
       
   246 
       
   247 
       
   248 def _GetSavedConfiguration(WebNode_id):
       
   249     """
       
   250     Returns a dictionary containing the Modbus parameter configuration
       
   251     that was last saved to file. If no file exists, or file contains 
       
   252     wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
       
   253     addr_type of the WebNode_id), then return None
       
   254     """
       
   255     filename = _WebNodeList[WebNode_id]["filename"]
       
   256     try:
       
   257         #if os.path.isfile(filename):
       
   258         save_info = json.load(open(filename))
       
   259     except Exception:    
       
   260         return None
       
   261 
       
   262     if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
       
   263         return None
       
   264     if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
       
   265         return None
       
   266     if "config" not in save_info:
       
   267         return None
       
   268     
       
   269     saved_config = save_info["config"]
       
   270     
       
   271     #if _CheckConfiguration(saved_config):
       
   272     #    return saved_config
       
   273     #else:
       
   274     #    return None
       
   275 
       
   276     return saved_config
       
   277 
       
   278 
       
   279 
       
   280 def _GetPLCConfiguration(WebNode_id):
       
   281     """
       
   282     Returns a dictionary containing the current Modbus parameter configuration
       
   283     stored in the C variables in the loaded PLC (.so file)
       
   284     """
       
   285     current_config = {}
       
   286     C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
       
   287     WebParamList   = _WebNodeList[WebNode_id]["WebParamList"]
       
   288     GetParamFuncs  = _WebNodeList[WebNode_id]["GetParamFuncs"]
       
   289 
       
   290     for par_name, x1, x2, x3 in WebParamList:
       
   291         value = GetParamFuncs[par_name](C_node_id)
       
   292         if value is not None:
       
   293             current_config[par_name] = value
       
   294     
       
   295     return current_config
       
   296 
       
   297 
       
   298 
       
   299 def _SetPLCConfiguration(WebNode_id, newconfig):
       
   300     """
       
   301     Stores the Modbus parameter configuration into the
       
   302     the C variables in the loaded PLC (.so file)
       
   303     """
       
   304     C_node_id      = _WebNodeList[WebNode_id]["C_node_id"]
       
   305     SetParamFuncs  = _WebNodeList[WebNode_id]["SetParamFuncs"]
       
   306 
       
   307     for par_name in newconfig:
       
   308         value = newconfig[par_name]
       
   309         if value is not None:
       
   310             SetParamFuncs[par_name](C_node_id, value)
       
   311             
       
   312 
       
   313 
       
   314 
       
   315 def _GetWebviewConfigurationValue(ctx, WebNode_id, argument):
       
   316     """
       
   317     Callback function, called by the web interface (NevowServer.py)
       
   318     to fill in the default value of each parameter of the web form
       
   319     
       
   320     Note that the real callback function is a dynamically created function that
       
   321     will simply call this function to do the work. It will also pass the WebNode_id 
       
   322     as a parameter.
       
   323     """    
       
   324     try:
       
   325         return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
       
   326     except Exception:
       
   327         return ""
       
   328 
       
   329 
       
   330 
       
   331 
       
   332 def _updateWebInterface(WebNode_id):
       
   333     """
       
   334     Add/Remove buttons to/from the web interface depending on the current state
       
   335        - If there is a saved state => add a delete saved state button
       
   336     """
       
   337 
       
   338     config_hash = _WebNodeList[WebNode_id]["config_hash"]
       
   339     config_name = _WebNodeList[WebNode_id]["config_name"]
       
   340     
       
   341     # Add a "Delete Saved Configuration" button if there is a saved configuration!
       
   342     if _WebNodeList[WebNode_id]["SavedConfiguration"] is None:
       
   343         NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
       
   344     else:
       
   345         def __OnButtonDel(**kwargs):
       
   346             return OnButtonDel(WebNode_id = WebNode_id, **kwargs)
       
   347                 
       
   348         NS.ConfigurableSettings.addSettings(
       
   349             "ModbusConfigDelSaved"      + config_hash,  # name (internal, may not contain spaces, ...)
       
   350             _("Modbus Configuration: ") + config_name,  # description (user visible label)
       
   351             [],                                         # fields  (empty, no parameters required!)
       
   352             _("Delete Configuration Stored in Persistent Storage"), # button label
       
   353             __OnButtonDel,                              # callback    
       
   354             "ModbusConfigParm"          + config_hash)  # Add after entry xxxx
       
   355 
       
   356 
       
   357 
       
   358 def OnButtonSave(**kwargs):
       
   359     """
       
   360     Function called when user clicks 'Save' button in web interface
       
   361     The function will configure the Modbus plugin in the PLC with the values
       
   362     specified in the web interface. However, values must be validated first!
       
   363     
       
   364     Note that this function does not get called directly. The real callback
       
   365     function is the dynamic __OnButtonSave() function, which will add the 
       
   366     "WebNode_id" argument, and call this function to do the work.
       
   367     """
       
   368 
       
   369     #PLCObject.LogMessage("Modbus web server extension::OnButtonSave()  Called")
       
   370     
       
   371     newConfig    = {}
       
   372     WebNode_id   =  kwargs.get("WebNode_id", None)
       
   373     WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
       
   374     
       
   375     for par_name, x1, x2, x3 in WebParamList:
       
   376         value = kwargs.get(par_name, None)
       
   377         if value is not None:
       
   378             newConfig[par_name] = value
       
   379 
       
   380     # First check if configuration is OK.
       
   381     # Note that this is not currently required, as we use drop down choice menus
       
   382     # for baud, parity and sop bits, so the values should always be correct!
       
   383     #if not _CheckWebConfiguration(newConfig):
       
   384     #    return
       
   385     
       
   386     # store to file the new configuration so that 
       
   387     # we can recoup the configuration the next time the PLC
       
   388     # has a cold start (i.e. when Beremiz_service.py is retarted)
       
   389     _SetSavedConfiguration(WebNode_id, newConfig)
       
   390 
       
   391     # Configure PLC with the current Modbus parameters
       
   392     _SetPLCConfiguration(WebNode_id, newConfig)
       
   393 
       
   394     # Update the viewable configuration
       
   395     # The PLC may have coerced the values on calling _SetPLCConfiguration()
       
   396     # so we do not set it directly to newConfig
       
   397     _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
       
   398 
       
   399     # File has just been created => Delete button must be shown on web interface!
       
   400     _updateWebInterface(WebNode_id)
       
   401 
       
   402 
       
   403 
       
   404 
       
   405 def OnButtonDel(**kwargs):
       
   406     """
       
   407     Function called when user clicks 'Delete' button in web interface
       
   408     The function will delete the file containing the persistent
       
   409     Modbus configution
       
   410     """
       
   411 
       
   412     WebNode_id = kwargs.get("WebNode_id", None)
       
   413     
       
   414     # Delete the file
       
   415     _DelSavedConfiguration(WebNode_id)
       
   416 
       
   417     # Set the current configuration to the default (hardcoded in C)
       
   418     new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
       
   419     _SetPLCConfiguration(WebNode_id, new_config)
       
   420     
       
   421     #Update the webviewconfiguration
       
   422     _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
       
   423     
       
   424     # Reset SavedConfiguration
       
   425     _WebNodeList[WebNode_id]["SavedConfiguration"] = None
       
   426     
       
   427     # File has just been deleted => Delete button on web interface no longer needed!
       
   428     _updateWebInterface(WebNode_id)
       
   429 
       
   430 
       
   431 
       
   432 
       
   433 def OnButtonShowCur(**kwargs):
       
   434     """
       
   435     Function called when user clicks 'Show Current PLC Configuration' button in web interface
       
   436     The function will load the current PLC configuration into the web form
       
   437 
       
   438     Note that this function does not get called directly. The real callback
       
   439     function is the dynamic __OnButtonShowCur() function, which will add the 
       
   440     "WebNode_id" argument, and call this function to do the work.
       
   441     """
       
   442     WebNode_id = kwargs.get("WebNode_id", None)
       
   443     
       
   444     _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
       
   445     
       
   446 
       
   447 
       
   448 
       
   449 def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
       
   450     """
       
   451     Load from the compiled code (.so file, aloready loaded into memmory)
       
   452     the configuration parameters of a specific Modbus plugin node.
       
   453     This function works with both client and server nodes, depending on the
       
   454     Get/SetParamFunc dictionaries passed to it (either the client or the server
       
   455     node versions of the Get/Set functions)
       
   456     """
       
   457     WebNode_entry = {}
       
   458 
       
   459     # Get the config_name from the C code...
       
   460     config_name = GetParamFuncs["config_name"](C_node_id)
       
   461     # Get the addr_type from the C code...
       
   462     # addr_type will be one of "tcp", "rtu" or "ascii"
       
   463     addr_type   = GetParamFuncs["addr_type"  ](C_node_id)   
       
   464     # For some operations we cannot use the config name (e.g. filename to store config)
       
   465     # because the user may be using characters that are invalid for that purpose ('/' for
       
   466     # example), so we create a hash of the config_name, and use that instead.
       
   467     config_hash = hashlib.md5(config_name).hexdigest()
       
   468     
       
   469     #PLCObject.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
       
   470 
       
   471     # Add the new entry to the global list
       
   472     # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
       
   473     #       WebNode_entry will be stored as a reference, so we can later insert parameters at will.
       
   474     global _WebNodeList
       
   475     _WebNodeList.append(WebNode_entry)
       
   476     WebNode_id = len(_WebNodeList) - 1
       
   477 
       
   478     # store all WebNode relevant data for future reference
       
   479     #
       
   480     # Note that "WebParamList" will reference one of:
       
   481     #  - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
       
   482     WebNode_entry["C_node_id"    ] = C_node_id
       
   483     WebNode_entry["config_name"  ] = config_name 
       
   484     WebNode_entry["config_hash"  ] = config_hash
       
   485     WebNode_entry["filename"     ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
       
   486     WebNode_entry["GetParamFuncs"] = GetParamFuncs
       
   487     WebNode_entry["SetParamFuncs"] = SetParamFuncs
       
   488     WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type] 
       
   489     WebNode_entry["addr_type"    ] = addr_type  # 'tcp', 'rtu', or 'ascii' (as returned by C function)
       
   490     WebNode_entry["node_type"    ] = node_type  # 'client', 'server'
       
   491         
       
   492     
       
   493     # Dictionary that contains the Modbus configuration currently being shown
       
   494     # on the web interface
       
   495     # This configuration will almost always be identical to the current
       
   496     # configuration in the PLC (i.e., the current state stored in the 
       
   497     # C variables in the .so file).
       
   498     # The configuration viewed on the web will only be different to the current 
       
   499     # configuration when the user edits the configuration, and when
       
   500     # the user asks to save an edited configuration that contains an error.
       
   501     WebNode_entry["WebviewConfiguration"] = None
       
   502 
       
   503     # Upon PLC load, this Dictionary is initialised with the Modbus configuration
       
   504     # hardcoded in the C file
       
   505     # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
       
   506     WebNode_entry["DefaultConfiguration"] = _GetPLCConfiguration(WebNode_id)
       
   507     WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
       
   508     
       
   509     # Dictionary that stores the Modbus configuration currently stored in a file
       
   510     # Currently only used to decide whether or not to show the "Delete" button on the
       
   511     # web interface (only shown if "SavedConfiguration" is not None)
       
   512     SavedConfig = _GetSavedConfiguration(WebNode_id)
       
   513     WebNode_entry["SavedConfiguration"] = SavedConfig
       
   514     
       
   515     if SavedConfig is not None:
       
   516         _SetPLCConfiguration(WebNode_id, SavedConfig)
       
   517         WebNode_entry["WebviewConfiguration"] = SavedConfig
       
   518         
       
   519     # Define the format for the web form used to show/change the current parameters
       
   520     # We first declare a dynamic function to work as callback to obtain the default values for each parameter
       
   521     # Note: We transform every parameter into a string
       
   522     #       This is not strictly required for parameters of type annotate.Integer that will correctly
       
   523     #           accept the default value as an Integer python object
       
   524     #       This is obviously also not required for parameters of type annotate.String, that are
       
   525     #           always handled as strings.
       
   526     #       However, the annotate.Choice parameters (and all parameters that derive from it,
       
   527     #           sucn as Parity, Baud, etc.) require the default value as a string
       
   528     #           even though we store it as an integer, which is the data type expected
       
   529     #           by the set_***() C functions in mb_runtime.c
       
   530     def __GetWebviewConfigurationValue(ctx, argument):
       
   531         return str(_GetWebviewConfigurationValue(ctx, WebNode_id, argument))
       
   532     
       
   533     webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue)) 
       
   534                     for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
       
   535 
       
   536     # Configure the web interface to include the Modbus config parameters
       
   537     def __OnButtonSave(**kwargs):
       
   538         OnButtonSave(WebNode_id=WebNode_id, **kwargs)
       
   539 
       
   540     NS.ConfigurableSettings.addSettings(
       
   541         "ModbusConfigParm"          + config_hash,     # name (internal, may not contain spaces, ...)
       
   542         _("Modbus Configuration: ") + config_name,     # description (user visible label)
       
   543         webFormInterface,                              # fields
       
   544         _("Save Configuration to Persistent Storage"), # button label
       
   545         __OnButtonSave)                                # callback   
       
   546     
       
   547     # Add a "View Current Configuration" button 
       
   548     def __OnButtonShowCur(**kwargs):
       
   549         OnButtonShowCur(WebNode_id=WebNode_id, **kwargs)
       
   550 
       
   551     NS.ConfigurableSettings.addSettings(
       
   552         "ModbusConfigViewCur"       + config_hash, # name (internal, may not contain spaces, ...)
       
   553         _("Modbus Configuration: ") + config_name,     # description (user visible label)
       
   554         [],                                        # fields  (empty, no parameters required!)
       
   555         _("Show Current PLC Configuration"),       # button label
       
   556         __OnButtonShowCur)                         # callback    
       
   557 
       
   558     # Add the Delete button to the web interface, if required
       
   559     _updateWebInterface(WebNode_id)
       
   560 
       
   561 
       
   562 
       
   563 
       
   564 def _runtime_modbus_websettings_%(location_str)s_init():
       
   565     """
       
   566     Callback function, called (by PLCObject.py) when a new PLC program
       
   567     (i.e. XXX.so file) is transfered to the PLC runtime
       
   568     and loaded into memory
       
   569     """
       
   570     print("_runtime_modbus_websettings_init")
       
   571 
       
   572     #PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
       
   573 
       
   574     if PLCObject.PLClibraryHandle is None:
       
   575         # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
       
   576         # Hmm... This shold never occur!! 
       
   577         return  
       
   578     
       
   579     # Get the number of Modbus Client and Servers (Modbus plugin)
       
   580     # configured in the currently loaded PLC project (i.e., the .so file)
       
   581     # If the "__modbus_plugin_client_node_count" 
       
   582     # or the "__modbus_plugin_server_node_count" C variables 
       
   583     # are not present in the .so file we conclude that the currently loaded 
       
   584     # PLC does not have the Modbus plugin included (situation (2b) described above init())
       
   585     try:
       
   586         client_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_client_node_count").value
       
   587         server_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_server_node_count").value
       
   588     except Exception:
       
   589         # Loaded PLC does not have the Modbus plugin => nothing to do
       
   590         #   (i.e. do _not_ configure and make available the Modbus web interface)
       
   591         return
       
   592 
       
   593     if client_count < 0: client_count = 0
       
   594     if server_count < 0: server_count = 0
       
   595     
       
   596     if (client_count == 0) and (server_count == 0):
       
   597         # The Modbus plugin in the loaded PLC does not have any client and servers configured
       
   598         #  => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
       
   599         return
       
   600     
       
   601     # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
       
   602     # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
       
   603     GetClientParamFuncs = {}
       
   604     SetClientParamFuncs = {}
       
   605     GetServerParamFuncs = {}
       
   606     SetServerParamFuncs = {}
       
   607 
       
   608     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
       
   609         ParamFuncName                      = "__modbus_get_ClientNode_" + name        
       
   610         GetClientParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
       
   611         GetClientParamFuncs[name].restype  = c_dtype
       
   612         GetClientParamFuncs[name].argtypes = [ctypes.c_int]
       
   613         
       
   614     for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
       
   615         ParamFuncName                      = "__modbus_set_ClientNode_" + name
       
   616         SetClientParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
       
   617         SetClientParamFuncs[name].restype  = None
       
   618         SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
       
   619 
       
   620     for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
       
   621         ParamFuncName                      = "__modbus_get_ServerNode_" + name        
       
   622         GetServerParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
       
   623         GetServerParamFuncs[name].restype  = c_dtype
       
   624         GetServerParamFuncs[name].argtypes = [ctypes.c_int]
       
   625         
       
   626     for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
       
   627         ParamFuncName                      = "__modbus_set_ServerNode_" + name
       
   628         SetServerParamFuncs[name]          = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
       
   629         SetServerParamFuncs[name].restype  = None
       
   630         SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
       
   631 
       
   632     for node_id in range(client_count):
       
   633         _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
       
   634 
       
   635     for node_id in range(server_count):
       
   636         _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
       
   637 
       
   638 
       
   639 
       
   640 
       
   641 
       
   642 def _runtime_modbus_websettings_%(location_str)s_cleanup():
       
   643     """
       
   644     Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
       
   645     """
       
   646 
       
   647     #PLCObject.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
       
   648     
       
   649     # Delete the Modbus specific web interface extensions
       
   650     # (Safe to ask to delete, even if it has not been added!)
       
   651     global _WebNodeList    
       
   652     for WebNode_entry in _WebNodeList:
       
   653         config_hash = WebNode_entry["config_hash"]
       
   654         NS.ConfigurableSettings.delSettings("ModbusConfigParm"     + config_hash)
       
   655         NS.ConfigurableSettings.delSettings("ModbusConfigViewCur"  + config_hash)  
       
   656         NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)  
       
   657         
       
   658     # Dele all entries...
       
   659     _WebNodeList = []
       
   660