# HG changeset patch # User Edouard Tisserant # Date 1614155820 -3600 # Node ID 7cf38cd0d820e3251ba1e94a7651f93c0364aae8 # Parent edd558965f585a1239c495f69914ce2864dd8b2b# Parent 971bb35039572a683fbbe52f5e43515ddda646a8 Merge changes from default into svghmi branch diff -r edd558965f58 -r 7cf38cd0d820 modbus/mb_runtime.c --- a/modbus/mb_runtime.c Tue Feb 23 10:07:21 2021 +0100 +++ b/modbus/mb_runtime.c Wed Feb 24 09:37:00 2021 +0100 @@ -62,7 +62,7 @@ (int) client_requests[request_id].count, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -74,7 +74,7 @@ (int) client_requests[request_id].count, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -86,7 +86,7 @@ (int) client_requests[request_id].count, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -98,7 +98,7 @@ (int) client_requests[request_id].count, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -108,7 +108,7 @@ client_requests[request_id].coms_buffer[0], client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -118,7 +118,7 @@ client_requests[request_id].coms_buffer[0], client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -138,7 +138,7 @@ client_requests[request_id].coms_buffer, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -149,7 +149,7 @@ client_requests[request_id].coms_buffer, client_nodes[client_requests[request_id].client_node_id].mb_nd, client_requests[request_id].retries, - &(client_requests[request_id].error_code), + &(client_requests[request_id].mb_error_code), &(client_requests[request_id].resp_timeout), &(client_requests[request_id].coms_buf_mutex)); @@ -352,34 +352,39 @@ */ int res_tmp = __execute_mb_request(req); + client_requests[req].tn_error_code = 0; // assume success switch (res_tmp) { case PORT_FAILURE: { if (res_tmp != client_nodes[client_node_id].prev_error) fprintf(stderr, "Modbus plugin: Error connecting Modbus client %%s to remote server.\n", client_nodes[client_node_id].location); client_nodes[client_node_id].prev_error = res_tmp; + client_requests[req].tn_error_code = 1; // error accessing IP network, or serial interface break; } case INVALID_FRAME: { if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned an invalid/corrupted frame.\n", client_requests[req].location); client_requests[req].prev_error = res_tmp; + client_requests[req].tn_error_code = 2; // reply received from server was an invalid frame break; } case TIMEOUT: { if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s timed out waiting for reply from server.\n", client_requests[req].location); client_requests[req].prev_error = res_tmp; + client_requests[req].tn_error_code = 3; // server did not reply before timeout expired break; } case MODBUS_ERROR: { - if (client_requests[req].prev_error != client_requests[req].error_code) { - fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned error code 0x%%2x", client_requests[req].location, client_requests[req].error_code); - if (client_requests[req].error_code <= MAX_MODBUS_ERROR_CODE ) { - fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].error_code]); + if (client_requests[req].prev_error != client_requests[req].mb_error_code) { + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned error code 0x%%2x", client_requests[req].location, client_requests[req].mb_error_code); + if (client_requests[req].mb_error_code <= MAX_MODBUS_ERROR_CODE ) { + fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].mb_error_code]); fprintf(stderr, ".\n"); } } - client_requests[req].prev_error = client_requests[req].error_code; + client_requests[req].prev_error = client_requests[req].mb_error_code; + client_requests[req].tn_error_code = 4; // server returned a valid Modbus error frame break; } default: { @@ -394,7 +399,14 @@ break; } } - + + /* Set the flag_tn_error_code and flag_mb_error_code that are mapped onto + * located BYTE variables, so the user program + * knows how the communication is going. + */ + client_requests[req].flag_mb_error_code = client_requests[req].mb_error_code; + client_requests[req].flag_tn_error_code = client_requests[req].tn_error_code; + /* 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 */ diff -r edd558965f58 -r 7cf38cd0d820 modbus/mb_runtime.h --- a/modbus/mb_runtime.h Tue Feb 23 10:07:21 2021 +0100 +++ b/modbus/mb_runtime.h Wed Feb 24 09:37:00 2021 +0100 @@ -147,7 +147,8 @@ u16 address; u16 count; int retries; - u8 error_code; // modbus error code (if any) of current request + u8 mb_error_code; // modbus error code (if any) of last executed request + u8 tn_error_code; // transaction error code (if any) of last executed 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 @@ -157,30 +158,43 @@ 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! ) + * (u8 because IEC 61131-3 BOOL are mapped onto u8 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; + u8 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. ) + * (does not really need to be u8 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 + u8 flag_exec_started; + /* flag that will be mapped onto a (BYTE) located variable + * (u8 because the flag is a BYTE! ) + * -> 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 + * 4 -> server returned a valid Modbus error frame * -> will be reset (set to 0) once this MB transaction has completed sucesfully - */ - u16 flag_exec_status; + * + * In other words, this variable is a copy of tn_error_code, reset after each request attempt completes. + * We map this copy (instead of tn_error_code) onto a located variable in case the user program decides + * to overwrite its value and mess up the plugin logic. + */ + u8 flag_tn_error_code; + /* flag that will be mapped onto a (BYTE) located variable + * (u8 because the flag is a BYTE! ) + * -> if flag_tn_error_code is 4, this flag will store the MB error code returned by the MB server in a MB error frame + * -> will be reset (set to 0) once this MB transaction has completed succesfully + * + * In other words, this variable is a copy of mb_error_code, reset after each request attempt completes. + * We map this copy (instead of mb_error_code) onto a located variable in case the user program decides + * to overwrite its value and mess up the plugin logic. + */ + u8 flag_mb_error_code; } client_request_t; diff -r edd558965f58 -r 7cf38cd0d820 modbus/mb_utils.py --- a/modbus/mb_utils.py Tue Feb 23 10:07:21 2021 +0100 +++ b/modbus/mb_utils.py Wed Feb 24 09:37:00 2021 +0100 @@ -186,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 /* mb_error_code */, 0 /* tn_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)) diff -r edd558965f58 -r 7cf38cd0d820 modbus/modbus.py --- a/modbus/modbus.py Tue Feb 23 10:07:21 2021 +0100 +++ b/modbus/modbus.py Wed Feb 24 09:37:00 2021 +0100 @@ -132,13 +132,31 @@ # %QX1.2.3.n entries = [] entries.append({ - "name": "Exec. request flag", + "name": "Execute 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", + "description": "Modbus request execution control flag", + "children": []}) + entries.append({ + "name": "Modbus Request Status flag", + "type": LOCATION_VAR_MEMORY, + "size": 8, # BYTE flag + "IEC_type": "BYTE", # BYTE flag + "var_name": "var_name", + "location": "B" + ".".join([str(i) for i in current_location]) + ".0.1", + "description": "Modbus request status flag (0 -> OK, 1 -> Network error, 2 -> Received invalid frame, 3 -> Timeout, 4 -> Received error frame)", + "children": []}) + entries.append({ + "name": "Modbus Error Code", + "type": LOCATION_VAR_MEMORY, + "size": 8, # BYTE flag + "IEC_type": "BYTE", # BYTE flag + "var_name": "var_name", + "location": "B" + ".".join([str(i) for i in current_location]) + ".0.2", + "description": "Modbus Error Code received in Modbus error frame", "children": []}) for offset in range(address, address + count): entries.append({ @@ -171,6 +189,7 @@ return [], "", False + # # # @@ -873,29 +892,45 @@ # 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) + # (b) is a flag added by this modbus plugin. + # We currently add 3 flags: An execution control flag + # and another two status flags. + # We add the "Execution Control Flag" to each client request (one flag per request) + # to allow the user program 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 there 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.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"])) + # Add if it is a Execution Request Flag (mapped onto %QXa.b.c.0.0), so last number is a '0' + if iecvar["LOC"][4] == 0: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) + # Add if it is a "Modbus Request Status flag" (mapped onto %QWa.b.c.0.1), so last number is a '1' + # -> 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 Modbus error frame + # -> will be reset (set to 0) once this MB transaction has completed sucesfully + if iecvar["LOC"][4] == 1: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_tn_error_code;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) + # Add if it is a "Modbus Error code" (mapped onto %QWa.b.c.0.2), so last number is a '2' + # -> if "Modbus Request Status flag" is 4, this flag will store the MB error code returned by the MB server in a MB error frame + # -> will be reset (set to 0) once this MB transaction has completed succesfully + if iecvar["LOC"][4] == 2: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_mb_error_code;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) client_requestid += 1 tcpclient_node_count += 1 client_nodeid += 1 @@ -918,17 +953,17 @@ # 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) + # (b) is a flag added by this modbus plugin. + # We currently add 3 flags: An execution control flag + # and another two status flags. + # We add the "Execution Control Flag" to each client request (one flag per request) + # to allow the user program 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 there 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: @@ -938,9 +973,26 @@ # 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"])) + # Add if it is a Execution Request Flag (mapped onto %QXa.b.c.0.0), so last number is a '0' + if iecvar["LOC"][4] == 0: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) + # Add if it is a "Modbus Request Status flag" (mapped onto %QWa.b.c.0.1), so last number is a '1' + # -> 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 Modbus error frame + # -> will be reset (set to 0) once this MB transaction has completed sucesfully + if iecvar["LOC"][4] == 1: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_tn_error_code;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) + # Add if it is a "Modbus Error code" (mapped onto %QWa.b.c.0.2), so last number is a '2' + # -> if "Modbus Request Status flag" is 4, this flag will store the MB error code returned by the MB server in a MB error frame + # -> will be reset (set to 0) once this MB transaction has completed succesfully + if iecvar["LOC"][4] == 2: + loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_mb_error_code;" % (client_requestid)) + loc_vars_list.append(str(iecvar["NAME"])) client_requestid += 1 rtuclient_node_count += 1 client_nodeid += 1 diff -r edd558965f58 -r 7cf38cd0d820 runtime/PLCObject.py --- a/runtime/PLCObject.py Tue Feb 23 10:07:21 2021 +0100 +++ b/runtime/PLCObject.py Wed Feb 24 09:37:00 2021 +0100 @@ -398,8 +398,10 @@ self.PythonRuntimeCall("init", use_evaluator=False) self.PythonThreadCondLock = Lock() - self.PythonThreadCond = Condition(self.PythonThreadCondLock) - self.PythonThreadCmd = "Wait" + self.PythonThreadCmdCond = Condition(self.PythonThreadCondLock) + self.PythonThreadAckCond = Condition(self.PythonThreadCondLock) + self.PythonThreadCmd = None + self.PythonThreadAck = None self.PythonThread = Thread(target=self.PythonThreadProc, name="PLCPythonThread") self.PythonThread.start() @@ -442,24 +444,43 @@ while True: self.PythonThreadCondLock.acquire() cmd = self.PythonThreadCmd - while cmd == "Wait": - self.PythonThreadCond.wait() + while cmd is None: + self.PythonThreadCmdCond.wait() cmd = self.PythonThreadCmd - self.PythonThreadCmd = "Wait" + self.PythonThreadCmd = None self.PythonThreadCondLock.release() - if cmd == "Activate": + if cmd == "PreStart": + self.PreStartPLC() + # Ack once PreStart done, must be finished before StartPLC + self.PythonThreadAcknowledge(cmd) + elif cmd == "Start": + # Ack Immediately, for responsiveness + self.PythonThreadAcknowledge(cmd) self.PythonRuntimeCall("start") - self.PreStartPLC() + self.LogMessage("Python extensions started") self.PythonThreadLoop() self.PythonRuntimeCall("stop", reverse_order=True) - else: # "Finish" + elif cmd == "Finish": + self.PythonThreadAcknowledge(cmd) break + def PythonThreadAcknowledge(self, ack): + self.PythonThreadCondLock.acquire() + self.PythonThreadAck = ack + self.PythonThreadAckCond.notify() + self.PythonThreadCondLock.release() + def PythonThreadCommand(self, cmd): self.PythonThreadCondLock.acquire() self.PythonThreadCmd = cmd - self.PythonThreadCond.notify() + self.PythonThreadCmdCond.notify() + ack = None + while ack != cmd: + self.PythonThreadAckCond.wait() + ack = self.PythonThreadAck + self.PythonThreadAck = None + self.PythonThreadCondLock.release() def _fail(self, msg): @@ -483,13 +504,14 @@ self._fail(_("Problem starting PLC : can't load PLC")) if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped: + self.PythonThreadCommand("PreStart") c_argv = ctypes.c_char_p * len(self.argv) res = self._startPLC(len(self.argv), c_argv(*self.argv)) if res == 0: + self.LogMessage("PLC started") self.PLCStatus = PlcStatus.Started self.StatusChange() - self.PythonThreadCommand("Activate") - self.LogMessage("PLC started") + self.PythonThreadCommand("Start") else: self._fail(_("Problem starting PLC : error %d" % res))