--- 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)