Modbus plugin: Add "exec. req. flag" and "write on change" features
--- a/modbus/mb_runtime.c Wed Nov 13 11:21:04 2019 +0100
+++ b/modbus/mb_runtime.c Thu May 28 10:54:48 2020 +0100
@@ -25,6 +25,8 @@
#include <stdio.h>
#include <string.h> /* required for memcpy() */
+#include <time.h>
+#include <signal.h>
#include "mb_slave_and_master.h"
#include "MB_%(locstr)s.h"
@@ -299,10 +301,42 @@
// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
- // get the current time
- clock_gettime(CLOCK_MONOTONIC, &next_cycle);
-
- // loop the communication with the client
+ // configure the timer for periodic activation
+ {
+ struct itimerspec timerspec;
+ timerspec.it_interval.tv_sec = period_sec;
+ timerspec.it_interval.tv_nsec = period_nsec;
+ timerspec.it_value = timerspec.it_interval;
+
+ if (timer_settime(client_nodes[client_node_id].timer_id, 0 /* flags */, &timerspec, NULL) < 0)
+ fprintf(stderr, "Modbus plugin: Error configuring periodic activation timer for Modbus client %%s.\n", client_nodes[client_node_id].location);
+ }
+
+ /* loop the communication with the client
+ *
+ * When the client thread has difficulty communicating with remote client and/or server (network issues, for example),
+ * then the communications get delayed and we will fall behind in the period.
+ *
+ * This is OK. Note that if the condition variable were to be signaled multiple times while the client thread is inside the same
+ * Modbus transaction, then all those signals would be ignored.
+ * However, and since we keep the mutex locked during the communication cycle, it is not possible to signal the condition variable
+ * during that time (it is only possible while the thread is blocked during the call to pthread_cond_wait().
+ *
+ * This means that when network issues eventually get resolved, we will NOT have a bunch of delayed activations to handle
+ * in quick succession (which would goble up CPU time).
+ *
+ * Notice that the above property is valid whether the communication cycle is run with the mutex locked, or unlocked.
+ * Since it makes it easier to implement the correct semantics for the other activation methods if the communication cycle
+ * is run with the mutex locked, then that is what we do.
+ *
+ * Note that during all the communication cycle we will keep locked the mutex
+ * (i.e. the mutex used together with the condition variable that will activate a new communication cycle)
+ *
+ * Note that we never get to explicitly unlock this mutex. It will only be unlocked by the pthread_cond_wait()
+ * call at the end of the cycle.
+ */
+ pthread_mutex_lock(&(client_nodes[client_node_id].mutex));
+
while (1) {
/*
struct timespec cur_time;
@@ -311,9 +345,22 @@
*/
int req;
for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){
- /*just do the requests belonging to the client */
+ /* just do the requests belonging to the client */
if (client_requests[req].client_node_id != client_node_id)
continue;
+
+ /* only do the request if:
+ * - this request was explictly asked to be executed by the client program
+ * OR
+ * - the client thread was activated periodically
+ * (in which case we execute all the requests belonging to the client node)
+ */
+ if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0))
+ continue;
+
+ //fprintf(stderr, "Modbus plugin: RUNNING<###> of Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n",
+ // req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+
int res_tmp = __execute_mb_request(req);
switch (res_tmp) {
case PORT_FAILURE: {
@@ -357,36 +404,40 @@
break;
}
}
- }
- // Determine absolute time instant for starting the next cycle
- struct timespec prev_cycle, now;
- prev_cycle = next_cycle;
- timespec_add(next_cycle, period_sec, period_nsec);
- /* NOTE A:
- * When we have difficulty communicating with remote client and/or server, then the communications get delayed and we will
- * fall behind in the period. This means that when communication is re-established we may end up running this loop continuously
- * for some time until we catch up.
- * This is undesirable, so we detect it by making sure the next_cycle will start in the future.
- * When this happens we will switch from a purely periodic task _activation_ sequence, to a fixed task suspension interval.
- *
- * NOTE B:
- * It probably does not make sense to check for overflow of timer - so we don't do it for now!
- * Even in 32 bit systems this will take at least 68 years since the computer booted
- * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0
- * every time the system boots). On 64 bit systems, it will take over
- * 10^11 years to overflow.
- */
- clock_gettime(CLOCK_MONOTONIC, &now);
- if ( ((now.tv_sec > next_cycle.tv_sec) || ((now.tv_sec == next_cycle.tv_sec) && (now.tv_nsec > next_cycle.tv_nsec)))
- /* We are falling behind. See NOTE A above */
- || (next_cycle.tv_sec < prev_cycle.tv_sec)
- /* Timer overflow. See NOTE B above */
- ) {
- next_cycle = now;
- timespec_add(next_cycle, period_sec, period_nsec);
- }
-
- clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL);
+
+ /* We have just finished excuting a client transcation request.
+ * If the current cycle was activated by user request we reset the flag used to ask to run it
+ */
+ if (0 != client_requests[req].flag_exec_req) {
+ client_requests[req].flag_exec_req = 0;
+ client_requests[req].flag_exec_started = 0;
+ }
+
+ //fprintf(stderr, "Modbus plugin: RUNNING<---> of Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n",
+ // req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+ }
+
+ // Wait for signal (from timer or explicit request from user program) before starting the next cycle
+ {
+ // No need to lock the mutex. Is is already locked just before the while(1) loop.
+ // Read the comment there to understand why.
+ // pthread_mutex_lock(&(client_nodes[client_node_id].mutex));
+
+ /* the client thread has just finished a cycle, so all the flags used to signal an activation
+ * and specify the activation source (periodic, user request, ...)
+ * get reset here, before waiting for a new activation.
+ */
+ client_nodes[client_node_id].periodic_act = 0;
+ client_nodes[client_node_id].execute_req = 0;
+
+ while (client_nodes[client_node_id].execute_req == 0)
+ pthread_cond_wait(&(client_nodes[client_node_id].condv),
+ &(client_nodes[client_node_id].mutex));
+
+ // We run the communication cycle with the mutex locked.
+ // Read the comment just above the while(1) to understand why.
+ // pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+ }
}
// humour the compiler.
@@ -394,6 +445,50 @@
}
+
+/* Function to activate a client node's thread */
+/* returns -1 if it could not send the signal */
+static int __signal_client_thread(int client_node_id) {
+ /* We TRY to signal the client thread.
+ * We do this because this function can be called at the end of the PLC scan cycle
+ * and we don't want it to block at that time.
+ */
+ if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) != 0)
+ return -1;
+ client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
+ pthread_cond_signal (&(client_nodes[client_node_id].condv));
+ pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+ return 0;
+}
+
+
+
+/* Function that will be called whenever a client node's periodic timer expires. */
+/* The client node's thread will be waiting on a condition variable, so this function simply signals that
+ * condition variable.
+ *
+ * The same callback function is called by the timers of all client nodes. The id of the client node
+ * in question will be passed as a parameter to the call back function.
+ */
+void __client_node_timer_callback_function(union sigval sigev_value) {
+ /* signal the client node's condition variable on which the client node's thread should be waiting... */
+ /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
+ //pthread_mutex_lock (&(client_nodes[sigev_value.sival_int].mutex));
+ if (pthread_mutex_trylock (&(client_nodes[sigev_value.sival_int].mutex)) != 0)
+ /* we never get to signal the thread for activation. But that is OK.
+ * If it still in the communication cycle (during which the mutex is kept locked)
+ * then that means that the communication cycle is falling behing in the periodic
+ * communication cycle, and we therefore need to skip a period.
+ */
+ return;
+ client_nodes[sigev_value.sival_int].execute_req = 1; // tell the thread to execute
+ client_nodes[sigev_value.sival_int].periodic_act = 1; // tell the thread the activation was done by periodic timer
+ pthread_cond_signal (&(client_nodes[sigev_value.sival_int].condv));
+ pthread_mutex_unlock(&(client_nodes[sigev_value.sival_int].mutex));
+}
+
+
+
int __cleanup_%(locstr)s ();
int __init_%(locstr)s (int argc, char **argv){
int index;
@@ -421,9 +516,14 @@
return -1;
}
- /* init the mutex for each client request */
+ /* init each client request */
/* Must be done _before_ launching the client threads!! */
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
+ /* make sure flags connected to user program MB transaction start request are all reset */
+ client_requests[index].flag_exec_req = 0;
+ client_requests[index].flag_exec_started = 0;
+ /* init the mutex for each client request */
+ /* Must be done _before_ launching the client threads!! */
if (pthread_mutex_init(&(client_requests[index].coms_buf_mutex), NULL)) {
fprintf(stderr, "Modbus plugin: Error initializing request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location);
goto error_exit;
@@ -443,6 +543,39 @@
}
client_nodes[index].init_state = 1; // we have created the node
+ /* initialize the mutex variable that will be used by the thread handling the client node */
+ if (pthread_mutex_init(&(client_nodes[index].mutex), NULL) < 0) {
+ fprintf(stderr, "Modbus plugin: Error creating mutex for modbus client node %%s\n", client_nodes[index].location);
+ goto error_exit;
+ }
+ client_nodes[index].init_state = 2; // we have created the mutex
+
+ /* initialize the condition variable that will be used by the thread handling the client node */
+ if (pthread_cond_init(&(client_nodes[index].condv), NULL) < 0) {
+ fprintf(stderr, "Modbus plugin: Error creating condition variable for modbus client node %%s\n", client_nodes[index].location);
+ goto error_exit;
+ }
+ client_nodes[index].execute_req = 0; //variable associated with condition variable
+ client_nodes[index].init_state = 3; // we have created the condition variable
+
+ /* initialize the timer that will be used to periodically activate the client node */
+ {
+ // start off by reseting the flag that will be set whenever the timer expires
+ client_nodes[index].periodic_act = 0;
+
+ struct sigevent evp;
+ evp.sigev_notify = SIGEV_THREAD; /* Notification method - call a function in a new thread context */
+ evp.sigev_value.sival_int = index; /* Data passed to function upon notification - used to indentify which client node to activate */
+ evp.sigev_notify_function = __client_node_timer_callback_function; /* function to call upon timer expiration */
+ evp.sigev_notify_attributes = NULL; /* attributes for new thread in which sigev_notify_function will be called/executed */
+
+ if (timer_create(CLOCK_MONOTONIC, &evp, &(client_nodes[index].timer_id)) < 0) {
+ fprintf(stderr, "Modbus plugin: Error creating timer for modbus client node %%s\n", client_nodes[index].location);
+ goto error_exit;
+ }
+ }
+ client_nodes[index].init_state = 4; // we have created the timer
+
/* launch a thread to handle this client node */
{
int res = 0;
@@ -450,11 +583,11 @@
res |= pthread_attr_init(&attr);
res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index));
if (res != 0) {
- fprintf(stderr, "Modbus plugin: Error starting modbus client thread for node %%s\n", client_nodes[index].location);
+ fprintf(stderr, "Modbus plugin: Error starting thread for modbus client node %%s\n", client_nodes[index].location);
goto error_exit;
}
}
- client_nodes[index].init_state = 2; // we have created the node and a thread
+ client_nodes[index].init_state = 5; // we have created the thread
}
/* init each local server */
@@ -499,9 +632,26 @@
int index;
for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){
- /*just do the output requests */
+ /* synchronize the PLC and MB buffers only for the output requests */
if (client_requests[index].req_type == req_output){
+
+ // lock the mutex brefore copying the data
if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){
+
+ // Check if user configured this MB request to be activated whenever the data to be written changes
+ if (client_requests[index].write_on_change) {
+ // Let's check if the data did change...
+ // compare the data in plcv_buffer to coms_buffer
+ int res;
+ res = memcmp((void *)client_requests[index].coms_buffer /* buf 1 */,
+ (void *)client_requests[index].plcv_buffer /* buf 2*/,
+ REQ_BUF_SIZE * sizeof(u16) /* size in bytes */);
+
+ // if data changed, activate execution request
+ if (0 != res)
+ client_requests[index].flag_exec_req = 1;
+ }
+
// copy from plcv_buffer to coms_buffer
memcpy((void *)client_requests[index].coms_buffer /* destination */,
(void *)client_requests[index].plcv_buffer /* source */,
@@ -509,7 +659,33 @@
pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex));
}
}
- }
+ /* if the user program set the execution request flag, then activate the thread
+ * that handles this Modbus client transaction so it gets a chance to be executed
+ * (but don't activate the thread if it has already been activated!)
+ *
+ * NOTE that we do this, for both the IN and OUT mapped location, under this
+ * __publish_() function. The scan cycle of the PLC works as follows:
+ * - call __retrieve_()
+ * - execute user programs
+ * - call __publish_()
+ * - insert <delay> until time to start next periodic/cyclic scan cycle
+ *
+ * In an attempt to be able to run the MB transactions during the <delay>
+ * interval in which not much is going on, we handle the user program
+ * requests to execute a specific MB transaction in this __publish_()
+ * function.
+ */
+ if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) {
+ int client_node_id = client_requests[index].client_node_id;
+ if (__signal_client_thread(client_node_id) >= 0) {
+ /* - upon success, set flag_exec_started
+ * - both flags (flag_exec_req and flag_exec_started) will be reset
+ * once the transaction has completed.
+ */
+ client_requests[index].flag_exec_started = 1;
+ }
+ }
+ }
}
@@ -544,12 +720,39 @@
/* kill thread and close connections of each modbus client node */
for (index=0; index < NUMBER_OF_CLIENT_NODES; index++) {
close = 0;
- if (client_nodes[index].init_state >= 2) {
+ if (client_nodes[index].init_state >= 5) {
// thread was launched, so we try to cancel it!
close = pthread_cancel(client_nodes[index].thread_id);
close |= pthread_join (client_nodes[index].thread_id, NULL);
if (close < 0)
- fprintf(stderr, "Modbus plugin: Error closing thread for modbus client %%s\n", client_nodes[index].location);
+ fprintf(stderr, "Modbus plugin: Error closing thread for modbus client node %%s\n", client_nodes[index].location);
+ }
+ res |= close;
+
+ close = 0;
+ if (client_nodes[index].init_state >= 4) {
+ // timer was created, so we try to destroy it!
+ close = timer_delete(client_nodes[index].timer_id);
+ if (close < 0)
+ fprintf(stderr, "Modbus plugin: Error destroying timer for modbus client node %%s\n", client_nodes[index].location);
+ }
+ res |= close;
+
+ close = 0;
+ if (client_nodes[index].init_state >= 3) {
+ // condition variable was created, so we try to destroy it!
+ close = pthread_cond_destroy(&(client_nodes[index].condv));
+ if (close < 0)
+ fprintf(stderr, "Modbus plugin: Error destroying condition variable for modbus client node %%s\n", client_nodes[index].location);
+ }
+ res |= close;
+
+ close = 0;
+ if (client_nodes[index].init_state >= 2) {
+ // mutex was created, so we try to destroy it!
+ close = pthread_mutex_destroy(&(client_nodes[index].mutex));
+ if (close < 0)
+ fprintf(stderr, "Modbus plugin: Error destroying mutex for modbus client node %%s\n", client_nodes[index].location);
}
res |= close;
--- a/modbus/mb_runtime.h Wed Nov 13 11:21:04 2019 +0100
+++ b/modbus/mb_runtime.h Thu May 28 10:54:48 2020 +0100
@@ -54,11 +54,32 @@
typedef struct{
const char *location;
node_addr_t node_address;
- int mb_nd;
+ int mb_nd; // modbus library node used for this client
int init_state; // store how far along the client's initialization has progressed
- u64 comm_period;
+ u64 comm_period;// period to use when periodically sending requests to remote server
int prev_error; // error code of the last printed error message (0 when no error)
- pthread_t thread_id; // thread handling all communication with this client
+ pthread_t thread_id; // thread handling all communication for this client node
+ timer_t timer_id; // timer used to periodically activate this client node's thread
+ pthread_mutex_t mutex; // mutex to be used with the following condition variable
+ pthread_cond_t condv; // used to signal the client thread when to start new modbus transactions
+ int execute_req; /* used, in association with condition variable,
+ * to signal when to send the modbus request to the server
+ * Note that we cannot simply rely on the condition variable to signal
+ * when to activate the client thread, as the call to
+ * pthread_cond_wait() may return without having been signaled!
+ * From the manual:
+ * Spurious wakeups from the
+ * pthread_cond_timedwait() or pthread_cond_wait() functions may occur.
+ * Since the return from pthread_cond_timedwait() or pthread_cond_wait()
+ * does not imply anything about the value of this predicate, the predi-
+ * cate should be re-evaluated upon such return.
+ */
+ int periodic_act; /* (boolen) flag will be set when the client node's thread was activated
+ * (by signaling the above condition variable) by the periodic timer.
+ * Note that this same thread may also be activated (condition variable is signaled)
+ * by other sources, such as when the user program requests that a specific
+ * client MB transation be executed (flag_exec_req in client_request_t)
+ */
} client_node_t;
@@ -82,11 +103,37 @@
u8 error_code; // modbus error code (if any) of current request
int prev_error; // error code of the last printed error message (0 when no error)
struct timespec resp_timeout;
+ u8 write_on_change; // boolean flag. If true => execute MB request when data to send changes
// buffer used to store located PLC variables
u16 plcv_buffer[REQ_BUF_SIZE];
// buffer used to store data coming from / going to server
u16 coms_buffer[REQ_BUF_SIZE];
pthread_mutex_t coms_buf_mutex; // mutex to access coms_buffer[]
+ /* boolean flag that will be mapped onto a (BOOL) located variable
+ * (u16 because IEC 61131-3 BOOL are mapped onto u16 in C code! )
+ * -> allow PLC program to request when to start the MB transaction
+ * -> will be reset once the MB transaction has completed
+ */
+ u16 flag_exec_req;
+ /* flag that works in conjunction with flag_exec_req
+ * (does not really need to be u16 as it is not mapped onto a located variable. )
+ * -> used by internal logic to indicate that the client thread
+ * that will be executing the MB transaction
+ * requested by flag exec_req has already been activated.
+ * -> will be reset once the MB transaction has completed
+ */
+ u16 flag_exec_started;
+ /* flag that will be mapped onto a (WORD) located variable
+ * (u16 because the flag is a word! )
+ * -> MSByte will store the result of the last executed MB transaction
+ * 1 -> error accessing IP network, or serial interface
+ * 2 -> reply received from server was an invalid frame
+ * 3 -> server did not reply before timeout expired
+ * 4 -> server returned a valid error frame
+ * -> if the MSByte is 4, the LSByte will store the MB error code returned by the server
+ * -> will be reset (set to 0) once this MB transaction has completed sucesfully
+ */
+ u16 flag_exec_status;
} client_request_t;
--- a/modbus/mb_utils.py Wed Nov 13 11:21:04 2019 +0100
+++ b/modbus/mb_utils.py Thu May 28 10:54:48 2020 +0100
@@ -184,7 +184,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 */,
+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))
@@ -198,6 +198,7 @@
"slaveid": GetCTVal(child, 1),
"address": GetCTVal(child, 3),
"count": GetCTVal(child, 2),
+ "write_on_change": GetCTVal(child, 5),
"timeout": timeout,
"timeout_s": timeout_s,
"timeout_ns": timeout_ns,
@@ -222,5 +223,11 @@
self.GetCTRoot().logger.write_error(
"Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (start_address + nr_channels must be less than 65536)\nModbus plugin: Aborting C code generation for this node\n" % request_dict)
return None
+ if (request_dict["write_on_change"] and (request_dict["iotype"] == 'req_input')):
+ self.GetCTRoot().logger.write_error(
+ "Modbus plugin: (warning) MB client request node %(locreqstr)s has option 'write_on_change' enabled.\nModbus plugin: This option will be ignored by the Modbus read function.\n" % request_dict)
+ # NOTE: this is only a warning (we don't wish to abort code generation) so following line must be left commented out!
+ # return None
+
return req_init_template % request_dict
--- a/modbus/modbus.py Wed Nov 13 11:21:04 2019 +0100
+++ b/modbus/modbus.py Thu May 28 10:54:48 2020 +0100
@@ -83,6 +83,7 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
+ <xsd:attribute name="Write_on_change" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -115,7 +116,29 @@
datatacc = modbus_function_dict[function][6]
# 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register'
dataname = modbus_function_dict[function][7]
+ # start off with a boolean entry
+ # This is a flag used to allow the user program to control when to
+ # execute the Modbus request.
+ # NOTE: If the Modbus request has a 'current_location' of
+ # %QX1.2.3
+ # then the execution control flag will be
+ # %QX1.2.3.0.0
+ # and all the Modbus registers/coils will be
+ # %QX1.2.3.0
+ # %QX1.2.3.1
+ # %QX1.2.3.2
+ # ..
+ # %QX1.2.3.n
entries = []
+ entries.append({
+ "name": "Exec. request flag",
+ "type": LOCATION_VAR_MEMORY,
+ "size": 1, # BOOL flag
+ "IEC_type": "BOOL", # BOOL flag
+ "var_name": "var_name",
+ "location": "X" + ".".join([str(i) for i in current_location]) + ".0.0",
+ "description": "MB request execution control flag",
+ "children": []})
for offset in range(address, address + count):
entries.append({
"name": dataname + " " + str(offset),
@@ -270,7 +293,7 @@
<xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
<xsd:simpleType>
<xsd:restriction base="xsd:unsignedLong">
- <xsd:minInclusive value="1"/>
+ <xsd:minInclusive value="0"/>
<xsd:maxInclusive value="2147483647"/>
</xsd:restriction>
</xsd:simpleType>
@@ -388,7 +411,7 @@
<xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
- <xsd:minInclusive value="1"/>
+ <xsd:minInclusive value="0"/>
<xsd:maxInclusive value="2147483647"/>
</xsd:restriction>
</xsd:simpleType>
@@ -720,12 +743,32 @@
for iecvar in subchild.GetLocations():
# absloute address - start address
relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
- # test if relative address in request specified range
- if relative_addr in xrange(int(GetCTVal(subchild, 2))):
+ # test if the located variable
+ # (a) has relative address in request specified range
+ # AND is NOT
+ # (b) is a control flag added by this modbus plugin
+ # to control its execution at runtime.
+ # Currently, we only add the "Execution Control Flag"
+ # to each client request (one flag per request)
+ # to control when to execute the request (if not executed periodically)
+ # While all Modbus registers/coils are mapped onto a location
+ # with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped
+ # onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last
+ # two numbers are always '0.0', and the first two identify the request.
+ # In the following if, we check for this condition by checking
+ # if their are at least 4 or more number in the location's address.
+ if ( relative_addr in xrange(int(GetCTVal(subchild, 2))) # condition (a) explained above
+ and len(iecvar["LOC"]) < 5): # condition (b) explained above
if str(iecvar["NAME"]) not in loc_vars_list:
loc_vars.append(
"u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
loc_vars_list.append(str(iecvar["NAME"]))
+ # Now add the located variable in case it is a flag (condition (b) above
+ if len(iecvar["LOC"]) >= 5: # condition (b) explained above
+ if str(iecvar["NAME"]) not in loc_vars_list:
+ loc_vars.append(
+ "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid))
+ loc_vars_list.append(str(iecvar["NAME"]))
client_requestid += 1
tcpclient_node_count += 1
client_nodeid += 1
@@ -745,12 +788,32 @@
for iecvar in subchild.GetLocations():
# absloute address - start address
relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
- # test if relative address in request specified range
- if relative_addr in xrange(int(GetCTVal(subchild, 2))):
+ # test if the located variable
+ # (a) has relative address in request specified range
+ # AND is NOT
+ # (b) is a control flag added by this modbus plugin
+ # to control its execution at runtime.
+ # Currently, we only add the "Execution Control Flag"
+ # to each client request (one flag per request)
+ # to control when to execute the request (if not executed periodically)
+ # While all Modbus registers/coils are mapped onto a location
+ # with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped
+ # onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last
+ # two numbers are always '0.0', and the first two identify the request.
+ # In the following if, we check for this condition by checking
+ # if their are at least 4 or more number in the location's address.
+ if ( relative_addr in xrange(int(GetCTVal(subchild, 2))) # condition (a) explained above
+ and len(iecvar["LOC"]) < 5): # condition (b) explained above
if str(iecvar["NAME"]) not in loc_vars_list:
loc_vars.append(
"u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
loc_vars_list.append(str(iecvar["NAME"]))
+ # Now add the located variable in case it is a flag (condition (b) above
+ if len(iecvar["LOC"]) >= 5: # condition (b) explained above
+ if str(iecvar["NAME"]) not in loc_vars_list:
+ loc_vars.append(
+ "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid))
+ loc_vars_list.append(str(iecvar["NAME"]))
client_requestid += 1
rtuclient_node_count += 1
client_nodeid += 1