# HG changeset patch # User Mario de Sousa # Date 1590659688 -3600 # Node ID 990004083eb835d24f09672c82b34f45e704ca60 # Parent 1b4b335e19ea269bf5d339eef7c73cdf4b09f1ff Modbus plugin: Add "exec. req. flag" and "write on change" features diff -r 1b4b335e19ea -r 990004083eb8 modbus/mb_runtime.c --- 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 #include /* required for memcpy() */ +#include +#include #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 until time to start next periodic/cyclic scan cycle + * + * In an attempt to be able to run the MB transactions during the + * 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; diff -r 1b4b335e19ea -r 990004083eb8 modbus/mb_runtime.h --- 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; diff -r 1b4b335e19ea -r 990004083eb8 modbus/mb_utils.py --- 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 diff -r 1b4b335e19ea -r 990004083eb8 modbus/modbus.py --- 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 @@ + @@ -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 @@ - + @@ -388,7 +411,7 @@ - + @@ -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