Merge changes from default into svghmi branch svghmi
authorEdouard Tisserant
Wed, 24 Feb 2021 09:37:00 +0100
branchsvghmi
changeset 3163 7cf38cd0d820
parent 3161 edd558965f58 (current diff)
parent 2720 971bb3503957 (diff)
child 3164 ea4a61b4a325
Merge changes from default into svghmi branch
ConfigTreeNode.py
ProjectController.py
modbus/web_settings.py
--- 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
              */
--- 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;
 
 
--- 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))
--- 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
--- 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))