﻿<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1">
  <POU Name="LocalEWinch" Id="{e10eea01-0ddc-4374-bb74-072e41c99069}" SpecialFunc="None">
    <Declaration><![CDATA[(*
Purpose
=======

This class is providing all fundamental modes of a electric winch, which are then used to build 
winches like CTD or Trawl winches.

Description
===========

A mode is started by a run<xxx> method. Modes are

=================== =====================================================================
Mode                Description
=================== =====================================================================
AutoDirect          This is a mode for Haulin and Payout without PID controller.  
				    The velocity is taken from the PositionGenerator. This mode is used  
				    for simple winches without the demand for exact positioning
Auto			    This mode for Haulin and Payout is for exact positing under 
				    inclusion of AHC.  
ManualMode		    Control of velocity of a winch via manual devices
ManualModeAHC	    Control of position of a winch via manual devices which includes
				    AHC
PassivePayout	    Control of the trajectory of a payout via tension, which demands an 
				    external force which is pulling the wire
=================== =====================================================================

Methods And Properties
======================
*)
FUNCTION_BLOCK LocalEWinch IMPLEMENTS IWinch
VAR_INPUT
 	Config									: REFERENCE TO ConfigElectricWinch; 
	OpValues								: REFERENCE TO OpValueWinch;
	Drive									: p7elec.IDrive; 
	Encoder									: p7elec.IEncoder;
	HCController							: IHeaveCompController;
	SpoolHeadPosition						: LREAL;
	BrakeHPU								: p7type.IHPUBrake;
END_VAR
VAR
	{attribute 'hide'} setpointPID 							: LREAL;
	{attribute 'hide'} torqueLimit 							: LREAL;
	{attribute 'hide'} errorLimit							: LREAL;
	{attribute 'hide'} sign									: LREAL;
	{attribute 'hide'} driveOk								: BOOL;
	{attribute 'hide'} driveRun 							: BOOL;
	{attribute 'hide'} cmd 									: WinchCMD;
	
	{attribute 'hide'} is_ready								: BOOL;
	{attribute 'hide'} timerPrepare							: TON;
	{attribute 'hide'} timerBrakeHPU						: TON;
	// new spooling variables
	_InboundVirtualWireDirection 							: BOOL;	// used in virtual wire and offset calculations 
	_pulsesPerLayer											: LINT; // used in virtual wire and offset calculations
	_singleThread_virtualWirePosition						: LREAL; // NEW value
END_VAR
VAR
	
	{attribute 'hide'} _started 								: BOOL;
	//
	// Basic vars
	// 
	{attribute 'hide'} PID_cycle_timer							: p6com.CycleGenerator;
	
	// 
	//
	//
	{attribute 'hide'} _meas_velocity							: LREAL;
	{attribute 'hide'} _meas_acceleration						: LREAL;
	{attribute 'hide'} _meas_position 							: LREAL;
	{attribute 'hide'} _meas_radius								: LREAL; // current radius where we are now
	{attribute 'hide'} _meas_tension							: LREAL;
	{attribute 'hide'} _meas_speed								: LREAL;
	{attribute 'hide'} _meas_break_failing						: BOOL;
	
	// 
	// Spooling
	//
	{attribute 'hide'} _diamondScrew_VirtualWirePosition 					: LREAL;
	{attribute 'hide'} _spool_layer 							: DINT;
	{attribute 'hide'} _loopsPerLayer							: LREAL;
	{attribute 'hide'} _pulses_per_meter						: LREAL;
	
	//
	// State machine
	//
	{attribute 'hide'} _state									: EWinchSM := EWInchSM.IDLE;
	{attribute 'hide'} _stateclass								: EWinchSMClass;
	{attribute 'hide'} _event 									: BOOL;
	{attribute 'hide'} _eventId								: p6com.EventIdentifier;
	{attribute 'hide'} _cmd 									: OneShotWinchCMD;
	{attribute 'hide'} _opstate 								: p6com.P6OpState;
	
	//
	// Mode related vars
	//
	
	// _parameter vars are interpreted according to their mode
	// Research:
	// _parameter1 : Max velocity
	// _parameter2 : Acceleration
	// _parameter3: Max tension
	//
	// Fishery:
	// _parameter1: Max velocity
	// _parameter2: Max tension
	// _parameter3: Min tension
	// _parameter4: rampdown time
	{attribute 'hide'} _parameter1								: LREAL; 
	{attribute 'hide'} _parameter2								: LREAL;
	{attribute 'hide'} _parameter3								: LREAL;
	{attribute 'hide'} _parameter4								: LREAL;
	{attribute 'hide'} _setpoint								: LREAL;
	
	{attribute 'hide'} _ahc										: BOOL;
	
	{attribute 'hide'} _ahcposition								: LREAL;
	{attribute 'hide'} _ahcvelocity								: LREAL;
	{attribute 'hide'} _ahcacceleration 						: LREAL;

	{attribute 'hide'} _velocity 								: LREAL;
	{attribute 'hide'} _velocityRel								: LREAL;
	{attribute 'hide'} _torque									: LREAL;
	{attribute 'hide'} _accleration								: LREAL;
	//{attribute 'hide'} _tensionMax								: LREAL;
	{attribute 'hide'} _tensionRel								: LREAL;
	{attribute 'hide'} _tensionMin								: LREAL;
	{attribute 'hide'} _speed									: LREAL;
	{attribute 'hide'} _torqueRel								: LREAL;

	
	//
	// Signals 
	// 
	{attribute 'hide'} ready_auto 								: BOOL;
	{attribute 'hide'} stop_cond  								: BOOL;	
	{attribute 'hide'} errorBrake								: BOOL;
	
	//
	// Property related values
	//
	{attribute 'hide'} _lengthError							: LREAL;

	//
	// misc
	//
END_VAR

]]></Declaration>
    <Implementation>
      <ST><![CDATA[
// A winch can't work when an encoder is missing. In the case
// The encoder is from the drive, whe have to wait at 2 cycles before the
// value is valid
IF (THIS^.Encoder = 0) AND config.Option_DoCalcs THEN
	RETURN;
ELSIF Encoder <> 0 THEN
	Encoder.DirectionValue := config.Option_SetEncoderDirection;
END_IF

// The process timer has to be used for analog signals, 
// to run the pid controllers and the calculation for velocity 
PID_cycle_timer.update(In:=p6com.CycleTime_PID0, PLCTask_CycleTime := p6com.CycleTime_Task0);
	
IF (NOT _started) THEN
	_started := TRUE;	
END_IF;


//
// Calculcate values of the winch like length and tension
//
_meas_speed := Drive.getSpeed();
IF config.Option_DoCalcs THEN
	_calcPositionVelocityRadiusTension2();
END_IF
IF config.Option_HasSpooling THEN
	_calcHeadPositionForSpooling();		// old function, 
	_calcVirtualWirePosition(); 		// new code, made on Skreigrunn for single thread screw
	_checkAndChangeSpoolDirection();	// new code, will change virtual spool direction if bit is toggled.
END_IF

_meas_break_failing := _isBrakeFailing();

// 
// Reading of AHC values
//

IF (HCController <> 0) AND config.Option_HasAHC THEN
	_ahcposition 		:= HCController.HCPosition;
	_ahcvelocity 		:= HCController.HCVelocity;
	IF _ahc AND (HCController.OpState <> p6com.P6OpState.RUN) THEN
		HCController.setOperation(HeaveCompCMD.START);
	ELSIF (NOT _ahc) AND (HCController.OpState = p6com.P6OpState.RUN) THEN
		HCController.setOperation(HeaveCompCMD.STOP);
	END_IF
ELSE
	_ahcposition 		:= 0.0;
	_ahcvelocity 		:= 0.0;
END_IF

//
// Signals
//

driveOk 	:= Drive.State = p6com.P6State.OP;
driveRun 	:= Drive.OpState = p6com.P6OpState.RUN; 

timerPrepare(IN:=_opstate = p6com.P6OpState.PREPARE, PT:=T#200MS);

timerBrakeHPU(IN:= _opstate = p6com.P6OpState.PREPARE, PT:=config.TimeForBrakePressure);

IF BrakeHPU <> 0 THEN
	errorBrake := BrakeHPU.Error;
ELSE
	errorBrake := FALSE;
END_IF;

cmd 		:= _cmd.Get();

//
// State machine
//

IF cmd = WinchCMD.FASTSTOP THEN
	_state := EWinchSM.FASTSTOP;
	
ELSIF cmd = WinchCMD.FASTSTOP_OFF THEN	
	_state := EWinchSM.IDLE;
END_IF;

CASE _state OF
	
	EWinchSM.FASTSTOP:
		_state := EWinchSM.FASTSTOP_IDLE;
		IF config.Option_ActivateOnEStop AND Drive.OpState = p7type.P6OpState.STOPPED THEN 
			Drive.setOperation(DriveCMD.ACTIVATE);
		END_IF;
		
	EWinchSM.FASTSTOP_IDLE:
		;
	//
	// Idle
	//
	EWinchSM.Idle:
		_opstate := p6com.P6OpState.STOPPED;
		_state 				:= EWinchSM.IDLE_RUN;
		Drive.setOperation(DriveCMD.STOP);
 
	EWInchSM.IDLE_RUN:
		_opstate := p6com.P6OpState.STOPPED;
		IF (NOT driveOk) THEN 
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);
		ELSE
			CASE cmd OF
				WinchCMD.MANUAL: 	_state := EWinchSM.MANUAL; 
						_opstate := p6com.P6OpState.PREPARE;
				WinchCMD.AUTO: 		_state := EWinchSM.AUTO; 
						_opstate := p6com.P6OpState.PREPARE;
				WinchCMD.TORQUE: 	_state := EWinchSM.TORQUE;
						_opstate := p6com.P6OpState.PREPARE;
				WinchCMD.SLAVE: 	_state := EWinchSM.SLAVEMODE;
						_opstate := p6com.P6OpState.PREPARE;
			END_CASE
		END_IF
		
	//
	// Manual
	//
	// Parameters: 
	// setpoint : -1 .. 1 related to SpeedMax
	// Parameter1 : Relative Max Torque 
	// Parameter2 : -
	// Parameter3 : - 
	// Parameter4 : -
	EWinchSM.MANUAL: 
		_opstate := p6com.P6OpState.PREPARE;
		IF Drive.isActivationPossible() AND _brakePressureOK() THEN
			_state := EWinchSM.MANUAL_PREOP1;
			Drive.setOperation(DriveCMD.ACTIVATE);
		ELSIF (errorBrake OR timerBrakeHPU.Q) THEN
			_state := EWinchSM.IDLE;			
		END_IF;
	
	EWinchSM.MANUAL_PREOP1:
		_opstate := p6com.P6OpState.PREPARE;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);		
		ELSIF drive.OpState = p6com.P6OpState.STOPPED AND timerPrepare.Q THEN
			_state := EWinchSM.IDLE;
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
			_opstate := p6com.P6OpState.STOPPED;
		ELSIF driveRun AND timerPrepare.Q THEN
			_state := EWinchSM.MANUAL_OP;
		END_IF;

	EWinchSM.MANUAL_OP:
		_opstate := p6com.P6OpState.RUN;
		
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF NOT driveRun THEN
			_state := EWinchSM.IDLE;			
		ELSE		
			CASE cmd OF
				WinchCMD.FASTSTOP: 	_state := EWinchSM.IDLE;
						_opstate := p6com.P6OpState.NOTAVAILABLE;
				WinchCMD.STOP: 		_state := EWinchSM.MANUAL_EXIT1;
				WinchCMD.AUTO: 		_state := EWinchSM.AUTO;
						_opstate := p6com.P6OpState.PREPARE;
				WinchCMD.TORQUE: 	_state := EWinchSM.TORQUE;
						_opstate := p6com.P6OpState.PREPARE;
				WinchCMD.SLAVE: 	_state := EWinchSM.SLAVEMODE;
						_opstate := p6com.P6OpState.PREPARE;
			END_CASE
		END_IF
		
	EWinchSM.MANUAL_EXIT1:
		_opstate := p6com.P6OpState.STOPPED;
		_state := EWinchSM.MANUAL_EXIT2;			
		
	EWinchSM.MANUAL_EXIT2:
		_opstate := p6com.P6OpState.STOPPED;
		_state := EWinchSM.IDLE;			
		

		
		
		

	//
	// Constant Torque
	//
	// Parameters: 
	// setpoint : 	0 .. 1 related to Drive.TorqueRelMax() 
	// 				Att: Boosting can lowering the value after a time period 
	// Parameter1 : Max speed [RPM] for the case no force is applied on the wire. This is related to the RPM after the gear!
	//				In any case is the speed limited in a Drive instance. If zero, mode is stopping   
	// Parameter2 : -
	// Parameter3 : - 
	// Parameter4 : -
	EWinchSM.TORQUE:
		_opstate := p6com.P6OpState.PREPARE;
		IF errorBrake  OR timerBrakeHPU.Q THEN
			_state := EWinchSM.IDLE;			
		ELSIF Drive.isActivationPossible() AND _brakePressureOK() THEN
			_state := EWinchSM.TORQUE_PREOP1;
			Drive.setOperation(DriveCMD.ACTIVATE);
		END_IF;
		
	EWinchSM.TORQUE_PREOP1:
		_opstate := p6com.P6OpState.PREPARE;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);		
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF driveRun AND timerPrepare.Q THEN
			_state := EWinchSM.TORQUE_PREOP2;
		ELSIF drive.OpState = p6com.P6OpState.STOPPED AND timerPrepare.Q THEN
			_state := EWinchSM.IDLE;
		END_IF;
	
	EWinchSM.TORQUE_PREOP2:
		_opstate := p6com.P6OpState.PREPARE;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);		
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF driveRun THEN
			_state := EWinchSM.TORQUE_OP;
		ELSIF drive.OpState = p6com.P6OpState.STOPPED THEN
			_state := EWinchSM.IDLE;
		END_IF;
		
	EWinchSM.TORQUE_OP:
		_opstate := p6com.P6OpState.RUN;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF NOT driveRun THEN
			_state := EWinchSM.IDLE;			
		ELSE		
			CASE cmd OF
				WinchCMD.FASTSTOP: 	_state := EWinchSM.IDLE;
				WinchCMD.STOP: 		_state := EWinchSM.TORQUE_EXIT1;
				WinchCMD.MANUAL:	_state := EwinchSM.MANUAL;
			END_CASE
		END_IF
		
	EWinchSM.TORQUE_EXIT1:
		_opstate := p6com.P6OpState.STOPPED;
		_state := EWinchSM.TORQUE_EXIT2;			
		
	EWinchSM.TORQUE_EXIT2:
		_opstate := p6com.P6OpState.STOPPED;
		_state := EWinchSM.IDLE;			


	//
	// Slave Mode
	//
	// Parameters: 
	// setpoint : 	Speed [RPM] related to Drive.SpeedMax()
	// Parameter1 : Torque Relative 0..Drive.TorqueRelMax()
	// Parameter2 : -
	// Parameter3 : - 
	// Parameter4 : -
	EWinchSM.SLAVEMODE:
		_opstate := p6com.P6OpState.PREPARE;
		IF  Drive.isActivationPossible() AND  _brakePressureOK() THEN
			_state := EWinchSM.SLAVEMODE_PREOP1;
			Drive.setOperation(DriveCMD.ACTIVATE);
		ELSIF errorBrake  OR timerBrakeHPU.Q THEN
			_state := EWinchSM.IDLE;
		//{warning 'Check this piece of code!!!!'}
		ELSIF driveRun THEN	// don't waste time if drive is running
			_state := EWinchSM.SLAVEMODE_OP;				
		END_IF;
		
	EWinchSM.SLAVEMODE_PREOP1:
		_opstate := p6com.P6OpState.PREPARE;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);		
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF driveRun AND timerPrepare.Q THEN
			_state := EWinchSM.SLAVEMODE_PREOP2;
		ELSIF drive.OpState = p6com.P6OpState.STOPPED AND timerPrepare.Q THEN
			_state := EWinchSM.IDLE;
		END_IF;
		
	EWinchSM.SLAVEMODE_PREOP2:
		_opstate := p6com.P6OpState.PREPARE;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);		
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF driveRun THEN
			_state := EWinchSM.SLAVEMODE_OP;
		ELSIF drive.OpState = p6com.P6OpState.STOPPED THEN
			_state := EWinchSM.IDLE;
		END_IF;
		
	EWinchSM.SLAVEMODE_OP:
		_opstate := p6com.P6OpState.RUN;
		IF NOT driveOk THEN
			_setFault(EWinchSM.ERROR, p6com.EventIdentifier.WINCHBASE_DRIVE_IS_NOT_AVAILABLE);
		ELSIF errorBrake THEN
			_state := EWinchSM.IDLE;			
		ELSIF NOT driveRun THEN
			_state := EWinchSM.IDLE;			
		ELSE		
			CASE cmd OF
				WinchCMD.STOP: 		_state := EWinchSM.SLAVEMODE_EXIT1;
				WinchCMD.MANUAL: 	_state := EWinchSM.MANUAL_OP;
			END_CASE
		END_IF
		
	EWinchSM.SLAVEMODE_EXIT1:
		_opstate := p6com.P6OpState.STOPPED;
		_state := EWinchSM.MANUAL_OP;			
		

	//
	// Error
	//
	EWinchSM.ERROR: 
		_opstate := p6com.P6OpState.NOTAVAILABLE;		
		IF cmd = WinchCMD.RESET THEN
			Drive.setOperation(p7hyd.DriveCMD.RESET);
		ELSIF driveOk THEN
			_event := FALSE;
			_state := EWinchSM.IDLE;
		END_IF
	
END_CASE

	
//
// Statemachine output
// 
_stateclass := _state / 100;
CASE _stateclass OF
	EWinchSMClass.Idle:
		_lengthError := 0.0;
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax());
		
		
	EWinchSMClass.FASTSTOP:
		_lengthError := 0.0;
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax());
			
	//
	// Manual
	//
	EWinchSMClass.MANUALPREOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
		_lengthError := 0.0;
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax());
		
	EWinchSMClass.MANUALOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
		_lengthError := 0.0;
			
		IF config.Option_TensionLimitManMode THEN
			// get the maximum torqueRel setting for the maximum tension (in ton), does also limit to 1% minimum
			// do not limit 
			torqueLimit := _tensionToTorqueRel(_parameter1 * Config.TensionMax); //1 (or potmeter value) * inputs max tension in tons 
		ELSE
			// Never go under 1% torque - Skreigrunn justert til 0 igjen...
			// Liaoyou and all other projects needs this to be 1%
			torqueLimit := LIMIT(0.01, _parameter1, 1.0) * Drive.getTorqueRelMax();		
		END_IF
	
		Drive.setProcessValues(
			LIMIT(-1,_setpoint,1) * Drive.getSpeedMax(), 
			torqueLimit
		);		
		


	//
	// Constant Torque
	//
	EWinchSMClass.TORQUEPREOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax());
		
	EWinchSMClass.TORQUEOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
		Drive.setProcessValues(
			Speed := _parameter1 ,// -ABS(_parameter1),
			TorqueRel := LIMIT(-1.0, _setpoint, 1.0) * Drive.getTorqueRelMax(), // allow negative torque
		); 

	//
	// Slave Mode
	//
	
	EWinchSMClass.SLAVEMODEPREOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax()); //    torqueLimit );
		
	EWinchSMClass.SLAVEMODEOP:
		IF BrakeHPU <> 0 THEN
			BrakeHPU.setActive();
		END_IF
					
		// Limit torque (_parameter1) in auto mode.								// new on Liaoyou 050923 EH
		IF config.Option_TensionLimitAutoModes THEN
			// get the maximum torqueRel setting for the maximum tension (in ton)
			torqueLimit := LIMIT(0.01, _parameter1, _tensionToTorqueRel(Config.TensionMax)); 
		ELSE
			// Never go under 1% torque 
			torqueLimit := LIMIT(0.01, _parameter1, Drive.getTorqueRelMax()) ; // I presume torque input already is scaled to torqueRelMax   	
		END_IF
			
		Drive.setProcessValues(
			LIMIT(-1,_setpoint,1) * Drive.getSpeedMax(), 
			torqueLimit										//_parameter1
		);		
					
	// 
	// Idle
	//
	ELSE
		_lengthError := 0.0;
		Drive.setProcessValues(0.0, Drive.getTorqueRelMax());	

END_CASE

]]></ST>
    </Implementation>
    <Method Name="setVirtualWirePosition" Id="{041223ca-f6f2-0cfd-21df-8e4093837409}">
      <Declaration><![CDATA[{attribute 'TcRpcEnable'}
METHOD setVirtualWirePosition : BOOL // new code for single thread
VAR_INST
	offsetPosition	:LREAL;
	localHead		: LREAL;
	offsetPulses	: LINT;
	error 			: BOOL;
	Local_number : LINT;
	runCount : UINT;
	Invert		:	INT;
	encPulsesForCalculation : LINT;
	
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[// calculate a new encoder offset valut so that the new virtual wire position is equal to spool head position 
// WITHOUT changing layer

THIS^._calcVirtualWirePosition();
localHead := _singleThread_virtualWirePosition;
runCount := runCount +1;

offsetPosition := THIS^.SpoolHeadPosition - THIS^._singleThread_virtualWirePosition;

IF _pulsesPerLayer = 0 THEN		// missing config, ABORT
	error := TRUE;
	RETURN;	
END_IF

// how many pulse equivalents do we need to move the spool head
offsetPulses := TRUNC(offsetPosition * LINT_TO_REAL(_pulsesPerLayer));

////Invert if counter is positive
//encPulsesForCalculation :=  THIS^.Encoder.ValueRaw + OpValues.encoder_offset_wire_calc;
//IF encPulsesForCalculation > 0 THEN
//	invert := -1;
//ELSE
//	invert := 1;	
	
//END_IF

// add or subtract offset pulses to existing offset based on virtual wire direction
IF _InboundVirtualWireDirection  THEN
	OpValues.encoder_offset_wire_calc := OpValues.encoder_offset_wire_calc - offsetPulses ; //* invert; // (was '+')
ELSE
	OpValues.encoder_offset_wire_calc := OpValues.encoder_offset_wire_calc + offsetPulses ; //* invert;  // (was '-')
END_IF

Local_number := OpValues.encoder_offset_wire_calc ;


]]></ST>
      </Implementation>
    </Method>
    <Property Name="ID" Id="{0fdffa89-76b6-4db4-9a5f-129b16bdd60d}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY ID : DINT]]></Declaration>
      <Get Name="Get" Id="{63cf7a8a-9b4e-436e-ae8e-0698eb06577d}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF __ISVALIDREF(Config) THEN
	ID := Config.ID;
ELSE
	ID := 0;
END_IF
]]></ST>
        </Implementation>
      </Get>
      <Set Name="Set" Id="{f6f81b61-a5be-419c-9a10-53cdf51ab183}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF __ISVALIDREF(Config) THEN
	Config.ID := ID;
END_IF
]]></ST>
        </Implementation>
      </Set>
    </Property>
    <Property Name="Velocity" Id="{26d7f5df-f3e3-4413-98f2-32b80bba5a6a}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY Velocity : LREAL]]></Declaration>
      <Get Name="Get" Id="{92c3da20-b520-4415-84e3-e601b91328f6}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Velocity := _meas_velocity;
]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="IsRotating" Id="{28867acc-046d-4943-ae3f-55423b0dfb87}">
      <Declaration><![CDATA[(* When the winch is rotating faster than config.winchIsRotatingValue this property is true *)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY IsRotating : BOOL]]></Declaration>
      <Get Name="Get" Id="{e595d241-0d81-4864-b1b1-7acffb2f49bf}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF THIS^.Drive <> 0 THEN
	IsRotating := NOT THIS^.Drive.isAtZeroSpeed();
ELSE
	IsRotating := FALSE;
END_IF
]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="Torque" Id="{31ee8869-1ade-400f-bac5-51ff7ac2b525}">
      <Declaration><![CDATA[(* Torque in NM*)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY Torque : LREAL]]></Declaration>
      <Get Name="Get" Id="{f53b51b5-63f2-483f-a5ed-4f16f0dd8249}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[Torque := THIS^.Drive.getTorque();

]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="PulsesPerMeter" Id="{3411eca8-9c16-4564-b4a1-9d309e282d6c}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY PulsesPerMeter : LREAL]]></Declaration>
      <Get Name="Get" Id="{5f4ede5b-c1a5-4a16-b5e3-81bca0db2720}">
        <Declaration><![CDATA[VAR
	LoopsPerLayer : LREAL;
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[LoopsPerLayer := _NoLoopPerLayer();

IF config.Width > 0 THEN
	IF Config.Option_DrumEncoder THEN
		_pulses_per_meter := THIS^.Encoder.Resolution * LoopsPerLayer /  config.Width;
	ELSE
		_pulses_per_meter := THIS^.Encoder.Resolution * LoopsPerlayer * Drive.getGearRatio()/ config.Width;
	END_IF
ELSE
	_pulses_per_meter := 0;
END_IF


PulsesPerMeter := _pulses_per_meter;]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="_isBrakeFailing" Id="{34c9a5f8-b383-44ed-b834-3deed1243247}">
      <Declaration><![CDATA[METHOD PRIVATE _isBrakeFailing : BOOL
VAR_INST
	stoptimer		: TON;
	ref				: LINT;
	state 			: INT;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[IF THIS^.Encoder = 0 THEN
	_isBrakeFailing := FALSE;
	RETURN;
END_IF

stoptimer(IN:= THIS^.Drive.OpState = p6com.P6OpState.STOPPED OR THIS^.Drive.OpState = p6com.P6OpState.NOTAVAILABLE, PT:= T#500MS);


CASE state OF
	0: 
		_isBrakeFailing := FALSE;
		IF stoptimer.Q THEN
			state := 10;
			ref := Encoder.ValueRaw;
		END_IF
		
	10:
		IF stoptimer.Q THEN 
			_isBrakeFailing := ABS(ref-THIS^.Encoder.ValueRaw) >  THIS^.Encoder.Resolution / 30;
		ELSE
			_isBrakeFailing := FALSE;
			state := 0;
		END_IF
			
	ELSE
		state := 0;
END_CASE
]]></ST>
      </Implementation>
    </Method>
    <Method Name="_calcHeadPositionForSpooling" Id="{4272bf3f-0cba-473c-8cb2-9b640cdfe4ce}">
      <Declaration><![CDATA[METHOD PRIVATE _calcHeadPositionForSpooling : BOOL
VAR_INST
	ResolutionTurn : LREAL;	
	ResolutionLayer : LINT;
	LoopsPerLayer : LREAL;
	Layer_before_offset : LINT;
	Layer : LINT;
	LayerPosition : LINT;
	LayerPositionNorm : LREAL;
	Deadzone : LREAL;
	

	LayerPositionNormWithDeadzoneNull : LREAL;
	HeadPositionPre : LREAL;
	HeadPosition : LREAL;
	HeadOff : LREAL;
	HeadLayer : LREAL;
	
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}

LoopsPerLayer := _NoLoopPerLayer();

_LoopsPerLayer := LoopsPerLayer;

IF Config.Option_DrumEncoder THEN
	ResolutionTurn := THIS^.Encoder.Resolution ;
ELSE
	ResolutionTurn := THIS^.Encoder.Resolution * Drive.getGearRatio();
END_IF

// uses by property
IF config.Width > 0 THEN
	IF Config.Option_DrumEncoder THEN
		_pulses_per_meter := THIS^.Encoder.Resolution * LoopsPerLayer /  config.Width;
	ELSE
		_pulses_per_meter := ResolutionTurn * LoopsPerlayer / config.Width;
	END_IF
ELSE
	_pulses_per_meter := 0;
END_IF

ResolutionLayer := TRUNC(ResolutionTurn * LoopsPerLayer);

IF ResolutionLayer = 0 THEN			// inconsistent config
	_diamondScrew_VirtualWirePosition := 0.5;
	RETURN;	
END_IF

Layer_before_offset:= THIS^.Encoder.ValueRaw / ResolutionLayer;
 IF THIS^.Encoder.ValueRaw < 0 THEN
	Layer_before_offset := Layer_before_offset - 1;
END_IF;

// Layer position in pulses  
LayerPosition := ABS(THIS^.Encoder.ValueRaw) MOD ResolutionLayer;

// Normalizing 
LayerPositionNorm := LINT_TO_LREAL(LayerPosition) / LINT_TO_LREAL(ResolutionLayer);	
IF THIS^.Encoder.ValueRaw < 0 THEN
	LayerPositionNorm := 1.0 - LayerPositionNorm;
END_IF;

// Add an offset because the initializing position is not null
LayerPositionNorm := LayerPositionNorm + OpValues.HeadOffset;

Layer := Layer_before_offset ;  //can be changed in if's below

IF LayerPositionNorm >= 1.0 THEN
	LayerPositionNorm := LayerPositionNorm - 1.0;
	Layer :=  Layer_before_offset + 1;
ELSIF LayerPositionNorm < 0.0 THEN
	LayerPositionNorm := LayerPositionNorm + 1.0;
	Layer :=  Layer_before_offset - 1;	
END_IF;

_spool_layer := LINT_TO_DINT(Layer);

// 
// Calc now the position for the head. The deadzone has to be included into the calculation
// 
Deadzone :=  (1.0 / LoopsPerLayer) * config.DeadzoneWidth;  // spooling will not move on the start of each layer 

LayerPositionNormWithDeadzoneNull := SEL(LayerPositionNorm>Deadzone, 0.0, LayerPositionNorm);
HeadPositionPre := LIMIT(0.0, (LayerPositionNormWithDeadzoneNull - Deadzone) / ((LoopsPerLayer-1.0)*Deadzone), 1.0);

IF config.Switch_EncoderSpoolDir THEN		// change dir
	IF (ABS(Layer) MOD 2) = 0 THEN				// even layer
		HeadPosition := 1.0 - HeadPositionPre;
	ELSE 
		HeadPosition := HeadPositionPre;		// odd layer
	END_IF
ELSE										// norm dir
	IF (ABS(Layer) MOD 2) = 1 THEN				// odd layer
		HeadPosition := 1.0 - HeadPositionPre;
	ELSE 
		HeadPosition := HeadPositionPre;		// even layer
	END_IF
END_IF

HeadPosition := LIMIT(0.0, HeadPosition, 1.0);

_diamondScrew_VirtualWirePosition := HeadPosition;
]]></ST>
      </Implementation>
    </Method>
    <Property Name="SpeedMax" Id="{475576e7-dbb8-4a9d-8330-81f7279a2569}">
      <Declaration><![CDATA[(* Get the max RPM for the drum *)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY SpeedMax : LREAL]]></Declaration>
      <Get Name="Get" Id="{111eafa0-895d-4781-9623-95ad5060d126}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[SpeedMax := THIS^.Drive.getSpeedMax(); 
]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="_setFault" Id="{48b3741c-358c-4122-a662-03bdafcb4caa}">
      <Declaration><![CDATA[METHOD PRIVATE _setFault : BOOL
VAR_INPUT
	state : EWinchSM;
	error_reason : p6com.EventIdentifier;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[_state := state;
_event := TRUE;
_eventId := error_reason;
]]></ST>
      </Implementation>
    </Method>
    <Method Name="_tensionToTorqueRel" Id="{4953458a-84b5-4b42-bf52-298c380494cc}">
      <Declaration><![CDATA[// Input maximum allowed tension(ton), returns torqueRel that corresponds to this value
METHOD PRIVATE _tensionToTorqueRel : LREAL
VAR_INPUT
	Tension : LREAL;
END_VAR
VAR_INST
	_maxTorqueRequested : LREAL;
	_relativeToMaxTorque : LREAL;
	_TorqueRelOutput : LREAL;
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}
IF Drive.getTorqueMax() > 0.0 THEN 													// input is max tons we should limit to
	_maxTorqueRequested := Tension*1000.0*9.81*_meas_radius ; 						// =Nm
	_relativeToMaxTorque := _maxTorqueRequested /  Drive.getTorqueMax(); 			// result should be 0-1 
	_TorqueRelOutput := _relativeToMaxTorque * Drive.getTorqueRelMax();   			// Multiply with maximum torqueRel, 
	_tensionToTorqueRel := LIMIT(0.01, _TorqueRelOutput, Drive.getTorqueRelMax()); 	// limit in case user asks for more tension than winch can supply
ELSE
	_tensionToTorqueRel := 0;
END_IF


 ]]></ST>
      </Implementation>
    </Method>
    <Method Name="_velocityToRPM" Id="{4aac796b-ea81-4780-bc49-7a87a3289fc6}">
      <Declaration><![CDATA[METHOD PRIVATE _velocityToRPM : LREAL
VAR_INPUT
	Velocity : LREAL;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}
IF _meas_radius > 0.0 THEN
	_velocityToRPM := 60.0 * Velocity / (p6com.Math.PI2 * _meas_radius);
ELSE
	_velocityToRPM := 0.0;
END_IF

 ]]></ST>
      </Implementation>
    </Method>
    <Method Name="setSpoolOffset" Id="{5424d876-1ba3-496a-9f2d-3ceae6640286}">
      <Declaration><![CDATA[{attribute 'TcRpcEnable'}
METHOD setSpoolOffset : BOOL
]]></Declaration>
      <Implementation>
        <ST><![CDATA[_calcAndSaveSpoolHeadOffset(THIS^.SpoolHeadPosition);


]]></ST>
      </Implementation>
    </Method>
    <Property Name="HeadPosition" Id="{574ba7d8-6f1b-4f88-ab71-bb6579bb14f0}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
(* Virtual head position for the spool system - diamond screw *)
{attribute 'TcRpcEnable'}
PROPERTY HeadPosition : LREAL]]></Declaration>
      <Get Name="Get" Id="{3ed54983-f23a-4152-9bad-646c7a5d1c50}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[

HeadPosition := _diamondScrew_VirtualWirePosition;

]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="Radius" Id="{5cc25cdf-d0a1-4d7c-99b9-a4911b93c2c8}">
      <Declaration><![CDATA[(* Get the radius of the drum with wire  in meter *)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY Radius : LREAL]]></Declaration>
      <Get Name="Get" Id="{0b1f4381-dfcc-4aa6-b8db-550747625015}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Radius := _meas_radius;
]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="TorqueRel" Id="{64891389-112a-417e-aa1f-a97f77a0e59c}">
      <Declaration><![CDATA[(* Torque in % (0-1)*)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY TorqueRel : LREAL]]></Declaration>
      <Get Name="Get" Id="{3a463f5e-43b6-40b5-8e49-ee4fe1facdd7}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[TorqueRel := THIS^.Drive.getTorqueRel();]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="Length" Id="{6e9136fb-dfde-45e7-9b9f-44ffd9e23b33}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
(* Lenght of the wire in meter *)
{attribute 'TcRpcEnable'}
PROPERTY Length : LREAL]]></Declaration>
      <Get Name="Get" Id="{e9103d59-9e4a-465f-b24f-0abed889b9cb}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Length := _meas_position;
]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="_configValueChangeCircumferenceOrLenght" Id="{72c85ad8-8553-4035-a46f-0d54afc797e9}">
      <Declaration><![CDATA[(*
Purpose
=======

This method calculates the value of circumference when wire length was changed and vice versa.
And this method makes cutof length calculations

*)
METHOD PRIVATE _configValueChangeCircumferenceOrLenght : BOOL
VAR_INST
	state : INT;
	WinchCalcs : WinchCalcs;
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[CASE state OF
	 0:
	 	IF Config.Circumference <> Config._Circumference THEN
			state := 10;
		END_IF
		
	10: // Circumference changed
		Config.WireLength := WinchCalcs.WinchDataToLength(Config.Circumference, Config.CoreDiameter, Config.Width, Config.WireDiameter, Config.WireDensity);
		// Store the new past
		Config._Circumference := Config.Circumference;
		// store the new operational value
		Opvalues.circumference := Config.Circumference;
		// New length is now zero
		THIS^.Encoder.setValueTo(0);
		state := 0;
					
END_CASE
]]></ST>
      </Implementation>
    </Method>
    <Property Name="OpState" Id="{7af64f1a-5963-4d3f-840e-f3db3b47f9aa}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
(* This is the unified state of the state of operation *)
{attribute 'TcRpcEnable'}
PROPERTY OpState : p6com.P6OpState]]></Declaration>
      <Get Name="Get" Id="{beb4df29-759f-4a2b-b0d5-2c225cb8b492}">
        <Declaration><![CDATA[
]]></Declaration>
        <Implementation>
          <ST><![CDATA[OpState := _opstate;


]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="Tension" Id="{7b7dd46c-c2cf-4fb7-b61d-6d23ea9506f0}">
      <Declaration><![CDATA[(* Get the tension in kg *)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY Tension : LREAL]]></Declaration>
      <Get Name="Get" Id="{725626cb-8c74-4443-8e2a-c7a52975bc34}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[Tension := _meas_tension;

]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="State" Id="{8535bd9f-39d3-44c6-9c58-6de72d59dbda}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
(* Get the state of the winch *)
{attribute 'TcRpcEnable'}
PROPERTY State : p6com.P6State]]></Declaration>
      <Get Name="Get" Id="{017eecc4-a4a0-4ae6-b784-538264d2aab3}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF _event THEN
	State := p6com.P6State.PREOP;
ELSE
	State := p6com.P6State.OP;
END_IF]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="setLengthNull" Id="{8699b0fb-5c57-4d54-b9ec-cc55ce088147}">
      <Declaration><![CDATA[// Resets length of wire to 0
{attribute 'TcRpcEnable'}
METHOD setLengthNull : BOOL
]]></Declaration>
      <Implementation>
        <ST><![CDATA[THIS^.Encoder.setValueTo(0);
// The circumference is measure the first time at the outer rim, which is the reference
// This has to be taken into account 
Opvalues.circumference := (_meas_radius + config.WireDensity * config.WireDiameter / 2) * math.PI2;
]]></ST>
      </Implementation>
    </Method>
    <Property Name="isReadyToActivate" Id="{8946bf4e-bb8c-4bf2-944f-6e283a82d149}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY isReadyToActivate : BOOL]]></Declaration>
      <Get Name="Get" Id="{1567d929-b23f-4971-90cc-8b3b070f0e84}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF THIS^.Drive <> 0 THEN
	isReadyToActivate := Drive.isActivationPossible() AND _opstate=p6com.P6OpState.STOPPED;
ELSE
	isReadyToActivate := FALSE;	
END_IF]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="LengthError" Id="{95a1b779-d893-4bd5-aef5-ac042be188fc}">
      <Declaration><![CDATA[(* Error in meter when running with trajectory generator *)
{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY LengthError : LREAL]]></Declaration>
      <Get Name="Get" Id="{d414d478-8356-4538-9488-6f706d590ba7}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[LengthError := _LengthError;

 ]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="setCircumferenceFromConfig" Id="{98c83d40-0e8b-0ece-2cc0-2703f5361ffa}">
      <Declaration><![CDATA[{attribute 'TcRpcEnable'}
METHOD setCircumferenceFromConfig : BOOL
]]></Declaration>
      <Implementation>
        <ST><![CDATA[Opvalues.circumference := config.Circumference;

]]></ST>
      </Implementation>
    </Method>
    <Property Name="Speed" Id="{9d96458a-07d0-4d0a-99c4-3b5da293a46e}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
// Get the RPM of the drum
{attribute 'TcRpcEnable'}
PROPERTY Speed : LREAL]]></Declaration>
      <Get Name="Get" Id="{5e44dab5-05f3-4583-a194-f2da68f2a866}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[Speed := _meas_speed;]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Property Name="TorqueMax" Id="{a272d536-cef5-42a0-a171-983202ee43da}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
// Max torque in NM 
{attribute 'TcRpcEnable'}
PROPERTY TorqueMax : LREAL]]></Declaration>
      <Get Name="Get" Id="{e4ce1f9b-b4ae-44b7-8858-5468b3dccd26}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[TorqueMax := THIS^.Drive.getTorqueMax();

]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="_calcPositionVelocityRadiusTension2" Id="{a83eaf26-4a27-0e35-1f9c-f358d43eaff1}">
      <Declaration><![CDATA[METHOD PRIVATE _calcPositionVelocityRadiusTension2 : BOOL
VAR_INST
	noLoopsPerLayer							: LREAL;
	no_rot_per_layer 						: LREAL;
	radius_avg 								: LREAL;
	radius0 								: LREAL;
	layerHeight								: LREAL;
	rotations								: LREAL;
	_deltaLen								: LREAL;	
	_meas_velocity_1						: LREAL;
	_meas_position_1						: LREAL;
	
	EncoderResolution 						: LREAL;
	VelTimer 								: TON;
	VelocityMeasTimeCalculated 				: TIME;	
	filter									: p6com.Filter1stDeg;
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[ 
// Test for a config value change of circumference or wire lenght. 
// Is one of them changed calculate the other one
IF NOT config.Option_DiameterIsConstant THEN
	_configValueChangeCircumferenceOrLenght();
END_IF

IF Drive<>0 THEN 
	IF config.Option_DrumEncoder  THEN
		EncoderResolution := THIS^.Encoder.Resolution;		
	ELSE
		EncoderResolution := THIS^.Encoder.Resolution * Drive.getGearRatio();		
	END_IF
ELSE
	EncoderResolution := 0;
END_IF

IF EncoderResolution < 1 THEN
	RETURN;
END_IF

noLoopsPerLayer := _NoLoopPerLayer();
layerHeight := config.WireDensity * config.WireDiameter;

rotations := LINT_TO_LREAL((THIS^.Encoder.Value * 1000 / LREAL_TO_LINT(EncoderResolution)))/1000.0;

IF config.Option_DiameterIsConstant THEN
	_meas_radius := config.Circumference / p6com.Math.PI2;
	radius_avg := _meas_radius;
ELSE
	
	radius0 := Opvalues.circumference / p6com.Math.PI2 - config.WireDiameter/2 + layerHeight/2  ; // 2;	// radius on last winding on layer (on full layer)
	IF config.FirstLayerUsage < 1 AND opvalues.circumference = config.Circumference  AND noLoopsPerLayer > 0 THEN
		// subtract  up to one layer height if top layer is not full
		radius0 := radius0 - ((LIMIT(0, 1-config.FirstLayerUsage, 1) * noLoopsPerLayer) / noLoopsPerLayer) * layerHeight ; // bugfix 140923 Erlend
 	END_IF
	
	_meas_radius := radius0 - layerHeight * rotations / noLoopsPerLayer;

	radius_avg := (radius0 + _meas_radius) / 2.0;
	
END_IF

IF PID_cycle_timer.Q THEN
	// Store the actual lenght for usage in the next cycle
	_meas_position_1 := _meas_position;

	// Calculates the new length in meter 
	_meas_position := radius_avg * p6com.Math.PI2 * rotations;
	
	// low pass filter 1. deg for the position difference
	filter(Value:= _meas_position - _meas_position_1, Coefficient := config.Alpha, Out=>_deltaLen);

	IF ABS(_deltaLen) < 0.0001 THEN
		_deltaLen := 0.0;
	END_IF

	// Calc the new velocity
	_meas_velocity_1 := _meas_velocity;
	_meas_velocity  := _deltaLen / (TIME_TO_LREAL(p6com.CycleTime_PID0) / 1000.0);
	
	_meas_acceleration := (_meas_velocity - _meas_velocity_1) / (TIME_TO_LREAL(p6com.CycleTime_PID0) / 1000.0);
END_IF;

IF _meas_radius > 0.01 THEN
	_meas_tension := Drive.getTorque() / ( 9.81 * _meas_radius);
ELSE 
	_meas_tension := 0.0;
END_IF

]]></ST>
      </Implementation>
    </Method>
    <Method Name="_brakePressureOK" Id="{aa78fb7b-0d1a-07db-3d08-55fec1352427}">
      <Declaration><![CDATA[METHOD PRIVATE _brakePressureOK : BOOL
]]></Declaration>
      <Implementation>
        <ST><![CDATA[IF THIS^.BrakeHPU <> 0 THEN 
	_brakePressureOK := THIS^.BrakeHPU.PressureOK;
ELSE
	_brakePressureOK := TRUE;
END_IF

]]></ST>
      </Implementation>
    </Method>
    <Method Name="_calcPositionVelocityRadiusTension" Id="{af66063a-ad48-4410-9f54-78e9aab11e42}">
      <Declaration><![CDATA[METHOD PRIVATE _calcPositionVelocityRadiusTension : BOOL
VAR_INST
	A, B : LREAL;

	_deltaLen								: LREAL;	
	_meas_velocity_1						: LREAL;
	_meas_position_1						: LREAL;
	
	EncoderResolution 						: LREAL;
	VelTimer 								: TON;
	VelocityMeasTimeCalculated 				: TIME;	
	filter									: p6com.Filter1stDeg;
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}
 
// Test for a config value change of circumference or wire lenght. 
// Is one of them changed calculate the other one
IF NOT config.Option_DiameterIsConstant THEN
	_configValueChangeCircumferenceOrLenght();
END_IF

IF Drive<>0 THEN 
	EncoderResolution := THIS^.Encoder.Resolution * Drive.getGearRatio();
ELSE
	EncoderResolution := 0;
END_IF

IF EncoderResolution < 1 THEN
	RETURN;
END_IF


IF config.Option_DiameterIsConstant THEN
	_meas_radius := config.Circumference / p6com.Math.PI2;
	A := config.Circumference / EncoderResolution;
ELSE
	// A is the cicrcumference divided by enc-resolution (circumference per encoder pulse) at 0m length
	A := (config.Circumference - config.WireDiameter) / EncoderResolution;
	
	// B is the part  which becomes subtracted from A every counter pulse 
	B := - (config.WireDensity * Math.PI * EXPT(config.WireDiameter,2)) / (config.Width * EXPT(EncoderResolution,2));
	_meas_radius := (A + LREAL#2.0 * B * LINT_TO_LREAL(THIS^.Encoder.Value)) * EncoderResolution / p6com.Math.PI2;
END_IF

IF PID_cycle_timer.Q AND p6com.CycleTime_PID0 > T#0S THEN
	// Store the actual lenght for usage in the next cycle
	_meas_position_1 := _meas_position;

	// Calculates the new length in meter 
	IF config.Option_DiameterIsConstant THEN
		_meas_position := A *  LINT_TO_REAL(Encoder.Value);
	ELSE
		// The calculation is based on a  2nd order polynom
		_meas_position := A *  LINT_TO_REAL(Encoder.Value) + B * EXPT(LINT_TO_LREAL(Encoder.Value),2);
	END_IF
	
	
	 // Check for measurment jumps because of switching the PLC
	IF ABS(_meas_position_1 - _meas_position) > 4.0 THEN
		_meas_position_1 := _meas_position;
	END_IF 
	
	// low pass filter 1. deg for the position difference
	filter(Value:= _meas_position - _meas_position_1, Coefficient := config.Alpha, Out=>_deltaLen);

	IF ABS(_deltaLen) < 0.0001 THEN
		_deltaLen := 0.0;
	END_IF

	// Store velocity for the next cycle
	_meas_velocity_1 := _meas_velocity;
	
	// Calc the new velocity
	_meas_velocity  := _deltaLen / (TIME_TO_LREAL(p6com.CycleTime_PID0) / 1000.0);
	
	_meas_acceleration := (_meas_velocity - _meas_velocity_1) / (TIME_TO_LREAL(p6com.CycleTime_PID0) / 1000.0);
END_IF;

IF _meas_radius <> 0.0 THEN
	_meas_tension := Drive.getTorque() / ( 9.81 * _meas_radius);
ELSE 
	_meas_tension := 0.0;
END_IF

]]></ST>
      </Implementation>
    </Method>
    <Property Name="VirtualWirePosition" Id="{b6c8e189-3623-0b9d-1e6a-ac0abebc778a}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
(* Virtual wire used by the spool system - single thread*)
{attribute 'TcRpcEnable'}
PROPERTY VirtualWirePosition : LREAL]]></Declaration>
      <Get Name="Get" Id="{ca3c82d3-c5d9-0e9f-1589-5c1e57539eaa}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[VirtualWirePosition := _singleThread_virtualWirePosition;]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="setLengthNullAtOrigin" Id="{b7a1ee0b-49d8-0bf0-3c41-a0557c8cef61}">
      <Declaration><![CDATA[{attribute 'TcRpcEnable'}
METHOD setLengthNullAtOrigin : BOOL
VAR_INPUT
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[config._Circumference := 0;
_configValueChangeCircumferenceOrLenght();
setLengthNull();
]]></ST>
      </Implementation>
    </Method>
    <Property Name="isBrakeFailing" Id="{c4f6979b-c645-40e9-897f-b43eb081face}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY isBrakeFailing : BOOL]]></Declaration>
      <Get Name="Get" Id="{157bd8bb-a016-4852-8e3e-74b0b261c3bb}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[isBrakeFailing := _meas_break_failing;]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="setOperation" Id="{ceb70234-1366-42a7-8eb6-05919fc71d43}">
      <Declaration><![CDATA[{attribute 'TcRpcEnable'}
METHOD setOperation : BOOL
VAR_INPUT
	CMD 			: WinchCMD;
	Setpoint		: LREAL;
	parameter1 		: LREAL;		
	parameter2		: LREAL;
	parameter3		: LREAL;
	parameter4		: LREAL;
	AHC				: BOOL;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[_cmd.Set(CMD);
THIS^._ahc := AHC;
THIS^._setpoint := Setpoint;
THIS^._parameter1 := parameter1;
THIS^._parameter2 := parameter2;
THIS^._parameter3 := parameter3;
THIS^._parameter4 := parameter4;
]]></ST>
      </Implementation>
    </Method>
    <Method Name="_NoLoopPerLayer" Id="{cf3def12-1e38-0f46-1946-559ca457d870}">
      <Declaration><![CDATA[METHOD PROTECTED _NoLoopPerLayer : LREAL

]]></Declaration>
      <Implementation>
        <ST><![CDATA[IF config.NoLoopPerLayer >= 5.0 THEN
	_NoLoopPerLayer := config.NoLoopPerLayer;
ELSIF config.WireDiameter > 0 THEN
	_NoLoopPerLayer := config.Width / config.WireDiameter;
ELSE
	_NoLoopPerLayer := 1;		// you fucked up!
END_IF;	


]]></ST>
      </Implementation>
    </Method>
    <Method Name="_checkAndChangeSpoolDirection" Id="{e1cfa1f6-ae38-0a5f-2fcd-24bb188a3cc1}">
      <Declaration><![CDATA[METHOD PRIVATE _checkAndChangeSpoolDirection : BOOL // new code for single thread spooling
VAR_INPUT
	
END_VAR
VAR_INST
	hasRunBefore 	: BOOL;
	lastState 	: BOOL;
	pulseCountChange : LINT;
	error : BOOL;
	encPulsesForCalculation : LINT;
	invert : INT;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[IF hasRunBefore = FALSE THEN
	hasRunBefore := TRUE;
	lastState := Config.Switch_EncoderSpoolDir ;
	RETURN;
END_IF

IF lastState = Config.Switch_EncoderSpoolDir THEN // no change since last tinme
	RETURN;
END_IF 

// direction is changed

// calculate how many pulses we will add/subtract. By adding or subtracting 2 x pulses we are on on current layer, we will end up on excact same spot on next or previous layer
pulseCountChange := TRUNC(_singleThread_virtualWirePosition * LINT_TO_REAL(_pulsesPerLayer) * 2);

//encPulsesForCalculation :=  THIS^.Encoder.ValueRaw + OpValues.encoder_offset_wire_calc;
//IF encPulsesForCalculation > 0 THEN
//	invert := -1;
//ELSE
//	invert := 1;	
	
//END_IF


// this will move the virtual layer to next/previous layer withot changing the absolute spool position 
IF _InboundVirtualWireDirection  THEN	// running back to start
	OpValues.encoder_offset_wire_calc := OpValues.encoder_offset_wire_calc + pulseCountChange;  // * invert;  // (was '-')
ELSE									// forward
	OpValues.encoder_offset_wire_calc := OpValues.encoder_offset_wire_calc - pulseCountChange; //  * invert;  // (was '+');
END_IF
	

lastState := Config.Switch_EncoderSpoolDir;]]></ST>
      </Implementation>
    </Method>
    <Property Name="IDName" Id="{e223a7e0-2fe2-432a-9cdd-857a92b3247f}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY IDName : P6IdName]]></Declaration>
      <Get Name="Get" Id="{7dcc8939-53f5-43e1-aa87-ebb9e3ec6262}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[IDName := THIS^.Config.name;]]></ST>
        </Implementation>
      </Get>
      <Set Name="Set" Id="{afe77697-d087-4398-872b-e436f498ce6f}">
        <Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
        <Implementation>
          <ST><![CDATA[THIS^.Config.name := IDName;
]]></ST>
        </Implementation>
      </Set>
    </Property>
    <Method Name="_RPMToVelocity" Id="{e9820286-3692-4d2e-9987-13baf1ed1a4d}">
      <Declaration><![CDATA[METHOD PRIVATE _RPMToVelocity : LREAL
VAR_INPUT
	RPM : LREAL;
END_VAR
]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}


_RPMToVelocity := RPM * (Math.PI2 * _meas_radius) / 60.0;

]]></ST>
      </Implementation>
    </Method>
    <Property Name="IsSpeedNull" Id="{f0ce8dd3-b107-4b04-a29b-2d195414f436}">
      <Declaration><![CDATA[{attribute 'monitoring' := 'call'}
{attribute 'TcRpcEnable'}
PROPERTY IsSpeedNull : BOOL]]></Declaration>
      <Get Name="Get" Id="{213d0243-9231-43b1-b945-9993bdcbd5a9}">
        <Declaration><![CDATA[]]></Declaration>
        <Implementation>
          <ST><![CDATA[IF THIS^.Drive <> 0 THEN
	IsSpeedNull := THIS^.Drive.isAtZeroSpeed();
ELSE
	IsSpeedNull := 0;
END_IF]]></ST>
        </Implementation>
      </Get>
    </Property>
    <Method Name="_calcAndSaveSpoolHeadOffset" Id="{fac04d24-6887-45cd-85a6-58c3fbe409c4}">
      <Declaration><![CDATA[METHOD PRIVATE _calcAndSaveSpoolHeadOffset : BOOL
VAR_INPUT
	SpoolHeadPosition : LREAL;
END_VAR
VAR_INST
	offset_pre : LREAL;
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}

OpValues.HeadOffset := 0.0;			// delete previous offset
_calcHeadPositionForSpooling();		// and calculate a theoretical wire position

// Calc the difference between virtual wire position and head position, for an offset of 0
offset_pre := (_diamondScrew_VirtualWirePosition - SpoolHeadPosition);

IF config.NoLoopPerLayer > 0 THEN
	offset_pre := offset_pre * (1.0 - 1.0/config.NoLoopPerLayer);
ELSE
	offset_pre := 0.0;
END_IF

IF (_spool_layer MOD 2) = 0 THEN
	offset_pre := -offset_pre;
END_IF

OpValues.HeadOffset :=  LREAL_TO_REAL(offset_pre);
	
	]]></ST>
      </Implementation>
    </Method>
    <Method Name="_calcVirtualWirePosition" Id="{fe06138b-6283-02de-0a5c-4e1399624a96}">
      <Declaration><![CDATA[METHOD PRIVATE _calcVirtualWirePosition : BOOL // new code for single thread spooling
VAR_INST
    loopsPerLayer : LREAL;
	layer : LINT;
	deadzone : LREAL;
	pulsesPerTurn: LREAL;
	encPulsesForCalculation : LINT;
	currentLayerPulseCount : LINT;
	currentLayerPulseCount_2 : LINT;
	wirePosSingleDirection : LREAL;		// will count up to 1, and then start on 0 again
	wirePosAfterDeadzone	:	LREAL;
	virtualWirePosition			:	LREAL;	// will go from 0-1 and from 1-0 while counting
END_VAR]]></Declaration>
      <Implementation>
        <ST><![CDATA[{warning disable C0371}
(*
NOTE: make sure that the spool head direction is zero at motor side and 1 at screw end
Outward virtual wire position means moving away from the encoder motor        *)

loopsPerLayer := _NoLoopPerLayer();

// find ppt  based on encoder type
IF Config.Option_DrumEncoder THEN
	pulsesPerTurn := THIS^.Encoder.Resolution;
ELSE
	pulsesPerTurn := THIS^.Encoder.Resolution * Drive.getGearRatio();
END_IF

_pulsesPerLayer := TRUNC(pulsesPerTurn * loopsPerLayer);

IF _pulsesPerLayer = 0 THEN		// missing config, ABORT
	_singleThread_virtualWirePosition := 0.5;
	RETURN;	
END_IF

// Add calculated offset (see other method)
encPulsesForCalculation :=  THIS^.Encoder.ValueRaw + OpValues.encoder_offset_wire_calc;

// calculate which theoretical layer we're on
Layer := encPulsesForCalculation / _pulsesPerLayer ;

// if pulses are negative we need to subtract 1 to get correct layer due to integer division
IF encPulsesForCalculation < 0 THEN
	layer := layer -1;
END_IF

//Are we on an inbound (reverse) layer? 
_InboundVirtualWireDirection := ABS(Layer MOD 2) = 1;

// Number of pulses on current layer
currentLayerPulseCount := ABS( encPulsesForCalculation MOD _pulsesPerLayer) ;

//In excel, MOD rolls over automatically on negative numbers. Here, we need to do it maually
IF encPulsesForCalculation < 0 THEN
	currentLayerPulseCount_2 := _pulsesPerLayer - currentLayerPulseCount;
ELSE
	currentLayerPulseCount_2 := currentLayerPulseCount;
END_IF

wirePosSingleDirection := LINT_TO_REAL(currentLayerPulseCount_2)/LINT_TO_REAL(_pulsesPerLayer);

// deadZone of total width. Note: A value of 1 in config.DeadzoneWidth means one wire width
Deadzone :=  (1.0 / LoopsPerLayer) * config.DeadzoneWidth;  // spooling will not move on the start of each layer

// calculate new wire pos after deadzone is subtracted. Value must be limited (will be negative)
// will stay at zero for one wire width and then go up to 1 on the remaining layer part
wirePosAfterDeadzone := LIMIT(0.0, (wirePosSingleDirection - Deadzone) * (1/(1-Deadzone)), 1.0);

// reverse spool pos if on reverse layer
IF _InboundVirtualWireDirection THEN
	virtualWirePosition := 1- wirePosAfterDeadzone;		// wire moves from outer position to inner position
ELSE
	VirtualWirePosition := wirePosAfterDeadzone;		// wire moves from inner to outer position 
END_IF

_singleThread_virtualWirePosition := virtualWirePosition;


]]></ST>
      </Implementation>
    </Method>
  </POU>
</TcPlcObject>