merge
authorMario de Sousa <msousa@fe.up.pt>
Sun, 07 Jun 2020 23:47:32 +0100
changeset 2668 cca3e5d7d6f3
parent 2659 09bac1f52b1e (current diff)
parent 2667 253110ba0fd7 (diff)
child 2669 be233279d179
merge
--- a/modbus/mb_runtime.c	Sat Jun 06 08:43:41 2020 +0100
+++ b/modbus/mb_runtime.c	Sun Jun 07 23:47:32 2020 +0100
@@ -929,7 +929,7 @@
 const char *       __modbus_get_ClientNode_addr_type  (int nodeid)  {return addr_type_str[client_nodes[nodeid].node_address.naf];}
                                                                                                                         
 const char *       __modbus_get_ServerNode_config_name(int nodeid)  {return server_nodes[nodeid].config_name;                    }
-const char *       __modbus_get_ServerNode_host       (int nodeid)  {return server_nodes[nodeid].str1;                           }
+const char *       __modbus_get_ServerNode_host       (int nodeid)  {char*x=server_nodes[nodeid].str1; return (x[0]=='\0'?"#ANY#":x); }
 const char *       __modbus_get_ServerNode_port       (int nodeid)  {return server_nodes[nodeid].str2;                           }
 const char *       __modbus_get_ServerNode_device     (int nodeid)  {return server_nodes[nodeid].str1;                           }
 int                __modbus_get_ServerNode_baud       (int nodeid)  {return server_nodes[nodeid].node_address.addr.rtu.baud;     }
@@ -948,7 +948,8 @@
 void __modbus_set_ClientNode_comm_period(int nodeid, u64          value)  {client_nodes[nodeid].comm_period                     = value;}
                                                                                                                         
 
-void __modbus_set_ServerNode_host       (int nodeid, const char * value)  {__safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
+void __modbus_set_ServerNode_host       (int nodeid, const char * value)  {if (strcmp(value,"#ANY#")==0) value = "";
+                                                                           __safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
 void __modbus_set_ServerNode_port       (int nodeid, const char * value)  {__safe_strcnpy(server_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);}
 void __modbus_set_ServerNode_device     (int nodeid, const char * value)  {__safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);}
 void __modbus_set_ServerNode_baud       (int nodeid, int          value)  {server_nodes[nodeid].node_address.addr.rtu.baud      = value;}
--- a/modbus/mb_utils.py	Sat Jun 06 08:43:41 2020 +0100
+++ b/modbus/mb_utils.py	Sun Jun 07 23:47:32 2020 +0100
@@ -95,7 +95,7 @@
             "Modbus plugin: Invalid Start Address in server memory area node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
     request_dict["count"] = GetCTVal(child, 1)
-    if int(request_dict["count"]) not in xrange(1, 65536):
+    if int(request_dict["count"]) not in xrange(1, 65537):
         self.GetCTRoot().logger.write_error(
             "Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
         return None
--- a/modbus/modbus.py	Sat Jun 06 08:43:41 2020 +0100
+++ b/modbus/modbus.py	Sun Jun 07 23:47:32 2020 +0100
@@ -332,6 +332,10 @@
     def GetNodeCount(self):
         return (1, 0, 0)
 
+    def GetConfigName(self):
+        """ Return the node's Configuration_Name """
+        return self.ModbusTCPclient.getConfiguration_Name()
+
     def CTNGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -405,12 +409,19 @@
     def GetNodeCount(self):
         return (1, 0, 0)
 
-    # Return a list with a single tuple conatining the (location, port number)
-    #     location: location of this node in the configuration tree
+    # Return a list with a single tuple conatining the (location, IP address, port number)
+    #     location   : location of this node in the configuration tree
     #     port number: IP port used by this Modbus/IP server
+    #     IP address : IP address of the network interface on which the server will be listening
+    #                  ("", "*", or "#ANY#" => listening on all interfaces!)
     def GetIPServerPortNumbers(self):
-        port = self.GetParamsAttributes()[0]["children"][1]["value"]
-        return [(self.GetCurrentLocation(), port)]
+        port = self.ModbusServerNode.getLocal_Port_Number()
+        addr = self.ModbusServerNode.getLocal_IP_Address()
+        return [(self.GetCurrentLocation(), addr, port)]
+
+    def GetConfigName(self):
+        """ Return the node's Configuration_Name """
+        return self.ModbusServerNode.getConfiguration_Name()
 
     def CTNGenerate_C(self, buildpath, locations):
         """
@@ -499,6 +510,10 @@
     def GetNodeCount(self):
         return (0, 1, 0)
 
+    def GetConfigName(self):
+        """ Return the node's Configuration_Name """
+        return self.ModbusRTUclient.getConfiguration_Name()
+
     def CTNGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -585,6 +600,10 @@
     def GetNodeCount(self):
         return (0, 1, 0)
 
+    def GetConfigName(self):
+        """ Return the node's Configuration_Name """
+        return self.ModbusRTUslave.getConfiguration_Name()
+
     def CTNGenerate_C(self, buildpath, locations):
         """
         Generate C code
@@ -646,8 +665,7 @@
                 x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount()))
         return total_node_count
 
-    # Return a list with tuples of the (location, port numbers) used by all
-    # the Modbus/IP servers
+    # Return a list with tuples of the (location, port numbers) used by all the Modbus/IP servers
     def GetIPServerPortNumbers(self):
         IPServer_port_numbers = []
         for child in self.IECSortedChildren():
@@ -655,6 +673,13 @@
                 IPServer_port_numbers.extend(child.GetIPServerPortNumbers())
         return IPServer_port_numbers
 
+    # Return a list with tuples of the (location, configuration_name) used by all the Modbus nodes (tcp/rtu, clients/servers)
+    def GetConfigNames(self):
+        Node_Configuration_Names = []
+        for child in self.IECSortedChildren():
+            Node_Configuration_Names.extend([(child.GetCurrentLocation(), child.GetConfigName())])
+        return Node_Configuration_Names
+
     def CTNGenerate_C(self, buildpath, locations):
         # print "#############"
         # print self.__class__
@@ -669,38 +694,61 @@
 
         # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need
         #   total_node_count: (tcp nodes, rtu nodes, ascii nodes)
-        # Also get a list with tuples of (location, IP port numbers) used by all the Modbus/IP server nodes
+        #
+        # Also get a list with tuples of (location, IP address, port number) used by all the Modbus/IP server nodes
         #   This list is later used to search for duplicates in port numbers!
-        #   IPServer_port_numbers = [(location ,IPserver_port_number), ...]
-        #       location: tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
-        # IPserver_port_number: a number (i.e. port number used by the
-        # Modbus/IP server)
+        #   IPServer_port_numbers = [(location, IP address, port number), ...]
+        #       location            : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
+        #       IPserver_port_number: a number (i.e. port number used by the Modbus/IP server)
+        #       IP address          : IP address of the network interface on which the server will be listening
+        #                             ("", "*", or "#ANY#" => listening on all interfaces!)
+        #
+        # Also get a list with tuples of (location, Configuration_Name) used by all the Modbus nodes
+        #   This list is later used to search for duplicates in Configuration Names!
+        #   Node_Configuration_Names = [(location, Configuration_Name), ...]
+        #       location          : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
+        #       Configuration_Name: the "Configuration_Name" string
         total_node_count = (0, 0, 0)
-        IPServer_port_numbers = []
+        IPServer_port_numbers    = []
+        Node_Configuration_Names = []
         for CTNInstance in self.GetCTRoot().IterChildren():
             if CTNInstance.CTNType == "modbus":
-                # ask each modbus plugin instance how many nodes it needs, and
-                # add them all up.
-                total_node_count = tuple(x1 + x2 for x1, x2 in zip(
-                    total_node_count, CTNInstance.GetNodeCount()))
-                IPServer_port_numbers.extend(
-                    CTNInstance.GetIPServerPortNumbers())
+                # ask each modbus plugin instance how many nodes it needs, and add them all up.
+                total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, CTNInstance.GetNodeCount()))
+                IPServer_port_numbers.   extend(CTNInstance.GetIPServerPortNumbers())
+                Node_Configuration_Names.extend(CTNInstance.GetConfigNames        ())
+
+        # Search for use of duplicate Configuration_Names by Modbus nodes
+        # Configuration Names are used by the web server running on the PLC
+        # (more precisely, run by Beremiz_service.py) to identify and allow 
+        # changing the Modbus parameters after the program has been downloaded 
+        # to the PLC (but before it is started)
+        # With clashes in the configuration names, the Modbus nodes will not be
+        # distinguasheble on the web interface!
+        for i in range(0, len(Node_Configuration_Names) - 1):
+            for j in range(i + 1, len(Node_Configuration_Names)):
+                if Node_Configuration_Names[i][1] == Node_Configuration_Names[j][1]:
+                    error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use the same Configuration_Name \"{a3}\".\n").format(
+                                        a1=_lt_to_str(Node_Configuration_Names[i][0]),
+                                        a2=_lt_to_str(Node_Configuration_Names[j][0]),
+                                        a3=Node_Configuration_Names[j][1])
+                    self.FatalError(error_message)
 
         # Search for use of duplicate port numbers by Modbus/IP servers
-        # print IPServer_port_numbers
-        # ..but first define a lambda function to convert a tuple with the config tree location to a nice looking string
-        #   for e.g., convert the tuple (0, 3, 4) to "0.3.4"
-
-        for i in range(0, len(IPServer_port_numbers) - 1):
-            for j in range(i + 1, len(IPServer_port_numbers)):
-                if IPServer_port_numbers[i][1] == IPServer_port_numbers[j][1]:
-                    error_message = _("Error: Modbus/IP Servers %{a1}.x and %{a2}.x use the same port number {a3}.\n").format(
-                                        a1=_lt_to_str(IPServer_port_numbers[i][0]),
-                                        a2=_lt_to_str(IPServer_port_numbers[j][0]),
-                                        a3=IPServer_port_numbers[j][1])
+        # Note: We only consider duplicate port numbers if using the same network interface!
+        i = 0
+        for loc1, addr1, port1 in IPServer_port_numbers[:-1]:
+            i = i + 1
+            for loc2, addr2, port2 in IPServer_port_numbers[i:]:
+                if (port1 == port2) and (
+                          (addr1 == addr2)   # on the same network interface
+                       or (addr1 == "") or (addr1 == "*") or (addr1 == "#ANY#") # or one (or both) of the servers
+                       or (addr2 == "") or (addr2 == "*") or (addr2 == "#ANY#") # use all available network interfaces
+                   ):
+                    error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use same port number \"{a3}\" " + 
+                                      "on the same (or overlapping) network interfaces \"{a4}\" and \"{a5}\".\n").format(
+                                        a1=_lt_to_str(loc1), a2=_lt_to_str(loc2), a3=port1, a4=addr1, a5=addr2)
                     self.FatalError(error_message)
-                    #self.GetCTRoot().logger.write_warning(error_message)
-                    #raise Exception
 
         # Determine the current location in Beremiz's project configuration
         # tree
--- a/runtime/BACnet_config.py	Sat Jun 06 08:43:41 2020 +0100
+++ b/runtime/BACnet_config.py	Sun Jun 07 23:47:32 2020 +0100
@@ -87,6 +87,10 @@
 
 
 
+class BN_StrippedString(annotate.String):
+    def __init__(self, *args, **kwargs):
+        annotate.String.__init__(self, strip = True, *args, **kwargs)
+
 
 
 BACnet_parameters = [
@@ -94,8 +98,8 @@
     # (C code var name)         (used on web interface)                          (C data type)    (web data type)
     #                                                                                             (annotate.String,
     #                                                                                              annotate.Integer, ...)
-    ("network_interface"      , _("Network Interface")                         , ctypes.c_char_p, annotate.String),
-    ("port_number"            , _("UDP Port Number")                           , ctypes.c_char_p, annotate.String),
+    ("network_interface"      , _("Network Interface")                         , ctypes.c_char_p, BN_StrippedString),
+    ("port_number"            , _("UDP Port Number")                           , ctypes.c_char_p, BN_StrippedString),
     ("comm_control_passwd"    , _("BACnet Communication Control Password")     , ctypes.c_char_p, annotate.String),
     ("device_id"              , _("BACnet Device ID")                          , ctypes.c_int,    annotate.Integer),
     ("device_name"            , _("BACnet Device Name")                        , ctypes.c_char_p, annotate.String),
@@ -269,8 +273,8 @@
             _("BACnet Configuration"),                # description
             [],                                       # fields  (empty, no parameters required!)
             _("Delete Configuration Stored in Persistent Storage"), # button label
-            OnButtonDel)                              # callback    
-
+            OnButtonDel,                              # callback    
+            "BACnetConfigParm")                       # Add after entry xxxx
 
 
 def OnButtonSave(**kwargs):
--- a/runtime/Modbus_config.py	Sat Jun 06 08:43:41 2020 +0100
+++ b/runtime/Modbus_config.py	Sun Jun 07 23:47:32 2020 +0100
@@ -74,6 +74,12 @@
 
 
 
+
+class MB_StrippedString(annotate.String):
+    def __init__(self, *args, **kwargs):
+        annotate.String.__init__(self, strip = True, *args, **kwargs)
+
+
 class MB_StopBits(annotate.Choice):
     _choices = [0, 1, 2]
 
@@ -93,6 +99,10 @@
 
 
 class MB_Parity(annotate.Choice):
+    # For more info on what this class really does, have a look at the code in
+    # file twisted/nevow/annotate.py
+    # grab this code from $git clone https://github.com/twisted/nevow/
+    # 
     # Warning: do _not_ name this variable choice[] without underscore, as that name is
     # already used for another similar variable by the underlying class annotate.Choice
     _choices = [  0,      1,      2  ]
@@ -143,14 +153,16 @@
     ("addr_type"        , _("")                      , ctypes.c_char_p,    annotate.String)
     ]                                                                      
                                                                            
+# Parameters we will need to get from the C code, and that _will_ be shown
+# on the web interface.
 TCPclient_parameters = [                                                   
     #    param. name       label                        ctype type         annotate type
     # (C code var name)   (used on web interface)      (C data type)       (web data type)
     #                                                                      (annotate.String,
     #                                                                       annotate.Integer, ...)
-    ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    annotate.String),
-    ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    annotate.String),
-    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer)
+    ("host"             , _("Remote IP Address")     , ctypes.c_char_p,    MB_StrippedString),
+    ("port"             , _("Remote Port Number")    , ctypes.c_char_p,    MB_StrippedString),
+    ("comm_period"      , _("Invocation Rate (ms)")  , ctypes.c_ulonglong, annotate.Integer )
     ]
 
 RTUclient_parameters = [                                                   
@@ -158,7 +170,7 @@
     # (C code var name)   (used on web interface)      (C data type)       (web data type)
     #                                                                      (annotate.String,
     #                                                                       annotate.Integer, ...)
-    ("device"           , _("Serial Port")           , ctypes.c_char_p,    annotate.String ),
+    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
     ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
     ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
@@ -170,9 +182,9 @@
     # (C code var name)   (used on web interface)      (C data type)       (web data type)
     #                                                                      (annotate.String,
     #                                                                       annotate.Integer, ...)
-    ("host"             , _("Local IP Address")      , ctypes.c_char_p,    annotate.String),
-    ("port"             , _("Local Port Number")     , ctypes.c_char_p,    annotate.String),
-    ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer)
+    ("host"             , _("Local IP Address")      , ctypes.c_char_p,    MB_StrippedString),
+    ("port"             , _("Local Port Number")     , ctypes.c_char_p,    MB_StrippedString),
+    ("slave_id"         , _("Slave ID")              , ctypes.c_ubyte,     annotate.Integer )
     ]
 
 RTUslave_parameters = [                                                   
@@ -180,7 +192,7 @@
     # (C code var name)   (used on web interface)      (C data type)       (web data type)
     #                                                                      (annotate.String,
     #                                                                       annotate.Integer, ...)
-    ("device"           , _("Serial Port")           , ctypes.c_char_p,    annotate.String ),
+    ("device"           , _("Serial Port")           , ctypes.c_char_p,    MB_StrippedString),
     ("baud"             , _("Baud Rate")             , ctypes.c_int,       MB_Baud         ),
     ("parity"           , _("Parity")                , ctypes.c_int,       MB_Parity       ),
     ("stop_bits"        , _("Stop Bits")             , ctypes.c_int,       MB_StopBits     ),
@@ -377,8 +389,6 @@
         if value is not None:
             newConfig[par_name] = value
 
-    _WebNodeList[WebNode_id]["WebviewConfiguration"] = newConfig
-    
     # First check if configuration is OK.
     # Note that this is not currently required, as we use drop down choice menus
     # for baud, parity and sop bits, so the values should always be correct!
@@ -393,6 +403,11 @@
     # Configure PLC with the current Modbus parameters
     _SetPLCConfiguration(WebNode_id, newConfig)
 
+    # Update the viewable configuration
+    # The PLC may have coerced the values on calling _SetPLCConfiguration()
+    # so we do not set it directly to newConfig
+    _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
+
     # File has just been created => Delete button must be shown on web interface!
     _updateWebInterface(WebNode_id)