FUNCTION_BLOCK "FB_OUC_TCP"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : SIOS
FAMILY : OUC
VERSION : 2.0
   VAR_INPUT 
      enable : Bool;
      send : Bool;
      rcvLen : UDInt;
      sendLen : UDInt;
      adhocMode : Bool;
      connParam {InstructionName := 'TCON_IP_v4'; LibVersion := '1.0'} : TCON_IP_v4;
   END_VAR

   VAR_OUTPUT 
      valid { ExternalWritable := 'False'} : Bool;
      done { ExternalWritable := 'False'} : Bool;
      ndr { ExternalWritable := 'False'} : Bool;
      busy { ExternalWritable := 'False'} : Bool;
      error { ExternalWritable := 'False'} : Bool;
      status { ExternalWritable := 'False'} : Word := #STATUS_NO_CALL;
      diagnostics { ExternalWritable := 'False'} : "typeDiagnostics";
      rcvdLen { ExternalWritable := 'False'} : UDInt;
   END_VAR

   VAR_IN_OUT 
      rcvdData : Array[*] of Byte;
      sendData : Array[*] of Byte;
   END_VAR

   VAR 
      statEnableOld { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statSendOld { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statValid { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statDone { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statNdr { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statBusy { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statError { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statErrorUserCleared { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statErrorAutoCleared { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statStatus { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word := #STATUS_NO_CALL;
      statSubfunctionStatus { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statFBErrorState { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statFBState { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt := #FB_STATE_NO_PROCESSING;
      statDisablingCompleted { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statEmptyDiagnostics { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : "typeDiagnostics";
      statConnParam {InstructionName := 'TCON_IP_v4'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TCON_IP_v4;
      instTCon {InstructionName := 'TCON'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TCON;
      instTRcv {InstructionName := 'TRCV'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TRCV;
      instTSend {InstructionName := 'TSEND'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TSEND;
      instTDiscon {InstructionName := 'TDISCON'; LibVersion := '2.1'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TDISCON;
      statRcvdLen { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : UDInt;
      statRcvEnable { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statConReq { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statDisconReq { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statSendReq { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
   END_VAR

   VAR_TEMP 
      tempEnable : Bool;
      tempSend : Bool;
      tempAdhocMode : Bool;
      tempRcvLen : UDInt;
      tempSendLen : UDInt;
   END_VAR

   VAR CONSTANT 
      FB_STATE_NO_PROCESSING : DInt := 0;
      FB_STATE_ENABLING : DInt := 10;
      FB_STATE_CONNECTED : DInt := 50;
      FB_STATE_DISABLING : DInt := 90;
      STATUS_EXECUTION_FINISHED : Word := 16#0000;
      STATUS_NO_CALL : Word := 16#7000;
      STATUS_FIRST_CALL : Word := 16#7001;
      STATUS_SUBSEQUENT_CALL : Word := 16#7002;
      SUB_STATUS_NO_ERROR : Word := 16#0000;
      ERR_IN_BLOCK_OPERATION : Word := 16#8001;
      ERR_PARAMETRIZATION : Word := 16#8200;
      ERR_PROCESSING_EXTERN : Word := 16#8400;
      ERR_PROCESSING_INTERN : Word := 16#8600;
      ERR_UNDEFINED_STATE : Word := 16#8600;
      ERR_TCON : Word := 16#8601;
      ERR_TSEND : Word := 16#8602;
      ERR_TRCV : Word := 16#8603;
      ERR_AREA_RESERVED : Word := 16#8800;
      ERR_USER_DEFINED_CLASSES : Word := 16#9000;
      ERR_OUC_PARTNER_TERMINATED_CONNECTION : Word := 16#80C5;
      ERR_OUC_CANNOT_REACH_PARTNER : Word := 16#80C6;
   END_VAR


BEGIN
	REGION BLOCK INFO HEADER
	  //===============================================================================
	  // (company) / (c)Copyright (year)
	  //-------------------------------------------------------------------------------
	  // Title:            (Title of this Block / Enable Template)
	  // Comment/Function: (that is implemented in the block)
	  //                   (Template for an FB with Enable / Valid handling based on PLCopen standard "Function Blocks for Motion Control" V2.0)
	  // Library/Family:   (that the source is dedicated to)
	  // Author:           (department / person in charge / contact)
	  // Tested with:      (test system with FW version)
	  // Engineering:      TIA Portal (SW version)
	  // Restrictions:     (OB types, etc.)
	  // Requirements:     (hardware, technological package, memory needed, etc.)
	  //-------------------------------------------------------------------------------
	  // Change log table:
	  // Version  | Date       | Expert in charge       | Changes applied
	  //----------|------------|------------------------|------------------------------
	  // 01.00.00 | yyyy-mm-dd | (name of expert)       | First released version
	  //===============================================================================
	END_REGION
	
	// Create process image
	#tempEnable := #enable;
	#tempSend := #send;
	#tempAdhocMode := #adhocMode;
	
	IF #tempAdhocMode = TRUE THEN // ignore parameter rcvLen in ad-hoc mode
	  #tempRcvLen := 0;
	ELSIF #tempAdhocMode = FALSE THEN // normal mode
	  #tempRcvLen := #rcvLen;
	END_IF;
	#tempSendLen := #sendLen;
	
	REGION ENABLING/DISABLING
	  IF (#tempEnable = TRUE) AND (#statStatus = #STATUS_NO_CALL) THEN // Enable FB
	    // First call; initialize FB
	    #statValid := TRUE;
	    #statBusy := TRUE;
	    #statError := FALSE;
	    #statErrorUserCleared := FALSE;
	    #statErrorAutoCleared := FALSE;
	    #statSubfunctionStatus := #SUB_STATUS_NO_ERROR;
	    #statFBErrorState := 0;
	    #diagnostics := #statEmptyDiagnostics;
	    #statDisablingCompleted := FALSE;
	    // State machine - start functionality
	    #statFBState := #FB_STATE_ENABLING;
	    #statStatus := #STATUS_FIRST_CALL;
	    
	    #statConReq := FALSE;
	    #statDisconReq := FALSE;
	    #statRcvEnable := FALSE;
	    #statSendReq := FALSE;
	    #statRcvdLen := 0;
	    #statConnParam := #connParam;
	    #statDone := FALSE;
	    #statNdr := FALSE;
	    
	  ELSIF (#tempEnable = FALSE) AND (#statEnableOld = TRUE) THEN // Disable FB
	    #statFBState := #FB_STATE_DISABLING;
	    
	  ELSIF (#statStatus = #STATUS_FIRST_CALL) THEN // Set status identifier of subsequent call
	    #statStatus := #STATUS_SUBSEQUENT_CALL;
	  END_IF;
	  
	  // Edge detection 'enable' input
	  #statEnableOld := #tempEnable;
	END_REGION ENABLING/DISABLING
	
	IF (#statStatus = #STATUS_NO_CALL) THEN // Nothing to do -> End here to reduce "system load"
	  RETURN;
	END_IF;
	
	REGION STATE_MACHINE
	  CASE #statFBState OF // State machine of FB
	    #FB_STATE_NO_PROCESSING:
	      REGION no processing
	        // No processing active (Note: this state must always be present and left empty)
	        ;
	      END_REGION no processing
	      
	    #FB_STATE_ENABLING: // Enabling active
	      #statConReq := true; // Set here to create new rising edge after partner refused connection
	      
	      REGION enabling
	        IF (#instTCon.DONE = true) THEN
	          #statConReq := false;
	          #statRcvEnable := true; // Enable receiving
	          #statErrorAutoCleared := false; // Reset possible errors
	          #statFBState := #FB_STATE_CONNECTED; // TODO: Replace with #FB_STATE_SEND if data is sent before anything is expected
	        ELSIF (#instTCon.ERROR = true) THEN
	          #statConReq := false;
	          #statStatus := #ERR_TCON;
	          #statSubfunctionStatus := #instTCon.STATUS;
	          #statFBErrorState := #statFBState;
	          
	          IF (#instTCon.STATUS = #ERR_OUC_PARTNER_TERMINATED_CONNECTION)
	            OR (#instTCon.STATUS = #ERR_OUC_CANNOT_REACH_PARTNER)
	          THEN // Error can be cleared automatically
	            #statErrorAutoCleared := true;
	            // Remain in enabling state and wait for partner
	          ELSE // Error must be cleared by user
	            #statErrorUserCleared := true;
	          END_IF;
	        END_IF;
	      END_REGION enabling
	      
	    #FB_STATE_CONNECTED: // connection is established
	      REGION connected
	        // Further processing state
	        // TODO: Use this state for further application specific code
	        // In this template sending and receiving is processed simultaneously. If sequential processing is required, move the following code to separate states
	        
	        
	        #statNdr := false; // reset ndr after one cycle
	        
	        REGION receive
	          IF #instTRcv.NDR = TRUE THEN // new data received
	            REGION ndr
	              #statNdr := TRUE;
	              #statRcvdLen := #instTRcv.RCVD_LEN; // Save received length
	            END_REGION
	            
	          ELSIF (#instTRcv.ERROR = true) THEN
	            REGION error occured
	              #statErrorAutoCleared := true; // In this template no error of TRCV causes the processing of the FB to stop
	              #statStatus := #ERR_TRCV;
	              #statSubfunctionStatus := #instTRcv.STATUS;
	              #statFBErrorState := #statFBState;
	            END_REGION
	            
	          ELSIF (#instTRcv.ERROR = FALSE AND #statStatus = #ERR_TRCV) THEN
	            REGION error cleared
	              #statErrorAutoCleared := FALSE; // Reset any errors
	              #statStatus := #STATUS_SUBSEQUENT_CALL; // Reset status
	            END_REGION
	          END_IF;
	        END_REGION receive
	        
	        REGION send
	          REGION trigger
	            IF #tempSend = TRUE AND #statSendOld = FALSE THEN
	              #statSendReq := TRUE;
	              
	            ELSIF (#tempSend = FALSE AND (#statDone = TRUE OR #statError = TRUE)) THEN // Reset outputs
	              #statDone := FALSE;
	              IF #statStatus = #STATUS_EXECUTION_FINISHED OR #statStatus = #ERR_TSEND THEN
	                #statErrorAutoCleared := FALSE; // Reset possible errors
	                #statStatus := #STATUS_SUBSEQUENT_CALL;
	              END_IF;
	            END_IF;
	            
	            #statSendOld := #tempSend;
	          END_REGION
	          
	          IF (#instTSend.DONE = true) THEN // data was sent succcessfully
	            REGION successful
	              #statSendReq := false;
	              #statDone := true;
	              #statStatus := #STATUS_EXECUTION_FINISHED;
	            END_REGION
	            
	          ELSIF (#instTSend.ERROR = true) THEN
	            REGION error occured
	              #statSendReq := false;
	              #statErrorAutoCleared := true; // In this template no error of TSEND causes the processing of the FB to stop
	              #statStatus := #ERR_TSEND;
	              #statSubfunctionStatus := #instTSend.STATUS;
	              #statFBErrorState := #statFBState;
	            END_REGION
	          END_IF;
	        END_REGION send
	        
	      END_REGION connected
	      
	    #FB_STATE_DISABLING: // Disabling active
	      REGION disabling
	        #statConReq := false;
	        #statSendReq := false;
	        #statRcvEnable := false;
	        #statDisconReq := true;
	        
	        IF (#instTDiscon.DONE = true OR #instTDiscon.ERROR = true) THEN // Ignore errors from TDISCON
	          #statDisconReq := false;
	          #statFBState := #FB_STATE_NO_PROCESSING;
	          #statDisablingCompleted := TRUE;
	        END_IF;
	      END_REGION disabling
	      
	    ELSE // Undefined state in state machine reached
	      REGION undefined state        
	        #statStatus := #ERR_UNDEFINED_STATE;
	        #statErrorUserCleared := TRUE;
	      END_REGION undefined state
	  END_CASE;
	END_REGION STATE_MACHINE
	
	REGION INSTRUCTIONS
	  // call TCON to establish the connection
	  #instTCon(REQ     := #statConReq,
	            ID      := #statConnParam.ID,
	            CONNECT := #statConnParam);
	  
	  // call TDISCON to disconnect the connection
	  #instTDiscon(REQ := #statDisconReq,
	               ID  := #statConnParam.ID);
	  
	  // call TRCV to receive data
	  #instTRcv(EN_R  := #statRcvEnable,
	            ID    := #statConnParam.ID,
	            LEN   := #tempRcvLen,
	            ADHOC := #tempAdhocMode,
	            DATA  := #rcvdData); // TODO: Replace with internal buffer if data shall be processed instead of output
	  
	  // call TSEND to send data
	  #instTSend(REQ  := #statSendReq,
	             ID   := #statConnParam.ID,
	             LEN  := #tempSendLen,
	             DATA := #sendData);
	END_REGION INSTRUCTIONS
	
	REGION OUTPUTS
	  // Write outputs
	  IF (#statDisablingCompleted = TRUE) THEN
	    REGION disabling
	      // Reset outputs if disabling completed
	      #statValid := FALSE;
	      #statBusy := FALSE;
	      #statError := FALSE;
	      #statErrorUserCleared := FALSE;
	      #statErrorAutoCleared := FALSE;
	      #statStatus := #STATUS_NO_CALL;
	      // TODO: Reset application specific outputs
	      #statRcvdLen := 0;
	      #statDone := false;
	      #statNdr := false;
	    END_REGION disabling
	    
	  ELSIF (#statErrorUserCleared = TRUE) AND (#statError = FALSE) THEN
	    REGION error user cleared
	      // Error can only be cleared by user; rising edge at enable input is needed to continue 
	      #statValid := FALSE;
	      #statBusy := FALSE;
	      #statError := TRUE;
	      // Write diagnostics
	      #diagnostics.status := #statStatus;
	      #diagnostics.subfunctionStatus := #statSubfunctionStatus;
	      #diagnostics.stateNumber := #statFBErrorState;
	      // execution aborted --> set state no processing
	      #statFBState := #FB_STATE_NO_PROCESSING;
	    END_REGION error user cleared
	    
	  ELSIF (#statErrorAutoCleared = TRUE) AND (#statError = FALSE) THEN
	    REGION error auto cleared
	      // Error can be reset by FB automatically
	      #statValid := FALSE;
	      #statBusy := TRUE;
	      #statError := TRUE;
	      // Write diagnostics
	      #diagnostics.status := #statStatus;
	      #diagnostics.subfunctionStatus := #statSubfunctionStatus;
	      #diagnostics.stateNumber := #statFBErrorState;
	    END_REGION error auto cleared
	    
	  ELSIF (#statErrorAutoCleared = FALSE) AND (#statErrorUserCleared = FALSE) AND (#statError = TRUE) THEN
	    REGION after user/auto cleared
	      // If autocleared error is acknowledged
	      #statValid := TRUE;
	      #statBusy := TRUE;
	      #statError := FALSE;
	      #statStatus := #STATUS_SUBSEQUENT_CALL;
	    END_REGION after user/auto cleared
	  END_IF;
	  
	  REGION Write static values to outputs
	    #valid := #statValid;
	    #done := #statDone;
	    #ndr := #statNdr;
	    #busy := #statBusy;
	    #error := #statError;
	    #status := #statStatus;
	    // if ENO mechanism is not used / not wanted replace the following line by --> ENO := TRUE;
	    ENO := NOT #statError;
	    // TODO: Write application specific static values to outputs
	    #rcvdLen := #statRcvdLen;
	  END_REGION Write static values to outputs
	END_REGION OUTPUTS
	
END_FUNCTION_BLOCK

