PROGRAM PLC_PRG
VAR
	LoadDemoConfigurations : BOOL := TRUE; // set this to load configurations from code into memory
	WellOnTestInternal : DINT; // the cached value of the well on test from the HMI
	WellConfig : WellCompleteConfig; // the configuration of the well that is actually on test
	WellConfigStatus : WellTestConfigStatus;
	ConfirmConfigCount : INT := 0; // a delay counter when waiting to validate the config
	ConfigBad : BOOL := FALSE; // set when the config fails validation
	MaxWellOnTest : INT; // set the maximum well on test 
	
 	BatchUserId : DINT; // set the batch user ID to all flow runs
	PurgeComplete : BOOL := FALSE; // set when the purge is complete
	
	SetBatchIds : BOOL; // whether the batch user IDs need to be set explicitly
	SetIdsCounter : INT;
	// time variables
	TimeNowS : UDINT; // unix seconds
	TestStartTime : UDINT;
	TestEndTime : UDINT;
	TestStartTimeLocal : UDINT;
	TestEndTimeLocal : UDINT;
	TestTimeRemaining : UDINT; // seconds
	
	TestStartTimeLocalDt : DATE_AND_TIME;
	TestEndTimeLocalDt : DATE_AND_TIME;
	
	CurrentStateString : STRING(32); // exposes the internal state as a string for the HMI
	
	// convert timestamps from the flow computer into values 
	// that can be displayed in the HMI. 
	LastBatchStartTimeLocal : UDINT;
	LastBatchEndTimeLocal : UDINT;
	LastBatchStartTimeLocalDt : DATE_AND_TIME;
	LastBatchEndTimeLocalDt : DATE_AND_TIME;
	
	CompleteDelayCount : INT :=0; // a delay counter for the 'complete' state
	StopDelayCount : INT := 0; // a delay counter for the 'stop' state
	
	// Auto mode
	WellOnTestAuto : INT := 0; // track the well on test when in auto mode
	
	// make sure apps are connected
	GasFlowConnected : BOOL := FALSE;
	LiquidFlowConnected : BOOL := FALSE;
	WaterFlowConnected : BOOL := FALSE;
	Connected : BOOL := FALSE;
	// just set the color in code instead of using alarms
	// this keeps things simpler for demo. Default to red 
	// frame. 16#FFCCCCCC = grey default frame color
	ConnectedColor :  DWORD := 16#FFFF0000; 
	
	// a delay counter to only check the apps' 
	// exec counts periodically on not every cycle
	ConnectedCounter : INT := 0; 
	
	// cache the last exec counts from the app. These
	// counts must be changing when connected to the apps.
	GasLastExecCount : UDINT := 0;
	LiquidLastExecCount : UDINT := 0;
	WaterLastExecCount : UDINT := 0;
	
	InitConnected : BOOL := FALSE;
	
	// track app alarms in a basic way. 
	// one bool for all runs
	FlowComputerAlarmsActive : BOOL := FALSE;
	FlowComputerAlarmsColor : DWORD := 16#FFCCCCCC;
END_VAR
//************************************************************************
// Section Start/Stop
// map radio button for start/stop test to booleans that are used internally
// only do this in manual mode
IF(GVL.ManualAutoMode = 0) THEN
	IF(GVL.StartStopTest = 0) THEN
		GVL.StopTest := TRUE;
		GVL.StartTest := FALSE;
	ELSE
		GVL.StartTest := TRUE;
		GVL.StopTest := FALSE;
	END_IF
END_IF

IF(CONNECTED = FALSE AND GVL.TestState <> TestStates.Waiting) THEN
	// we are not connected and not in the waiting state,
	// stop the test, which will then take us to the waiting state
	// we can't leave the waiting state until we are connected
	GVL.StopTest := TRUE;
	GVL.StartStopTest := 0;
END_IF
//************************************************************************

//************************************************************************
// Section Main State Machine
// main state machine. Do whatever is required depending on the internal state

// set the max well on test based on whether
// we are using hardcoded demo configs or
// real configs
IF(GVL.UseDemoConfigs = TRUE) THEN
	MaxWellOnTest := 5;
ELSE
	MaxWellOnTest := 32;
END_IF

IF(GVL.TestState = TestStates.Waiting) THEN
	// waiting to start
	IF(Connected = TRUE) THEN
		// only leave this state if we are connected, otherwise don't do anything
		IF(GVL.ManualAutoMode = 1) THEN
			// handle automatic mode
			WellOnTestAuto := WellOnTestAuto + 1;
			IF(WellOnTestAuto > MaxWellOnTest) THEN
				// loop back around after max wells
				WellOnTestAuto := 1;
			END_IF
			// start a test automatically in the waiting state when in auto mode
			WellOnTestInternal := WellOnTestAuto;
			GVL.WellOnTestOut := WellOnTestAuto;
			GVL.WellOnTestIn := WellOnTestAuto;
			// choose whether to use demo or real configs
			// real configs must be loaded and enabled 
			// before this can be used
			IF(GVL.UseDemoConfigs = TRUE) THEN
				// use demo configs which are hard coded in this app
				WellConfig := GVL.DemoWellConfigurations[WellOnTestInternal - 1];
			ELSE
				// use real configs. These MUST be loaded via external tags
				// before being used
				WellConfig := GVL.RealWellConfigurations[WellOnTestInternal - 1];
				WellConfigStatus := GVL.RealWellTestConfigStatus[WellOnTestInternal - 1];
			END_IF
			TestTimeRemaining := 0;
			GVL.StartTest := TRUE;
			GVL.StopTest := FALSE;
			GVL.StartStopTest := 1;
			GVL.TestState := TestStates.ValidateConfig;
		ELSE
			// handle manual mode
			IF(GVL.WellOnTestIn >= 1 AND GVL.WellOnTestIn <= MaxWellOnTest) THEN
				// always set the config so that the user sees the test info
				// even if the test is not started
				IF(GVL.UseDemoConfigs = TRUE) THEN
					// use demo configs which are hard coded in this app
					WellConfig := GVL.DemoWellConfigurations[WellOnTestInternal - 1];
				ELSE
					// use real configs. These MUST be loaded via external tags
					// before being used
					WellConfig := GVL.RealWellConfigurations[WellOnTestInternal - 1];
					WellConfigStatus := GVL.RealWellTestConfigStatus[WellOnTestInternal - 1];
				END_IF
			END_IF
			IF(GVL.StartTest = TRUE) THEN
				IF(GVL.WellOnTestIn >= 1 AND GVL.WellOnTestIn <= MaxWellOnTest) THEN
					// the well on test value is valid
					// cache the well on test internally so that 
					// we're fine if the value changes during 
					// the test. Also store the config
					WellOnTestInternal := GVL.WellOnTestIn;
					GVL.WellOnTestOut := GVL.WellOnTestIn;
					TestTimeRemaining := 0;
					GVL.TestState := TestStates.ValidateConfig;
					// have auto test start track the manual 
					// value set the by the user. Then we can start
					// auto mode at a spefific test
					WellOnTestAuto := UDINT_TO_INT(GVL.WellOnTestIn - 1);
				ELSE
					// the well on test number is not valid, set start to false
					GVL.StartTest := FALSE;		
				END_IF;
			END_IF
			// always force stop test to false in manual mode while waiting
			GVL.StopTest := FALSE;
			// set batch control to all flow runs to 'Stop Batch'
			GVL.GF01.Control_batchControl := 0;
			GVL.LF01.Control_batchControl := 0;
			GVL.WT01.Control_batchControl := 0;
			WellOnTestAuto := 0;
			// end manual mode
		END_IF
	END_IF
	// end waiting state
ELSIF(GVL.TestState = TestStates.ValidateConfig) THEN
	IF(GVL.UseDemoConfigs = TRUE) THEN
		// we are using hard coded demo configs, 
		// no need to validate, move to the next state
		GVL.TestState := TestStates.SiteSpecific;
	ELSE
		// we are using real configs. Is the config loaded and enabled?
		IF(WellConfigStatus.Enabled = FALSE OR 
			WellConfigStatus.Loaded = FALSE) THEN
			// the configuration is not loaded or not enabled, go
			// back to the waiting state
			GVL.TestState := TestStates.Waiting;
		ELSE
			// the config is loaded and enabled, go
			// to the site specific state
			GVL.TestState := TestStates.Waiting;
		END_IF
	END_IF
	// end validate config state
ELSIF(GVL.TestState = TestStates.SiteSpecific) THEN
	// place holder state to do site specific work, like opening and closing valves, etc
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// do work here then move to the next state
		GVL.TestState := TestStates.Configure;
	END_IF;
	// end site specific
ELSIF(GVL.TestState = TestStates.Configure) THEN
	// configure the flow runs
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// push the configuration to the flow computers
		// gas
		GVL.GF01.Compressibility_fractionMethane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionMethane;
		GVL.GF01.Compressibility_fractionNitrogen_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNitrogen;
		GVL.GF01.Compressibility_fractionCarbonDi_w := WellConfig.GasFlowRunConfig.Compressibility_fractionCarbonDi;
		GVL.GF01.Compressibility_fractionEthane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionEthane;
		
		GVL.GF01.Compressibility_fractionPropane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionPropane;
		GVL.GF01.Compressibility_fractionIbutane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionIbutane;
		GVL.GF01.Compressibility_fractionNbutane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNbutane;
		GVL.GF01.Compressibility_fractionIpentane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionIpentane;
		
		GVL.GF01.Compressibility_fractionNpentane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNpentane;
		GVL.GF01.Compressibility_fractionNhexane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNhexane;
		GVL.GF01.Compressibility_fractionNheptane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNheptane;
		GVL.GF01.Compressibility_fractionNoctane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNoctane;
		
		GVL.GF01.Compressibility_fractionNnonane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNnonane;
		GVL.GF01.Compressibility_fractionNdecane_w := WellConfig.GasFlowRunConfig.Compressibility_fractionNdecane;
		GVL.GF01.Compressibility_fractionHydrogen_w := WellConfig.GasFlowRunConfig.Compressibility_fractionHydrogen;
		GVL.GF01.Compressibility_fractionOxygen_w := WellConfig.GasFlowRunConfig.Compressibility_fractionOxygen;
		
		GVL.GF01.Compressibility_fractionCarbonMo_w := WellConfig.GasFlowRunConfig.Compressibility_fractionCarbonMo;
		GVL.GF01.Compressibility_fractionWater_w := WellConfig.GasFlowRunConfig.Compressibility_fractionWater;
		GVL.GF01.Compressibility_fractionH2s_w := WellConfig.GasFlowRunConfig.Compressibility_fractionH2s;
		GVL.GF01.Compressibility_fractionHelium_w := WellConfig.GasFlowRunConfig.Compressibility_fractionHelium;
		
		GVL.GF01.Compressibility_fractionArgon_w := WellConfig.GasFlowRunConfig.Compressibility_fractionArgon;
		// liquid
		GVL.LF01.FlowCalculation_liqProdType_w := WellConfig.LiquidFlowRunConfig.LiquidProductType;
		GVL.LF01.Inputs_densityType_w := WellConfig.LiquidFlowRunConfig.DensityType;
		GVL.LF01.Inputs_fixedDensity_w := WellConfig.LiquidFlowRunConfig.FixedDensity;
		GVL.LF01.FlowCalculation_baseDensityOil_w := WellConfig.LiquidFlowRunConfig.BaseDensityOil;
		GVL.LF01.FlowCalculation_corrFactOil_w := WellConfig.LiquidFlowRunConfig.CorrectionFactorOil;
		// water
		GVL.WT01.FlowCalculation_corrFactWater_w := WellConfig.WaterFlowRunConfig.CorrectionFactorWater;
		
		// set up asset ID. This allows us to link generated records together by asset ID
		// which makes reporting easy.
		// asset ID is just the well on test number.
		// First write to the preset tag then write to the 'set' tag so that app knows to process the value
		// gas
		GVL.GF01.Control_assetIdPreset := WellOnTestInternal;
		// liquid
		GVL.LF01.Control_assetIdPreset := WellOnTestInternal;
		// water
		GVL.WT01.Control_assetIdPreset := WellOnTestInternal;
		
		// batch user ID. All flow runs should have the same batch ID. 
		// Use the 'Next Batch ID' parameter to see what the IDs are. If they
		// are not the same then choose the highest value and push that 
		// out to the flow computers. It doesn't matter exactly what the ID is, 
		// what matters is that they are the same for all runs
		IF(GVL.GF01.FlowStatus_nextBatchUserId <> GVL.LF01.FlowStatus_nextBatchUserId OR
			GVL.GF01.FlowStatus_nextBatchUserId <> GVL.WT01.FlowStatus_nextBatchUserId OR
			GVL.LF01.FlowStatus_nextBatchUserId <> GVL.WT01.FlowStatus_nextBatchUserId) THEN
			// the batch user IDs are different, need to set them so that the next batch starts with the same IDs
			// Note that the parameter holds the next ID, not the last ID, so we want to preset to the next ID - 1
			BatchUserId := GVL.GF01.FlowStatus_nextBatchUserId;
			IF(GVL.LF01.FlowStatus_nextBatchUserId > BatchUserId) THEN
				BatchUserId := GVL.LF01.FlowStatus_nextBatchUserId;
			END_IF;
			IF(GVL.WT01.FlowStatus_nextBatchUserId > BatchUserId) THEN
				BatchUserId := GVL.WT01.FlowStatus_nextBatchUserId;
			END_IF
			// BatchUserId should now have the highest ID from all runs. This is the next ID
			// Set all runs to use this batch ID.
			// gas
			GVL.GF01.Control_batchUserIdPreset := BatchUserId;
			// liquid
			GVL.LF01.Control_batchUserIdPreset := BatchUserId;
			// water
			GVL.WT01.Control_batchUserIdPreset := BatchUserId;
			
			SetBatchIds := TRUE;
		ELSE
			// ELSE IDs are equal, nothing to do
			SetBatchIds := FALSE;
		END_IF; 
				
		// move to the next state
		GVL.TestState := TestStates.SetIds;
		SetIdsCounter := 0;
		// reset counter as we enter the next state
		ConfirmConfigCount := 0;
		ConfigBad := FALSE;
	END_IF;
	// end configure state
ELSIF(GVL.TestState = TestStates.SetIds) THEN
	// the tags that contain the IDs were set in the previous state.
	// signal the apps to process the new values
	IF(SetIdsCounter = 0) THEN
		// Asset ID is always set, batch ID
		// is only set when we need to
		GVL.GF01.Control_setAssetId := TRUE;
		GVL.LF01.Control_setAssetId := TRUE;
		GVL.WT01.Control_setAssetId := TRUE;
		IF(SetBatchIds = TRUE) THEN
			GVL.GF01.Control_setBatchUserId := TRUE;
			GVL.LF01.Control_setBatchUserId := TRUE;
			GVL.WT01.Control_setBatchUserId := TRUE;
		END_IF
	ELSE
		IF(SetIdsCounter >= 15) THEN
			// wait for the asset IDs to be processed
			// then move to the next state
			GVL.TestState := TestStates.ConfirmConfig;
		END_IF
	END_IF
	SetIdsCounter := SetIdsCounter + 1;
	// end set IDs state
ELSIF(GVL.TestState = TestStates.ConfirmConfig) THEN
	IF(GVL.StopTest = TRUE) THEN
		// use asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// wait 5  seconds before checking the configuration. We really only need a couple of seconds
		// but a longer wait allows the batch and asset IDs to get fully processed. When in 
		// automatic mode, if a test fails, and then the next one is started, the asset
		// ID does not get processed properly. 5000 ms / 200 ms callback time = 25 callbacks
		// There are two ways to validate the the flow computers are configured correctly:
		// 		1.	Check the "config invalid" tag for each group of interest
		//		2.	Check that the _r tag matches the _w tag. This means that the config change was accepted by the app
		// We'll just do number 1 for the demo, but number 2 allows you to pinpoint which parameter(s) were not accepted
		ConfirmConfigCount := ConfirmConfigCount + 1;
		IF(ConfirmConfigCount > 25) THEN
			ConfigBad := GVL.GF01.ConfigurationGroupStatus_compressGrpInVld OR 
						 GVL.LF01.ConfigurationGroupStatus_flwCalcGrpVld OR
						 GVL.LF01.ConfigurationGroupStatus_inputGrpVld OR
						 GVL.WT01.ConfigurationGroupStatus_flwCalcGrpVld;
			IF(ConfigBad = TRUE) THEN
				// the configuration is not good. 
				// stop. Add additional error codes or logging here
				GVL.StartStopTest := 0;
				GVL.TestState := TestStates.Stop;
			ELSE
				// move to the next state
				GVL.TestState := TestStates.StartPurge;
				PurgeComplete := FALSE;
			END_IF;
		END_IF;
	END_IF;
	// end confirm config
ELSIF(GVL.TestState = TestStates.StartPurge) THEN
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// send the purge start command to all flow runs
		GVl.GF01.Control_batchControl := 1;
		GVl.LF01.Control_batchControl := 1;
		GVl.WT01.Control_batchControl := 1;
		// go to the next state
		GVL.TestState := TestStates.Purge;
	END_IF;
ELSIF(GVL.TestState = TestStates.Purge) THEN
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// wait until purge is complete
		// check the actual purge volumes against the configured limits
		// When all flow runs have exceeded their limit, move to the next state
		PurgeComplete := GVL.GF01.BatchCurrentStatus_prgVolBase >= WellConfig.PurgeVolumeGas AND 
						 GVL.LF01.BatchStatus_prgVol >= WellConfig.PurgeVolumeLiquid AND 
						 GVL.WT01.BatchStatus_prgVol >= WellConfig.PurgeVolumeWater;
		IF(PurgeComplete = TRUE) THEN
			// go to the next state
			GVL.TestState := TestStates.StartBatch;
		END_IF;				
	END_IF; 
	// end of purge
ELSIF(GVL.TestState = TestStates.StartBatch) THEN
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// send the start batch command for all runs
		GVl.GF01.Control_batchControl := 2;
		GVl.LF01.Control_batchControl := 2;
		GVl.WT01.Control_batchControl := 2;	
		// cache the start time and calculate the end time
		// time stamps are stored as unix millizeconds, milliseconds since Jan 1 1970
		TimeNowS :=  SysTimeRtcGet(TimeNowS); 
		// get test start time as seconds, not milliseconds
		TestStartTime := TimeNowS; // ULINT_TO_UDINT(TimeNowMs/1000);
		// test duration is stored as seconds
		TestEndTime := TestStartTime + WellConfig.TestDuration;
		TestTimeRemaining := WellConfig.TestDuration; // TestEndTime - TestStartTime;
		// get start time and end as local time for HMI
		SysTimeRTCConvertUTCToLocal(TestStartTime, TestStartTimeLocal);
		SysTimeRTCConvertUTCToLocal(TestEndTime, TestEndTimeLocal);	
		TestStartTimeLocalDt := TO_DT(TestStartTimeLocal);
		TestEndTimeLocalDt := TO_DT(TestEndTimeLocal);
		// go to the next state
		GVL.TestState := TestStates.Test;
	END_IF;
ELSIF(GVL.TestState = TestStates.Test) THEN
	IF(GVL.StopTest = TRUE) THEN
		// user asked to stop
		GVL.TestState := TestStates.Stop;
	ELSE
		// see if we have exceeded the configured test time, and if so, stop the test
		TimeNowS :=  SysTimeRtcGet(TimeNowS);
		IF(TestEndTime >= TimeNowS) THEN
			// this will alwyas be 0 or a positive number
			TestTimeRemaining := TestEndTime - TimeNowS;
		ELSE
			TestTimeRemaining := 0;
		END_IF;
		IF(TestTimeRemaining = 0) THEN
			// Stop batches
			GVl.GF01.Control_batchControl := 0;
			GVl.LF01.Control_batchControl := 0;
			GVl.WT01.Control_batchControl := 0;	
			// go to the next state
			GVL.TestState := TestStates.Complete;
			TestTimeRemaining := 0;
		END_IF
	END_IF;
	// end test state
ELSIF(GVL.TestState = TestStates.Complete) THEN
	// do things when the test ends normally, clear state markers and go back to waiting state
	TestTimeRemaining := 0;
	TestStartTime := 0;
	TestEndTime := 0;
	TestStartTimeLocal := 0;
	TestEndTimeLocal := 0;
	TestStartTimeLocalDt := TO_DT(TestStartTimeLocal);
	TestEndTimeLocalDt := TO_DT(TestEndTimeLocal);
	CompleteDelayCount := CompleteDelayCount + 1;
	IF(CompleteDelayCount >= 10) THEN
		// change state after some delay
		CompleteDelayCount := 0;
		GVL.TestState := TestStates.Waiting;
		GVL.StartStopTest := 0;
	END_IF
	// end complete state
ELSIF(GVL.TestState = TestStates.Stop) THEN
	// stop was requested by the user
	// stop batches, clear state markers and go back to waiting state
	GVl.GF01.Control_batchControl := 0;
	GVl.LF01.Control_batchControl := 0;
	GVl.WT01.Control_batchControl := 0;	
	TestTimeRemaining := 0;
	TestStartTime := 0;
	TestEndTime := 0;
	TestStartTimeLocal := 0;
	TestEndTimeLocal := 0;
	TestStartTimeLocalDt := TO_DT(TestStartTimeLocal);
	TestEndTimeLocalDt := TO_DT(TestEndTimeLocal);
	StopDelayCount := StopDelayCount +1;
	IF(StopDelayCount >= 10) THEN
		// change state after some delay
		StopDelayCount := 0;
		GVL.TestState := TestStates.Waiting;
	END_IF
	// end stop state
END_IF;
// end section Main State Machine
//************************************************************************

//************************************************************************
// Section HMI State
// get the current state string
// no need to show all internal states, just
// show ones that take some time
IF(GVL.TestState = TestStates.Waiting) THEN
	CurrentStateString := 'Waiting';
ELSIF(GVL.TestState = TestStates.Configure) THEN
	CurrentStateString := 'Configure';
ELSIF(GVL.TestState = TestStates.ConfirmConfig) THEN
	CurrentStateString := 'Validate';
ELSIF(GVL.TestState = TestStates.Purge) THEN
	CurrentStateString := 'Purge';
ELSIF(GVL.TestState = TestStates.Test) THEN
	CurrentStateString := 'Test';
END_IF;
//************************************************************************

//************************************************************************
// Section Convert Timestamps
// convert batch history timestamps to DATE_AND_TIME values so that they can be displayed
// timestamps are stored by the well test application as unix seconds UTC
SysTimeRTCConvertUTCToLocal(GVL.GF01.BatchHistory_bchHisStartTs_001, LastBatchStartTimeLocal);
SysTimeRTCConvertUTCToLocal(GVL.GF01.BatchHistory_bchHisEndTs_001, LastBatchEndTimeLocal);
LastBatchStartTimeLocalDt := TO_DT(LastBatchStartTimeLocal);
LastBatchEndTimeLocalDt := TO_DT(LastBatchEndTimeLocal);
//************************************************************************

//************************************************************************
// Section Connection State
(* check connection state of apps. Wait 50 callbacks and see if the exec count for each app is changing *)
IF(ConnectedCounter >= 50) THEN
	(* The delay timer has expired. Reset the delay timer 
	and then see if we're connected by comparing the execution counts
	to the execution counts from the last time that we executed
	this code section *)
	ConnectedCounter := 0;
	(* an app is connected if the counts have changed *)
	GasFlowConnected := GVL.GF01.ApplicationInfo_execCount <> GasLastExecCount;
	LiquidFlowConnected := GVL.LF01.ApplicationInfo_execCount <> LiquidLastExecCount;
	WaterFlowConnected  := GVL.WT01.ApplicationInfo_execCount <> WaterLastExecCount;
	(* update the counts for each app *)
	GasLastExecCount := GVL.GF01.ApplicationInfo_execCount;
	LiquidLastExecCount := GVL.LF01.ApplicationInfo_execCount;
	WaterLastExecCount  := GVL.WT01.ApplicationInfo_execCount;
END_IF
(* always increment the delay counter *)
ConnectedCounter := ConnectedCounter + 1;
(* connected is true only if all apps are connected *)
Connected := GasFlowConnected AND LiquidFlowConnected AND WaterFlowConnected;
// set the color
IF(Connected = TRUE) THEN
	ConnectedColor := 16#FFCCCCCC; // grey
ELSE
	ConnectedColor := 16#FFFF0000; // red
END_IF;
//************************************************************************


//************************************************************************
// Section Alarms Active
// get the overall alarm status. Set the bool active if tehre
// are any alarms on any run. More granular information is
// available from each app if so desired. Each alarm type
// has an indivisual status tag. 
// Only let this go true if we are actually connected. When 
// not connected, this will be always be false;
FlowComputerAlarmsActive := (GVL.GF01.InputAlarmStatus_alarmActive OR
								GVL.LF01.InputAlarmStatus_alarmActive OR
								GVL.WT01.InputAlarmStatus_alarmActive) AND 
								Connected;

IF(FlowComputerAlarmsActive = TRUE) THEN
	FlowComputerAlarmsColor := 16#FFFF0000; // red
ELSE
	FlowComputerAlarmsColor := 16#FFCCCCCC; // grey
END_IF
//************************************************************************

//************************************************************************
// Section Well Confirurations
// configure wells to test
// do this once when the
// program is started
IF LoadDemoConfigurations = TRUE THEN
	// only load configurations once
	LoadDemoConfigurations := FALSE;
	//***********************************************************************************
	// well 1, index 0
	GVL.DemoWellConfigurations[0].WellNumber := 0; 
	GVL.DemoWellConfigurations[0].PurgeVolumeGas := 1.0; // m3
	GVL.DemoWellConfigurations[0].PurgeVolumeLiquid := 0.5; // m3
	GVL.DemoWellConfigurations[0].PurgeVolumeWater := 0.5; // m3
	GVl.DemoWellConfigurations[0].TestDuration := 7200; // seconds
	// gas
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionMethane := 0.79675;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNitrogen := 0.0081;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionCarbonDi := 0.016;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionEthane := 0.00525;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionPropane := 0.01805;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionWater := 0.01862;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionH2s := 0.01772;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionHydrogen := 0.00985;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionCarbonMo := 0.00798;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionOxygen := 0.00306;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionIbutane := 0.00462;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNbutane := 0.01882;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionIpentane := 0.00796;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNpentane := 0.01444;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNhexane := 0.00078;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNheptane := 0.01479;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNoctane := 0.00297;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNnonane := 0.00868;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionNdecane := 0.00885;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionHelium := 0.00991;
	GVL.DemoWellConfigurations[0].GasFlowRunConfig.Compressibility_fractionArgon := 0.0068;
	// liquid
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.DensityType := 0; // live density
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.FixedDensity := 757.43;
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.LiquidProductType := 0; // crude oil
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.CorrectionFactorOil := 1.0;
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.BaseDensityOil := 600.0; // kg/m3 
	GVL.DemoWellConfigurations[0].LiquidFlowRunConfig.BaseDensityWater := 1000.0; // kg/m3 
	// water
	GVL.DemoWellConfigurations[0].WaterFlowRunConfig.CorrectionFactorWater := 1.0;
	// end well 1, index 0
	//***********************************************************************************
	//***********************************************************************************
	// well 2, index 1
	GVL.DemoWellConfigurations[1].WellNumber := 1; 
	GVL.DemoWellConfigurations[1].PurgeVolumeGas := 1.0; // m3
	GVL.DemoWellConfigurations[1].PurgeVolumeLiquid := 0.25; // m3
	GVL.DemoWellConfigurations[1].PurgeVolumeWater := 0.25; // m3
	GVl.DemoWellConfigurations[1].TestDuration := 4800; // seconds
	// gas
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionMethane := 0.81754;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNitrogen := 0.01956;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionCarbonDi := 0.0089;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionEthane := 0.00935;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionPropane := 0.0159;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionWater := 0.00993;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionH2s := 0.01296;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionHydrogen := 0.00015;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionCarbonMo := 0.00269;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionOxygen := 0.01688;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionIbutane := 0.00436;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNbutane := 0.00203;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionIpentane := 0.01276;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNpentane := 0.01413;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNhexane := 0.00825;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNheptane := 0.0125;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNoctane := 0.00165;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNnonane := 0.00345;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionNdecane := 0.0;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionHelium := 0.01903;
	GVL.DemoWellConfigurations[1].GasFlowRunConfig.Compressibility_fractionArgon := 0.00798;
	// liquid
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.DensityType := 1; // fixed density
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.FixedDensity := 612.43;
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.LiquidProductType := 4; // gasonline is unrealistic for a test, but this is just a demo 
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.CorrectionFactorOil := 1.1;
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.BaseDensityOil := 650.0;
	GVL.DemoWellConfigurations[1].LiquidFlowRunConfig.BaseDensityWater := 1000.0; // kg/m3 
	// water
	GVL.DemoWellConfigurations[1].WaterFlowRunConfig.CorrectionFactorWater := 0.9;	
	// end well 2, index 1
	//***********************************************************************************
	//***********************************************************************************
	// well 3, index 2
	// this gas composition is intentionally bad and cannot be loaded. 
	GVL.DemoWellConfigurations[2].WellNumber := 2; 
	GVL.DemoWellConfigurations[2].PurgeVolumeGas := 2.0; // m3
	GVL.DemoWellConfigurations[2].PurgeVolumeLiquid := 1.0; // m3
	GVL.DemoWellConfigurations[2].PurgeVolumeWater := 0.0; // m3
	GVl.DemoWellConfigurations[2].TestDuration := 600; // seconds
	// gas
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionMethane := 0.7776;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNitrogen := 0.00721;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionCarbonDi := 0.01813;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionEthane := 0.01355;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionPropane := 0.01582;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionWater := 0.01054;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionH2s := 0.01669;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionHydrogen := 0.01648;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionCarbonMo := 0.00409;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionOxygen := 0.00105;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionIbutane := 0.00944;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNbutane := 0.0092;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionIpentane := 0.01874;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNpentane := 0.00747;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNhexane := 0.00229;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNheptane := 0.00448;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNoctane := 0.0151;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNnonane := 0.0114;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionNdecane := 0.01387;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionHelium := 0.0152;
	GVL.DemoWellConfigurations[2].GasFlowRunConfig.Compressibility_fractionArgon := 0.01165;
	// liquid
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.DensityType := 0; // live density
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.FixedDensity := 698.32;
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.LiquidProductType := 0; // crude oil
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.CorrectionFactorOil := 0.9;
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.BaseDensityOil := 605.0;
	GVL.DemoWellConfigurations[2].LiquidFlowRunConfig.BaseDensityWater := 1000.0; // kg/m3 
	// water
	GVL.DemoWellConfigurations[2].WaterFlowRunConfig.CorrectionFactorWater := 1.1;	
	// end well 3, index 2
	//***********************************************************************************
	//***********************************************************************************
	// well 4, index 3
	GVL.DemoWellConfigurations[3].WellNumber := 3; 
	GVL.DemoWellConfigurations[3].PurgeVolumeGas := 1.0; // m3
	GVL.DemoWellConfigurations[3].PurgeVolumeLiquid := 1.0; // m3
	GVL.DemoWellConfigurations[3].PurgeVolumeWater := 1.0; // m3
	GVl.DemoWellConfigurations[3].TestDuration := 3600; // seconds
	// gas
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionMethane := 0.81061;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNitrogen := 0.00518;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionCarbonDi := 0.00786;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionEthane := 0.0173;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionPropane := 0.01503;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionWater := 0.00918;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionH2s := 0.00871;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionHydrogen := 0.00875;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionCarbonMo := 0.00554;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionOxygen := 0.01932;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionIbutane := 0.00343;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNbutane := 0.01543;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionIpentane := 0.003;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNpentane := 0.01873;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNhexane := 0.0102;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNheptane := 0.00698;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNoctane := 0.00261;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNnonane := 0.00244;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionNdecane := 0.01714;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionHelium := 0.00825;
	GVL.DemoWellConfigurations[3].GasFlowRunConfig.Compressibility_fractionArgon := 0.00431;
	// liquid
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.DensityType := 0; // live density
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.FixedDensity := 703.2;
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.LiquidProductType := 0; // crude oil
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.CorrectionFactorOil := 1.0;
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.BaseDensityOil := 700.0;
	GVL.DemoWellConfigurations[3].LiquidFlowRunConfig.BaseDensityWater := 1000.0; // kg/m3 
	// water
	GVL.DemoWellConfigurations[3].WaterFlowRunConfig.CorrectionFactorWater := 1.2;		
	// end well 4, index 3
	//***********************************************************************************
	//***********************************************************************************
	// well 5, index 4
	GVL.DemoWellConfigurations[4].WellNumber := 4; 
	GVL.DemoWellConfigurations[4].PurgeVolumeGas := 1.5; // m3
	GVL.DemoWellConfigurations[4].PurgeVolumeLiquid := 1.5; // m3
	GVL.DemoWellConfigurations[4].PurgeVolumeWater := 1.5; // m3
	GVl.DemoWellConfigurations[4].TestDuration := 7200; // seconds
	// gas
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionMethane := 0.84489;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNitrogen := 0.0153;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionCarbonDi := 0.00023;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionEthane := 0.00564;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionPropane := 0.00318;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionWater := 0.00757;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionH2s := 0.00477;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionHydrogen := 0.01197;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionCarbonMo := 0.00725;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionOxygen := 0.00178;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionIbutane := 0.00229;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNbutane := 0.01697;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionIpentane := 0.01493;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNpentane := 0.01806;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNhexane := 0.0093;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNheptane := 0.01125;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNoctane := 0.01047;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNnonane := 0.0;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionNdecane := 0.0;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionHelium := 0.01415;
	GVL.DemoWellConfigurations[4].GasFlowRunConfig.Compressibility_fractionArgon := 0.0;
		// liquid
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.DensityType := 1; // fixed density
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.FixedDensity := 800.0;
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.LiquidProductType := 1; // fuel oil
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.CorrectionFactorOil := 1.0;
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.BaseDensityOil := 605.0;
	GVL.DemoWellConfigurations[4].LiquidFlowRunConfig.BaseDensityWater := 1000.0; // kg/m3 
	// water
	GVL.DemoWellConfigurations[4].WaterFlowRunConfig.CorrectionFactorWater := 1.0;		
	// end well 4, index 5
	//***********************************************************************************
END_IF;
// end section Well Configurations
//************************************************************************