# HG changeset patch # User Mario de Sousa # Date 1591570052 -3600 # Node ID cca3e5d7d6f39f04209559d8fa09859a24cdbeaa # Parent 09bac1f52b1ea4040f46b3df6ece5f7e80347892# Parent 253110ba0fd786c76c979d167295d1540d6ff3bd merge diff -r 09bac1f52b1e -r cca3e5d7d6f3 modbus/mb_runtime.c --- 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;} diff -r 09bac1f52b1e -r cca3e5d7d6f3 modbus/mb_utils.py --- 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 diff -r 09bac1f52b1e -r cca3e5d7d6f3 modbus/modbus.py --- 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 diff -r 09bac1f52b1e -r cca3e5d7d6f3 runtime/BACnet_config.py --- 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): diff -r 09bac1f52b1e -r cca3e5d7d6f3 runtime/Modbus_config.py --- 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)