--- a/Beremiz_service.py Thu May 28 11:16:59 2020 +0100
+++ b/Beremiz_service.py Sat Jun 06 08:43:41 2020 +0100
@@ -493,6 +493,7 @@
installThreadExcepthook()
@@ -512,7 +513,16 @@
LogMessageAndException(_("BACnet configuration web interface - import failed :"))
+ # Try to add support for Modbus configuration via web server interface + # NOTE:Modbus web config only makes sense if web server is available + if webport is not None: + import runtime.Modbus_config as MBconf + LogMessageAndException(_("Modbus configuration web interface - import failed :")) import runtime.WampClient as WC # pylint: disable=ungrouped-imports
WC.WorkingDir = WorkingDir
@@ -557,6 +567,12 @@
LogMessageAndException(_("BACnet web configuration failed startup. "))
+ MBconf.init(plcobj, NS, WorkingDir) + LogMessageAndException(_("Modbus web configuration failed startup. ")) --- a/modbus/mb_runtime.c Thu May 28 11:16:59 2020 +0100
+++ b/modbus/mb_runtime.c Sat Jun 06 08:43:41 2020 +0100
@@ -493,14 +493,37 @@
int __init_%(locstr)s (int argc, char **argv){
- for (index=0; index < NUMBER_OF_CLIENT_NODES;index++)
+ for (index=0; index < NUMBER_OF_CLIENT_NODES;index++) { client_nodes[index].mb_nd = -1;
- for (index=0; index < NUMBER_OF_SERVER_NODES;index++)
+ /* see comment in mb_runtime.h to understad why we need to initialize these entries */ + switch (client_nodes[index].node_address.naf) { + client_nodes[index].node_address.addr.tcp.host = client_nodes[index].str1; + client_nodes[index].node_address.addr.tcp.service = client_nodes[index].str2; + client_nodes[index].node_address.addr.rtu.device = client_nodes[index].str1; + for (index=0; index < NUMBER_OF_SERVER_NODES;index++) { // mb_nd with negative numbers indicate how far it has been initialised (or not)
// -2 --> no modbus node created; no thread created
// -1 --> modbus node created!; no thread created
// >=0 --> modbus node created!; thread created!
server_nodes[index].mb_nd = -2;
+ /* see comment in mb_runtime.h to understad why we need to initialize these entries */ + switch (server_nodes[index].node_address.naf) { + server_nodes[index].node_address.addr.tcp.host = server_nodes[index].str1; + server_nodes[index].node_address.addr.tcp.service = server_nodes[index].str2; + server_nodes[index].node_address.addr.rtu.device = server_nodes[index].str1; /* modbus library init */
/* Note that TOTAL_xxxNODE_COUNT are the nodes required by _ALL_ the instances of the modbus
@@ -815,3 +838,121 @@
+/**********************************************/ +/** Functions for Beremiz web interface. **/ +/**********************************************/ + * Beremiz has a program to run on the PLC (Beremiz_service.py) + * to handle downloading of compiled programs, start/stop of PLC, etc. + * (see runtime/PLCObject.py for start/stop, loading, ...) + * This service also includes a web server to access PLC state (start/stop) + * and to change some basic confiuration parameters. + * (see runtime/NevowServer.py for the web server) + * The web server allows for extensions, where additional configuration + * parameters may be changed on the running/downloaded PLC. + * Modbus plugin also comes with an extension to the web server, through + * which the basic Modbus plugin configuration parameters may be changed + * These parameters are changed _after_ the code (.so file) is loaded into + * memmory. These changes may be applied before (or after) the code starts + * running (i.e. before or after __init_() ets called)! + * The following functions are never called from other C code. They are + * called instead from the python code in runtime/Modbus_config.py, that + * implements the web server extension for configuring Modbus parameters. +/* The number of Cient nodes (i.e. the number of entries in the client_nodes array) + * The number of Server nodes (i.e. the numb. of entries in the server_nodes array) + * These variables are also used by the Modbus web config code to determine + * whether the current loaded PLC includes the Modbus plugin + * (so it should make the Modbus parameter web interface visible to the user). +const int __modbus_plugin_client_node_count = NUMBER_OF_CLIENT_NODES; +const int __modbus_plugin_server_node_count = NUMBER_OF_SERVER_NODES; +const int __modbus_plugin_param_string_size = MODBUS_PARAM_STRING_SIZE; +/* NOTE: We could have the python code in runtime/Modbus_config.py + * directly access the server_node_t and client_node_t structures, + * however this would create a tight coupling between these two + * disjoint pieces of code. + * Any change to the server_node_t or client_node_t structures would + * require the python code to be changed accordingly. I have therefore + * opted to create get/set functions, one for each parameter. + * We also convert the enumerated constants naf_ascii, etc... + * (from node_addr_family_t in modbus/mb_addr.h) + * into strings so as to decouple the python code that will be calling + * these functions from the Modbus library code definitions. +const char *addr_type_str[] = { +#define __safe_strcnpy(str_dest, str_orig, max_size) { \ + strncpy(str_dest, str_orig, max_size); \ + str_dest[max_size - 1] = '\0'; \ +/* NOTE: The host, port and device parameters are strings that may be changed + * (by calling the following functions) after loading the compiled code + * (.so file) into memory, but before the code starts running + * (i.e. before __init_() gets called). + * This means that the host, port and device parameters may be changed + * _before_ they get mapped onto the str1 and str2 variables by __init_(), + * which is why the following functions must access the str1 and str2 +const char * __modbus_get_ClientNode_config_name(int nodeid) {return client_nodes[nodeid].config_name; } +const char * __modbus_get_ClientNode_host (int nodeid) {return client_nodes[nodeid].str1; } +const char * __modbus_get_ClientNode_port (int nodeid) {return client_nodes[nodeid].str2; } +const char * __modbus_get_ClientNode_device (int nodeid) {return client_nodes[nodeid].str1; } +int __modbus_get_ClientNode_baud (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.baud; } +int __modbus_get_ClientNode_parity (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.parity; } +int __modbus_get_ClientNode_stop_bits (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.stop_bits;} +u64 __modbus_get_ClientNode_comm_period(int nodeid) {return client_nodes[nodeid].comm_period; } +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_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; } +int __modbus_get_ServerNode_parity (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.parity; } +int __modbus_get_ServerNode_stop_bits (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.stop_bits;} +u8 __modbus_get_ServerNode_slave_id (int nodeid) {return server_nodes[nodeid].slave_id; } +const char * __modbus_get_ServerNode_addr_type (int nodeid) {return addr_type_str[server_nodes[nodeid].node_address.naf];} +void __modbus_set_ClientNode_host (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} +void __modbus_set_ClientNode_port (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);} +void __modbus_set_ClientNode_device (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} +void __modbus_set_ClientNode_baud (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.baud = value;} +void __modbus_set_ClientNode_parity (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.parity = value;} +void __modbus_set_ClientNode_stop_bits (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.stop_bits = value;} +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_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;} +void __modbus_set_ServerNode_parity (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.parity = value;} +void __modbus_set_ServerNode_stop_bits (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.stop_bits = value;} +void __modbus_set_ServerNode_slave_id (int nodeid, u8 value) {server_nodes[nodeid].slave_id = value;} --- a/modbus/mb_runtime.h Thu May 28 11:16:59 2020 +0100
+++ b/modbus/mb_runtime.h Sat Jun 06 08:43:41 2020 +0100
@@ -30,6 +30,10 @@
#define DEF_REQ_SEND_RETRIES 0
+#define MODBUS_PARAM_STRING_SIZE 64 // Used by the Modbus server node
#define MEM_AREA_SIZE 65536
@@ -39,8 +43,48 @@
u16 rw_words[MEM_AREA_SIZE];
+ * Beremiz has a program to run on the PLC (Beremiz_service.py) + * to handle downloading of compiled programs, start/stop of PLC, etc. + * (see runtime/PLCObject.py for start/stop, loading, ...) + * This service also includes a web server to access PLC state (start/stop) + * and to change some basic confiuration parameters. + * (see runtime/NevowServer.py for the web server) + * The web server allows for extensions, where additional configuration + * parameters may be changed on the running/downloaded PLC. + * Modbus plugin also comes with an extension to the web server, through + * which the basic Modbus plugin configuration parameters may be changed + * This means that most values in the server_node_t and client_node_t + * may be changed after the co,piled code (.so file) is loaded into + * memory, and before the code starts executing. + * Since the we will also want to change the host and port (TCP) and the + * serial device (RTU) at this time, it is best if we allocate memory for + * these strings that may be overwritten by the web server (i.e., do not use + * const strings) in the server_node_t and client_node_t structures. + * The following structure members + * - node_addr_t.addr.tcp.host + * - node_addr_t.addr.tcp.service (i.e. the port) + * - node_addr_t.addr.rtu.device + * are all char *, and do not allocate memory for the strings. + * We therefore include two generic char arrays, str1 and str2, + * that will store the above strings, and the C code will initiliaze + * the node_addre_t.addr string pointers to these strings. + * i.e., either addr.rtu.device will point to str1, + * addr.tcp.host and addr.tcp.service + * will point to str1 and str2 respectively + const char *config_name; + char str1[MODBUS_PARAM_STRING_SIZE]; + char str2[MODBUS_PARAM_STRING_SIZE]; node_addr_t node_address;
int mb_nd; // modbus library node used for this server
@@ -53,6 +97,9 @@
// Used by the Modbus client node
+ const char *config_name; + char str1[MODBUS_PARAM_STRING_SIZE]; + char str2[MODBUS_PARAM_STRING_SIZE]; node_addr_t node_address;
int mb_nd; // modbus library node used for this client
int init_state; // store how far along the client's initialization has progressed
--- a/modbus/mb_utils.py Thu May 28 11:16:59 2020 +0100
+++ b/modbus/mb_utils.py Sat Jun 06 08:43:41 2020 +0100
@@ -57,20 +57,19 @@
params: child - the correspondent subplugin in Beremiz
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)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()))
- host, port, slaveid = GetCTVals(child, range(3))
+ config_name, host, port, slaveid = GetCTVals(child, range(4))
- 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)
node_dict = {"locnodestr": location,
+ "config_name": config_name, @@ -120,12 +119,13 @@
params: child - the correspondent subplugin in Beremiz
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)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()))
- device, baud, parity, stopbits, slaveid = GetCTVals(child, range(5))
+ config_name, device, baud, parity, stopbits, slaveid = GetCTVals(child, range(6)) node_dict = {"locnodestr": location,
+ "config_name": config_name, "parity": modbus_serial_parity_dict[parity],
@@ -140,12 +140,13 @@
params: child - the correspondent subplugin in Beremiz
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)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 */, %(coms_period)s /* communication period */}'''
+{"%(locnodestr)s", "%(config_name)s", "%(device)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 */, %(coms_period)s /* communication period */}''' location = ".".join(map(str, child.GetCurrentLocation()))
- device, baud, parity, stopbits, coms_period = GetCTVals(child, range(5))
+ config_name, device, baud, parity, stopbits, coms_period = GetCTVals(child, range(6)) node_dict = {"locnodestr": location,
+ "config_name": config_name, "parity": modbus_serial_parity_dict[parity],
@@ -160,12 +161,13 @@
params: child - the correspondent subplugin in Beremiz
node_init_template = '''/*node %(locnodestr)s*/
-{"%(locnodestr)s", {naf_tcp, {.tcp = {"%(host)s", "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */, 0 /* prev_error */}'''
+{"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */, 0 /* prev_error */}''' location = ".".join(map(str, child.GetCurrentLocation()))
- host, port, coms_period = GetCTVals(child, range(3))
+ config_name, host, port, coms_period = GetCTVals(child, range(4)) node_dict = {"locnodestr": location,
+ "config_name": config_name, "coms_period": coms_period}
@@ -184,7 +186,7 @@
req_init_template = '''/*request %(locreqstr)s*/
{"%(locreqstr)s", %(nodeid)s, %(slaveid)s, %(iotype)s, %(func_nr)s, %(address)s , %(count)s,
-DEF_REQ_SEND_RETRIES, 0 /* error_code */, 0 /* prev_code */, {%(timeout_s)d, %(timeout_ns)d} /* timeout */, %(write_on_change)d /* write_on_change */,
+DEF_REQ_SEND_RETRIES, 0 /* error_code */, 0 /* prev_code */, {%(timeout_s)d, %(timeout_ns)d} /* timeout */, %(write_on_change)d /* write_on_change */, {%(buffer)s}, {%(buffer)s}}'''
timeout = int(GetCTVal(child, 4))
--- a/modbus/modbus.py Thu May 28 11:16:59 2020 +0100
+++ b/modbus/modbus.py Sat Jun 06 08:43:41 2020 +0100
@@ -288,6 +288,7 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="ModbusTCPclient">
+ <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/> <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/>
<xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/>
<xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
@@ -308,6 +309,24 @@
# TODO: Replace with CTNType !!!
PlugType = "ModbusTCPclient"
+ # The ModbusTCPclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers() + # It will be an XML parser object created by + # GenerateParserFromXSDstring(self.XSD).CreateRoot() + # Set the default value for the "Configuration_Name" parameter + # The default value will need to be different for each instance of the + # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above + # This value will be used by the web interface + # (i.e. the extension to the web server used to configure the Modbus parameters). + # (The web server is run/activated/started by Beremiz_service.py) + # (The web server code is found in runtime/NevowServer.py) + # (The Modbus extension to the web server is found in runtime/Modbus_config.py) + loc_str = ".".join(map(str, self.GetCurrentLocation())) + self.ModbusTCPclient.setConfiguration_Name("Modbus TCP Client " + loc_str) # Return the number of (modbus library) nodes this specific TCP client will need
# return type: (tcp nodes, rtu nodes, ascii nodes)
@@ -345,6 +364,7 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="ModbusServerNode">
+ <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/> <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional" default="#ANY#"/>
<xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/>
<xsd:attribute name="SlaveID" use="optional" default="0">
@@ -363,6 +383,23 @@
# TODO: Replace with CTNType !!!
PlugType = "ModbusTCPserver"
+ # The ModbusServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers() + # It will be an XML parser object created by + # GenerateParserFromXSDstring(self.XSD).CreateRoot() + # Set the default value for the "Configuration_Name" parameter + # The default value will need to be different for each instance of the + # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above + # This value will be used by the web interface + # (i.e. the extension to the web server used to configure the Modbus parameters). + # (The web server is run/activated/started by Beremiz_service.py) + # (The web server code is found in runtime/NevowServer.py) + # (The Modbus extension to the web server is found in runtime/Modbus_config.py) + loc_str = ".".join(map(str, self.GetCurrentLocation())) + self.ModbusServerNode.setConfiguration_Name("Modbus TCP Server " + loc_str) # Return the number of (modbus library) nodes this specific TCP server will need
# return type: (tcp nodes, rtu nodes, ascii nodes)
@@ -404,6 +441,7 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="ModbusRTUclient">
+ <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/> <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/>
<xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/>
<xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/>
@@ -426,6 +464,23 @@
# TODO: Replace with CTNType !!!
PlugType = "ModbusRTUclient"
+ # The ModbusRTUclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers() + # It will be an XML parser object created by + # GenerateParserFromXSDstring(self.XSD).CreateRoot() + # Set the default value for the "Configuration_Name" parameter + # The default value will need to be different for each instance of the + # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above + # This value will be used by the web interface + # (i.e. the extension to the web server used to configure the Modbus parameters). + # (The web server is run/activated/started by Beremiz_service.py) + # (The web server code is found in runtime/NevowServer.py) + # (The Modbus extension to the web server is found in runtime/Modbus_config.py) + loc_str = ".".join(map(str, self.GetCurrentLocation())) + self.ModbusRTUclient.setConfiguration_Name("Modbus RTU Client " + loc_str) def GetParamsAttributes(self, path=None):
infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
@@ -474,6 +529,7 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="ModbusRTUslave">
+ <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/> <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/>
<xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/>
<xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/>
@@ -494,6 +550,23 @@
# TODO: Replace with CTNType !!!
PlugType = "ModbusRTUslave"
+ # The ModbusRTUslave attribute is added dynamically by ConfigTreeNode._AddParamsMembers() + # It will be an XML parser object created by + # GenerateParserFromXSDstring(self.XSD).CreateRoot() + # Set the default value for the "Configuration_Name" parameter + # The default value will need to be different for each instance of the + # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above + # This value will be used by the web interface + # (i.e. the extension to the web server used to configure the Modbus parameters). + # (The web server is run/activated/started by Beremiz_service.py) + # (The web server code is found in runtime/NevowServer.py) + # (The Modbus extension to the web server is found in runtime/Modbus_config.py) + loc_str = ".".join(map(str, self.GetCurrentLocation())) + self.ModbusRTUslave.setConfiguration_Name("Modbus RTU Slave " + loc_str) def GetParamsAttributes(self, path=None):
infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
--- a/runtime/NevowServer.py Thu May 28 11:16:59 2020 +0100
+++ b/runtime/NevowServer.py Sat Jun 06 08:43:41 2020 +0100
@@ -165,7 +165,8 @@
setattr(self, 'bind_' + name, _bind)
self.bindingsNames.append(name)
- def addSettings(self, name, desc, fields, btnlabel, callback):
+ def addSettings(self, name, desc, fields, btnlabel, callback, return annotate.MethodBinding(
@@ -179,8 +180,23 @@
setattr(self, 'action_' + name, callback)
- if name not in self.bindingsNames:
- self.bindingsNames.append(name)
+ if addAfterName not in self.bindingsNames: + # Just append new setting if not yet present + if name not in self.bindingsNames: + self.bindingsNames.append(name) + # We need to insert new setting + # imediately _after_ addAfterName + # First remove new setting if already present + # to make sure it goes into correct place + if name in self.bindingsNames: + self.bindingsNames.remove(name) + # Now add new setting in correct place + self.bindingsNames.insert( + self.bindingsNames.index(addAfterName)+1, def delSettings(self, name):