--- a/modbus/web_settings.py Wed Mar 01 10:54:54 2023 +0100
+++ b/modbus/web_settings.py Fri Mar 10 09:13:29 2023 +0100
@@ -125,67 +125,86 @@
+# The parameter coerce functions referenced in the TCPclient_parameters etc. lists. +# These functions check wether the parameter has a legal value (i.e. they validate the parameter), +# and if not changes it to the closest legal value (i.e. coerces the parameter value to a legal value) +def _coerce_comm_period(value): + # Period may not be negative value +def _coerce_req_delay(value): + # Request delay may not be negative value + # (i.e. delay between any two consecutive requests sent by a Beremiz RTU master or TCP client) # 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)
-# The annotate type entry is basically useless and is completely ignored.
-# We kee that entry so that this list can later be correctly merged with the
+# The annotate type entry is used to tell the web server the type of data entry widget to use. +# (see the file twisted/nevow/formless/annotate.py for list of options) +# Unfortunately an unsigned integer option does not exist. - # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
+ # param. name label ctype type annotate type Coerce + # (C code var name) (used on web interface) (C data type) (web data type) function - ("config_name" , _("") , ctypes.c_char_p, annotate.String),
- ("addr_type" , _("") , ctypes.c_char_p, annotate.String)
+ ("config_name" , _("") , ctypes.c_char_p, annotate.String, None), + ("addr_type" , _("") , ctypes.c_char_p, annotate.String, None) # Parameters we will need to get from the C code, and that _will_ be shown
- # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
+ # param. name label ctype type annotate type Coerce + # (C code var name) (used on web interface) (C data type) (web data type) function - ("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 ),
- ("req_delay" , _("Request Delay (ms)") , ctypes.c_ulonglong, annotate.Integer )
+ ("host" , _("Remote IP Address") , ctypes.c_char_p, MB_StrippedString, None), + ("port" , _("Remote Port Number") , ctypes.c_char_p, MB_StrippedString, None), + ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer , _coerce_comm_period), + ("req_delay" , _("Request Delay (ms)") , ctypes.c_ulonglong, annotate.Integer , _coerce_req_delay) - # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
+ # param. name label ctype type annotate type Coerce + # (C code var name) (used on web interface) (C data type) (web data type) function - ("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 ),
- ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer),
- ("req_delay" , _("Request Delay (ms)") , ctypes.c_ulonglong, annotate.Integer)
+ ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString, None), + ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud , None), + ("parity" , _("Parity") , ctypes.c_int, MB_Parity , None), + ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits , None), + ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer, _coerce_comm_period), + ("req_delay" , _("Request Delay (ms)") , ctypes.c_ulonglong, annotate.Integer, _coerce_req_delay) - # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
+ # param. name label ctype type annotate type Coerce + # (C code var name) (used on web interface) (C data type) (web data type) function - ("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 )
+ ("host" , _("Local IP Address") , ctypes.c_char_p, MB_StrippedString, None), + ("port" , _("Local Port Number") , ctypes.c_char_p, MB_StrippedString, None), + ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer , None) - # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
+ # param. name label ctype type annotate type Coerce + # (C code var name) (used on web interface) (C data type) (web data type) function - ("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 ),
- ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer)
+ ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString, None), + ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud , None), + ("parity" , _("Parity") , ctypes.c_int, MB_Parity , None), + ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits , None), + ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer, None) @@ -213,8 +232,6 @@
def _SetModbusSavedConfiguration(WebNode_id, newConfig):
""" Stores a dictionary in a persistant file containing the Modbus parameter configuration """
WebNode_entry = _WebNodeList[WebNode_id]
@@ -296,7 +313,8 @@
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
- for par_name, x1, x2, x3 in WebParamList:
+ for WebParamListEntry in WebParamList: + par_name = WebParamListEntry[0] value = GetParamFuncs[par_name](C_node_id)
current_config[par_name] = value
@@ -336,6 +354,20 @@
+def _CoerceConfigurationValues(WebNode_id, config): + Coerces any incorrect value in the passed configuration + to the nearest correct value. + For example, negative times in delay paramater are changed to 0, etc... + CoerceParamFuncs = _WebNodeList[WebNode_id]["CoerceParamFuncs"] + for par_name in config: + CoerceFunction = CoerceParamFuncs[par_name] + if (config[par_name] is not None) and (CoerceFunction is not None): + config[par_name] = CoerceFunction(config[par_name]) def OnModbusButtonSave(**kwargs):
@@ -354,16 +386,30 @@
WebNode_id = kwargs.get("WebNode_id", None)
WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
- for par_name, x1, x2, x3 in WebParamList:
+ for WebParamListEntry in WebParamList: + par_name = WebParamListEntry[0] value = kwargs.get(par_name, None)
newConfig[par_name] = value
# 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!
- #if not _CheckWebConfiguration(newConfig):
+ # Note that this is currently not really required for most of the parameters, + # as we use drop down choice menus for baud, parity and sop bits + # (so these values should always be correct!). + # The timing properties (period in ms, delays in ms, etc.) + # do however need to be validated as the web interface does not + # enforce any limits on the values (e.g., they must be positive values). + # NOTE: It would be confusing for the user if we simply refuse to make the + # requested changes without giving any feedback that the changes were not applied, + # and why they were refused (i.e. at the moment the web page does not have + # any pop-up windows or special messages for that). + # That is why instead of returning without making the changes in case of error, + # we instead opt to change the requested configuration to the closest valid values + # and apply that instead. The user gets feedback on the applied values as these get + # shown on the web interface. + # TODO: Check IP address, Port number, etc... + _CoerceConfigurationValues(WebNode_id, newConfig) # store to file the new configuration so that
# we can recoup the configuration the next time the PLC
@@ -382,9 +428,11 @@
def OnModbusButtonReset(**kwargs):
- Function called when user clicks 'Delete' button in web interface
+ Function called when user clicks 'Reset' button in web interface The function will delete the file containing the persistent
+ Modbus configution. More specifically, reset to the configuration + hardcoded in the source code (<=> configuration specified WebNode_id = kwargs.get("WebNode_id", None)
@@ -406,7 +454,7 @@
-def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
+def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs, CoerceParamFuncs): Load from the compiled code (.so file, aloready loaded into memmory)
the configuration parameters of a specific Modbus plugin node.
@@ -443,11 +491,12 @@
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" ] = WebParamListDictDict[node_type][addr_type]
WebNode_entry["addr_type" ] = addr_type # 'tcp', 'rtu', or 'ascii' (as returned by C function)
WebNode_entry["node_type" ] = node_type # 'client', 'server'
+ WebNode_entry["GetParamFuncs"] = GetParamFuncs + WebNode_entry["SetParamFuncs"] = SetParamFuncs + WebNode_entry["CoerceParamFuncs"] = CoerceParamFuncs # Dictionary that contains the Modbus configuration currently being shown
@@ -491,7 +540,7 @@
return str(_GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument))
webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
- for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
+ for name, web_label, c_dtype, web_dtype, coerce_function in WebNode_entry["WebParamList"]] # Configure the web interface to include the Modbus config parameters
def __OnButtonSave(**kwargs):
@@ -569,43 +618,47 @@
# 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)
+ GetServerParamFuncs = {} - GetServerParamFuncs = {}
+ CoerceClientParamFuncs = {} + CoerceServerParamFuncs = {} # XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
- for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
+ for name, web_label, c_dtype, web_dtype, coerce_function in TCPclient_parameters + RTUclient_parameters + General_parameters: ParamFuncName = "__modbus_get_ClientNode_" + name
GetClientParamFuncs[name] = getattr(PLCObject.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:
+ for name, web_label, c_dtype, web_dtype, coerce_function in TCPclient_parameters + RTUclient_parameters: ParamFuncName = "__modbus_set_ClientNode_" + name
SetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetClientParamFuncs[name].restype = None
SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+ CoerceClientParamFuncs[name] = coerce_function # XXX TODO : stop reading from PLC .so file. This code is template code
# that can use modbus extension build data
- for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
+ for name, web_label, c_dtype, web_dtype, coerce_function in TCPserver_parameters + RTUslave_parameters + General_parameters: ParamFuncName = "__modbus_get_ServerNode_" + name
GetServerParamFuncs[name] = getattr(PLCObject.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:
+ for name, web_label, c_dtype, web_dtype, coerce_function in TCPserver_parameters + RTUslave_parameters: ParamFuncName = "__modbus_set_ServerNode_" + name
SetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
SetServerParamFuncs[name].restype = None
SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+ CoerceServerParamFuncs[name] = coerce_function for node_id in range(client_count):
- _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
+ _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs, CoerceClientParamFuncs) for node_id in range(server_count):
- _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
+ _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs, CoerceServerParamFuncs)