--- a/modbus/mb_utils.py Mon Jun 01 08:54:26 2020 +0100
+++ b/modbus/mb_utils.py Mon Jun 01 14:11:22 2020 +0100
@@ -57,14 +57,12 @@
params: child - the correspondent subplugin in Beremiz
"""
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)s", "%(config_name)s", %(slaveid)s, {naf_tcp, {.tcp = {%(host)s, "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}'''
+{"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", %(slaveid)s, {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}'''
location = ".".join(map(str, child.GetCurrentLocation()))
config_name, host, port, slaveid = GetCTVals(child, range(4))
if host == "#ANY#":
- host = 'INADDR_ANY'
- else:
- host = '"' + host + '"'
+ host = ''
# slaveid = GetCTVal(child, 2)
# if int(slaveid) not in xrange(256):
# self.GetCTRoot().logger.write_error("Error: Wrong slave ID in %s server node\nModbus Plugin C code returns empty\n"%location)
@@ -121,7 +119,7 @@
params: child - the correspondent subplugin in Beremiz
"""
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)s", "%(config_name)s", %(slaveid)s, {naf_rtu, {.rtu = {"%(device)s", %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}'''
+{"%(locnodestr)s", "%(config_name)s", "%(device)s", "",%(slaveid)s, {naf_rtu, {.rtu = {NULL, %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}'''
location = ".".join(map(str, child.GetCurrentLocation()))
config_name, device, baud, parity, stopbits, slaveid = GetCTVals(child, range(6))
--- a/runtime/Modbus_config.py Mon Jun 01 08:54:26 2020 +0100
+++ b/runtime/Modbus_config.py Mon Jun 01 14:11:22 2020 +0100
@@ -61,21 +61,21 @@
# Should be a directory that does not get wiped on reboot!
_ModbusConfFiledir = "/tmp"
-# Will contain references to the C functions
-# (implemented in beremiz/modbus/mb_runtime.c)
-# used to get/set the Modbus specific configuration paramters
-GetParamFuncs = {}
-SetParamFuncs = {}
-
-
-# List of all TCP clients configured in the loaded PLC (i.e. the .so file loaded into memory)
-# Each entry will be a dictionary. See _Add_TCP_Client() for the data structure details...
-_TCPclient_list = []
-
-
-
-
-# Paramters we will need to get from the C code, but that will not be shown
+# List of all Web Extension Setting nodes we are handling.
+# One WebNode each for:
+# - Modbus TCP client
+# - Modbus TCP server
+# - Modbus RTU client
+# - Modbus RTU slave
+# configured in the loaded PLC (i.e. the .so file loaded into memory)
+# Each entry will be a dictionary. See _AddWebNode() for the details
+# of the data structure in each entry.
+_WebNodeList = []
+
+
+
+
+# Parameters we will need to get from the C code, but that will not be shown
# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
General_parameters = [
# param. name label ctype type annotate type
@@ -108,15 +108,43 @@
("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer)
]
-
+TCPserver_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" , _("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)
+ ]
+
+RTUslave_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, ...)
+ ("device" , _("Serial Port") , ctypes.c_char_p, annotate.String),
+ ("baud" , _("Baud Rate") , ctypes.c_int, annotate.Integer),
+ ("parity" , _("Parity") , ctypes.c_int, annotate.Integer),
+ ("stop_bits" , _("Stop Bits") , ctypes.c_int, annotate.Integer),
+ ("slave_id" , _("Slave ID") , ctypes.c_ulonglong, annotate.Integer)
+ ]
+
+
+# Dictionary containing List of Web viewable parameters
# Note: the dictionary key must be the same as the string returned by the
# __modbus_get_ClientNode_addr_type()
# __modbus_get_ServerNode_addr_type()
# functions implemented in C (see modbus/mb_runtime.c)
-_client_parameters = {}
-_client_parameters["tcp" ] = TCPclient_parameters
-_client_parameters["rtu" ] = RTUclient_parameters
-_client_parameters["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
+_client_WebParamListDict = {}
+_client_WebParamListDict["tcp" ] = TCPclient_parameters
+_client_WebParamListDict["rtu" ] = RTUclient_parameters
+_client_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
+
+_server_WebParamListDict = {}
+_server_WebParamListDict["tcp" ] = TCPserver_parameters
+_server_WebParamListDict["rtu" ] = RTUslave_parameters
+_server_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
#def _CheckPortnumber(port_number):
@@ -164,22 +192,22 @@
-def _SetSavedConfiguration(node_id, newConfig):
+def _SetSavedConfiguration(WebNode_id, newConfig):
""" Stores a dictionary in a persistant file containing the Modbus parameter configuration """
- filename = _TCPclient_list[node_id]["filename"]
+ filename = _WebNodeList[WebNode_id]["filename"]
with open(os.path.realpath(filename), 'w') as f:
json.dump(newConfig, f, sort_keys=True, indent=4)
- _TCPclient_list[node_id]["SavedConfiguration"] = newConfig
-
-
-
-
-def _DelSavedConfiguration(node_id):
+ _WebNodeList[WebNode_id]["SavedConfiguration"] = newConfig
+
+
+
+
+def _DelSavedConfiguration(WebNode_id):
""" Deletes the file cotaining the persistent Modbus configuration """
- filename = _TCPclient_list[node_id]["filename"]
+ filename = _WebNodeList[WebNode_id]["filename"]
if os.path.exists(filename):
os.remove(filename)
@@ -187,12 +215,12 @@
-def _GetSavedConfiguration(node_id):
+def _GetSavedConfiguration(WebNode_id):
"""
Returns a dictionary containing the Modbus parameter configuration
that was last saved to file. If no file exists, then return None
"""
- filename = _TCPclient_list[node_id]["filename"]
+ filename = _WebNodeList[WebNode_id]["filename"]
try:
#if os.path.isfile(filename):
saved_config = json.load(open(filename))
@@ -208,16 +236,18 @@
-def _GetPLCConfiguration(node_id):
+def _GetPLCConfiguration(WebNode_id):
"""
Returns a dictionary containing the current Modbus parameter configuration
stored in the C variables in the loaded PLC (.so file)
"""
current_config = {}
- addr_type = _TCPclient_list[node_id]["addr_type"]
-
- for par_name, x1, x2, x3 in _client_parameters[addr_type]:
- value = GetParamFuncs[par_name](node_id)
+ C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
+ WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
+ GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
+
+ for par_name, x1, x2, x3 in WebParamList:
+ value = GetParamFuncs[par_name](C_node_id)
if value is not None:
current_config[par_name] = value
@@ -225,53 +255,54 @@
-def _SetPLCConfiguration(node_id, newconfig):
+def _SetPLCConfiguration(WebNode_id, newconfig):
"""
Stores the Modbus parameter configuration into the
the C variables in the loaded PLC (.so file)
"""
- addr_type = _TCPclient_list[node_id]["addr_type"]
-
+ C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
+ SetParamFuncs = _WebNodeList[WebNode_id]["SetParamFuncs"]
+
for par_name in newconfig:
value = newconfig[par_name]
if value is not None:
- SetParamFuncs[par_name](node_id, value)
+ SetParamFuncs[par_name](C_node_id, value)
-def _GetWebviewConfigurationValue(ctx, node_id, argument):
+def _GetWebviewConfigurationValue(ctx, WebNode_id, argument):
"""
Callback function, called by the web interface (NevowServer.py)
to fill in the default value of each parameter of the web form
Note that the real callback function is a dynamically created function that
- will simply call this function to do the work. It will also pass the node_id
+ will simply call this function to do the work. It will also pass the WebNode_id
as a parameter.
- """
+ """
try:
- return _TCPclient_list[node_id]["WebviewConfiguration"][argument.name]
+ return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
except Exception:
return ""
-def _updateWebInterface(node_id):
+def _updateWebInterface(WebNode_id):
"""
Add/Remove buttons to/from the web interface depending on the current state
- If there is a saved state => add a delete saved state button
"""
- config_hash = _TCPclient_list[node_id]["config_hash"]
- config_name = _TCPclient_list[node_id]["config_name"]
+ config_hash = _WebNodeList[WebNode_id]["config_hash"]
+ config_name = _WebNodeList[WebNode_id]["config_name"]
# Add a "Delete Saved Configuration" button if there is a saved configuration!
- if _TCPclient_list[node_id]["SavedConfiguration"] is None:
+ if _WebNodeList[WebNode_id]["SavedConfiguration"] is None:
_NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
else:
def __OnButtonDel(**kwargs):
- return OnButtonDel(node_id = node_id, **kwargs)
+ return OnButtonDel(WebNode_id = WebNode_id, **kwargs)
_NS.ConfigurableSettings.addSettings(
"ModbusConfigDelSaved" + config_hash, # name (internal, may not contain spaces, ...)
@@ -291,21 +322,21 @@
Note that this function does not get called directly. The real callback
function is the dynamic __OnButtonSave() function, which will add the
- "node_id" argument, and call this function to do the work.
+ "WebNode_id" argument, and call this function to do the work.
"""
#_plcobj.LogMessage("Modbus web server extension::OnButtonSave() Called")
- newConfig = {}
- node_id = kwargs.get("node_id", None)
- addr_type = _TCPclient_list[node_id]["addr_type"]
-
- for par_name, x1, x2, x3 in _client_parameters[addr_type]:
+ newConfig = {}
+ WebNode_id = kwargs.get("WebNode_id", None)
+ WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
+
+ for par_name, x1, x2, x3 in WebParamList:
value = kwargs.get(par_name, None)
if value is not None:
newConfig[par_name] = value
- _TCPclient_list[node_id]["WebviewConfiguration"] = newConfig
+ _WebNodeList[WebNode_id]["WebviewConfiguration"] = newConfig
# First check if configuration is OK.
## TODO...
@@ -315,13 +346,13 @@
# store to file the new configuration so that
# we can recoup the configuration the next time the PLC
# has a cold start (i.e. when Beremiz_service.py is retarted)
- _SetSavedConfiguration(node_id, newConfig)
+ _SetSavedConfiguration(WebNode_id, newConfig)
# Configure PLC with the current Modbus parameters
- _SetPLCConfiguration(node_id, newConfig)
+ _SetPLCConfiguration(WebNode_id, newConfig)
# File has just been created => Delete button must be shown on web interface!
- _updateWebInterface(node_id)
+ _updateWebInterface(WebNode_id)
@@ -333,23 +364,23 @@
Modbus configution
"""
- node_id = kwargs.get("node_id", None)
+ WebNode_id = kwargs.get("WebNode_id", None)
# Delete the file
- _DelSavedConfiguration(node_id)
+ _DelSavedConfiguration(WebNode_id)
# Set the current configuration to the default (hardcoded in C)
- new_config = _TCPclient_list[node_id]["DefaultConfiguration"]
- _SetPLCConfiguration(node_id, new_config)
+ new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
+ _SetPLCConfiguration(WebNode_id, new_config)
#Update the webviewconfiguration
- _TCPclient_list[node_id]["WebviewConfiguration"] = new_config
+ _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
# Reset SavedConfiguration
- _TCPclient_list[node_id]["SavedConfiguration"] = None
+ _WebNodeList[WebNode_id]["SavedConfiguration"] = None
# File has just been deleted => Delete button on web interface no longer needed!
- _updateWebInterface(node_id)
+ _updateWebInterface(WebNode_id)
@@ -361,40 +392,53 @@
Note that this function does not get called directly. The real callback
function is the dynamic __OnButtonShowCur() function, which will add the
- "node_id" argument, and call this function to do the work.
- """
- node_id = kwargs.get("node_id", None)
-
- _TCPclient_list[node_id]["WebviewConfiguration"] = _GetPLCConfiguration(node_id)
-
-
-
-
-def _Load_TCP_Client(node_id):
- TCPclient_entry = {}
-
- config_name = GetParamFuncs["config_name"](node_id)
+ "WebNode_id" argument, and call this function to do the work.
+ """
+ WebNode_id = kwargs.get("WebNode_id", None)
+
+ _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
+
+
+
+
+def _AddWebNode(C_node_id, WebParamListDict, GetParamFuncs, SetParamFuncs):
+ """
+ Load from the compiled code (.so file, aloready loaded into memmory)
+ the configuration parameters of a specific Modbus plugin node.
+ This function works with both client and server nodes, depending on the
+ Get/SetParamFunc dictionaries passed to it (either the client or the server
+ node versions of the Get/Set functions)
+ """
+ WebNode_entry = {}
+
+ config_name = GetParamFuncs["config_name"](C_node_id)
# addr_type will be one of "tcp", "rtu" or "ascii"
- addr_type = GetParamFuncs["addr_type" ](node_id)
+ addr_type = GetParamFuncs["addr_type" ](C_node_id)
# For some operations we cannot use the config name (e.g. filename to store config)
# because the user may be using characters that are invalid for that purpose ('/' for
# example), so we create a hash of the config_name, and use that instead.
config_hash = hashlib.md5(config_name).hexdigest()
- _plcobj.LogMessage("Modbus web server extension::_Load_TCP_Client("+str(node_id)+") config_name="+config_name)
+ #_plcobj.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
# Add the new entry to the global list
- # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in TCPclient_entry
- # TCPclient_entry will be stored as a reference, so we can insert parameters at will.
- global _TCPclient_list
- _TCPclient_list.append(TCPclient_entry)
-
- # store all node_id relevant data for future reference
- TCPclient_entry["node_id" ] = node_id
- TCPclient_entry["config_name" ] = config_name
- TCPclient_entry["addr_type" ] = addr_type
- TCPclient_entry["config_hash" ] = config_hash
- TCPclient_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
+ # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
+ # WebNode_entry will be stored as a reference, so we can later insert parameters at will.
+ global _WebNodeList
+ _WebNodeList.append(WebNode_entry)
+ WebNode_id = len(_WebNodeList) - 1
+
+ # store all WebNode relevant data for future reference
+ #
+ # Note that "WebParamList" will reference one of:
+ # - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
+ WebNode_entry["C_node_id" ] = C_node_id
+ WebNode_entry["config_name" ] = config_name
+ WebNode_entry["config_hash" ] = config_hash
+ WebNode_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
+ WebNode_entry["GetParamFuncs"] = GetParamFuncs
+ WebNode_entry["SetParamFuncs"] = SetParamFuncs
+ WebNode_entry["WebParamList" ] = WebParamListDict[addr_type]
# Dictionary that contains the Modbus configuration currently being shown
# on the web interface
@@ -404,35 +448,35 @@
# The configuration viewed on the web will only be different to the current
# configuration when the user edits the configuration, and when
# the user asks to save an edited configuration that contains an error.
- TCPclient_entry["WebviewConfiguration"] = None
+ WebNode_entry["WebviewConfiguration"] = None
# Upon PLC load, this Dictionary is initialised with the Modbus configuration
# hardcoded in the C file
# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
- TCPclient_entry["DefaultConfiguration"] = _GetPLCConfiguration(node_id)
- TCPclient_entry["WebviewConfiguration"] = TCPclient_entry["DefaultConfiguration"]
+ WebNode_entry["DefaultConfiguration"] = _GetPLCConfiguration(WebNode_id)
+ WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
# Dictionary that stores the Modbus configuration currently stored in a file
# Currently only used to decide whether or not to show the "Delete" button on the
- # web interface (only shown if _SavedConfiguration is not None)
- SavedConfig = _GetSavedConfiguration(node_id)
- TCPclient_entry["SavedConfiguration"] = SavedConfig
+ # web interface (only shown if "SavedConfiguration" is not None)
+ SavedConfig = _GetSavedConfiguration(WebNode_id)
+ WebNode_entry["SavedConfiguration"] = SavedConfig
if SavedConfig is not None:
- _SetPLCConfiguration(node_id, SavedConfig)
- TCPclient_entry["WebviewConfiguration"] = SavedConfig
+ _SetPLCConfiguration(WebNode_id, SavedConfig)
+ WebNode_entry["WebviewConfiguration"] = SavedConfig
# Define the format for the web form used to show/change the current parameters
# We first declare a dynamic function to work as callback to obtain the default values for each parameter
def __GetWebviewConfigurationValue(ctx, argument):
- return _GetWebviewConfigurationValue(ctx, node_id, argument)
+ return _GetWebviewConfigurationValue(ctx, WebNode_id, argument)
webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
- for name, web_label, c_dtype, web_dtype in _client_parameters[addr_type]]
+ for name, web_label, c_dtype, web_dtype in WebParamListDict[addr_type]]
# Configure the web interface to include the Modbus config parameters
def __OnButtonSave(**kwargs):
- OnButtonSave(node_id=node_id, **kwargs)
+ OnButtonSave(WebNode_id=WebNode_id, **kwargs)
_NS.ConfigurableSettings.addSettings(
"ModbusConfigParm" + config_hash, # name (internal, may not contain spaces, ...)
@@ -443,7 +487,7 @@
# Add a "View Current Configuration" button
def __OnButtonShowCur(**kwargs):
- OnButtonShowCur(node_id=node_id, **kwargs)
+ OnButtonShowCur(WebNode_id=WebNode_id, **kwargs)
_NS.ConfigurableSettings.addSettings(
"ModbusConfigViewCur" + config_hash, # name (internal, may not contain spaces, ...)
@@ -453,7 +497,8 @@
__OnButtonShowCur) # callback
# Add the Delete button to the web interface, if required
- _updateWebInterface(node_id)
+ _updateWebInterface(WebNode_id)
+
@@ -495,21 +540,41 @@
return
# Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
+ # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
+ GetClientParamFuncs = {}
+ SetClientParamFuncs = {}
+ GetServerParamFuncs = {}
+ SetServerParamFuncs = {}
+
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
- GetParamFuncName = "__modbus_get_ClientNode_" + name
- GetParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, GetParamFuncName)
- GetParamFuncs[name].restype = c_dtype
- GetParamFuncs[name].argtypes = [ctypes.c_int]
+ ParamFuncName = "__modbus_get_ClientNode_" + name
+ GetClientParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
+ GetClientParamFuncs[name].restype = c_dtype
+ GetClientParamFuncs[name].argtypes = [ctypes.c_int]
for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
- SetParamFuncName = "__modbus_set_ClientNode_" + name
- SetParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, SetParamFuncName)
- SetParamFuncs[name].restype = None
- SetParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+ ParamFuncName = "__modbus_set_ClientNode_" + name
+ SetClientParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
+ SetClientParamFuncs[name].restype = None
+ SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+
+ for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
+ ParamFuncName = "__modbus_get_ServerNode_" + name
+ GetServerParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
+ GetServerParamFuncs[name].restype = c_dtype
+ GetServerParamFuncs[name].argtypes = [ctypes.c_int]
+
+ for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
+ ParamFuncName = "__modbus_set_ServerNode_" + name
+ SetServerParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
+ SetServerParamFuncs[name].restype = None
+ SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
for node_id in range(client_count):
- _Load_TCP_Client(node_id)
-
+ _AddWebNode(node_id, _client_WebParamListDict ,GetClientParamFuncs, SetClientParamFuncs)
+
+ for node_id in range(server_count):
+ _AddWebNode(node_id, _server_WebParamListDict, GetServerParamFuncs, SetServerParamFuncs)
@@ -517,22 +582,22 @@
def OnUnLoadPLC():
"""
- # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
+ Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
"""
#_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
# Delete the Modbus specific web interface extensions
# (Safe to ask to delete, even if it has not been added!)
- global _TCPclient_list
- for TCPclient_entry in _TCPclient_list:
- config_hash = TCPclient_entry["config_hash"]
+ global _WebNodeList
+ for WebNode_entry in _WebNodeList:
+ config_hash = WebNode_entry["config_hash"]
_NS.ConfigurableSettings.delSettings("ModbusConfigParm" + config_hash)
_NS.ConfigurableSettings.delSettings("ModbusConfigViewCur" + config_hash)
_NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
# Dele all entries...
- _TCPclient_list = []
+ _WebNodeList = []