tbf/TBF/BenchControl/StateMachine.cs

832 lines
31 KiB
C#
Raw Normal View History

///
/// Copyright (c) 2013-2018 Sensus Slovensko a.s.
///
using System;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using log4net;
using NHibernate;
using Config.Entities;
using TBF.BenchControl.Generic;
using TBF.BenchControl.GenericDevices;
using TBF.BenchControl.Sequences;
using Dirichlet.Numerics;
using TBF.Resources;
namespace TBF.BenchControl
{
public class QuitStateMachineException : Exception
{
}
/// <summary>
/// This class controls real test bench behavior.
/// It is based on a state machine
/// </summary>
public static class StateMachine
{
private static readonly ILog log = LogManager.GetLogger(typeof(StateMachine));
private static readonly ILog wlog = LogManager.GetLogger(typeof(StateMachine));
/// Private devices and components
static IList<IComponent> components; /// list of all components
static IList<IDevice> devices; /// list of devices
///
/// Bench paths
static IList<Config.Entities.FeedingPath> feedingPaths;
static IList<Config.Entities.BenchPath> benchPaths;
static IList<Config.Entities.OutputPath> outputPaths;
static IList<Config.Entities.MetersPath> metersPaths;
static IList<Config.Entities.HeatMetersPath> heatMetersPaths;
public static IList<Config.Entities.TransitionSequence> TransitionSequences;
public static IList<Config.Entities.TransitionStep> TransitionSteps;
/// Public components
public static Elde.ControlBoardDev ControlBoard;
public static GenericDevices.IAmbient Ambient;
public static GenericDevices.IScaleOrTank Tank1;
public static GenericDevices.IScaleOrTank Tank2;
public static GenericDevices.IScaleOrTank Tank3;
public static GenericDevices.IValve DrainValve1;
public static GenericDevices.IValve DrainValve2;
public static GenericDevices.IValve DrainValve3;
2015-06-29 09:26:08 +00:00
public static IList<IValve> MasterValves; /// list of directly controlled valves
public static IList<IValve> CoupledValves; /// list of coupled valves
public static IList<Elde.ValveEx.Valve> ExtendedValves; /// list of extended valves
/// Time and synchronization
#if TURA_IPERL
public const int Period = 2; /// State machine period in sec.
#else
public const int Period = 1; /// State machine period in sec.
#endif
static DateTime startDateTime; /// DateTime of time instance when the state machine worker thread starts
static int currentTimeSec; /// Time from the start of the state machine in seconds
static bool quitStateMachine; /// flag to stop the worker thread
/// true when the state machine is running
static bool stateMachineRunning;
public static bool Running { get { return stateMachineRunning; } }
/// Worker thread and database session
static Thread workerThread;
static IList<State> states; /// list of states
2016-03-02 19:02:30 +00:00
public static DateTime CycleStartTimeStamp;
public static IList<IValve> DefaultValvesOpen
{
get
{
return GenericDevices.ValveBase.Merge(
Utils.ValvesOpen((feedingPaths != null && feedingPaths.Count > 0) ? feedingPaths[0] : null),
Utils.ValvesOpen((benchPaths != null && benchPaths.Count > 0) ? benchPaths[0] : null),
Utils.ValvesOpen((outputPaths != null && outputPaths.Count > 0) ? outputPaths[0] : null)
);
}
}
public static IList<IValve> DefaultValvesClose
{
get
{
return GenericDevices.ValveBase.Merge(
Utils.ValvesClose((feedingPaths != null && feedingPaths.Count > 0) ? feedingPaths[0] : null),
Utils.ValvesClose((benchPaths != null && benchPaths.Count > 0) ? benchPaths[0] : null),
Utils.ValvesClose((outputPaths != null && outputPaths.Count > 0) ? outputPaths[0] : null)
);
}
}
/// <summary>
/// Loaded by LoadProcedure() or IOperation LoadProcedureOp(...)
/// </summary>
public static Procedure Procedure; /// Procedure
public static IList<Test> Tests; /// Tests
/// Hardware devices connected to the PC controlling the bench.
public static IList<IComponent> Components { get { return components; } }
public static IList<IDevice> Devices { get { return devices; } }
/// <summary>
/// DateTime of time instance when the state machine worker thread starts
/// </summary>
public static DateTime StartDateTime { get { return startDateTime; } }
/// <summary>
/// Current state name
/// </summary>
public static int Time
{
get { return currentTimeSec; }
set { currentTimeSec = value; }
}
/// <summary>
/// Constructor
/// </summary>
static StateMachine()
{
devices = new List<IDevice>();
states = new List<State>();
currentTimeSec = 0;
quitStateMachine = false;
}
/// <summary>
/// Add a device to the state machine.
/// </summary>
/// <param name="obj">Device</param>
public static void AddDevice(IDevice device)
{
if (device != null && !devices.Contains(device)) devices.Add(device);
}
/// <summary>
/// Add a state to the state machine.
/// In this way a sequence can be created programtically.
/// </summary>
/// <param name="obj">State</param>
public static void AddState(State state)
{
if (state != null && !states.Contains(state)) states.Add(state);
}
/// <summary>
/// Remove the state from the state machine.
/// </summary>
/// <param name="obj">State</param>
public static void RemoveState(State state)
{
if (state != null && state != State.CurrentState && states.Contains(state)) states.Remove(state);
}
/// <summary>
/// Get a state from a label
/// </summary>
/// <param name="label"></param>
/// <returns>The matching state or null</returns>
static State GetStateFromLabel(string label)
{
if (label == null) return null;
foreach (State state in states)
{
if (state.Label != null && state.Label.Equals(label)) return state;
}
return null;
}
/// <summary>
/// Start the state machine in the state 'label' in a desired mode of operation.
/// This method is called in the UI thread and creates a new state machine thread.
/// This method call should be embedded in: try { StateMachine.Start(...); } catch { }
/// to handle configuration problems. Calls CreateDevices(mode) and CreateStates().
/// </summary>
/// <param name="mode">Mode of operation</param>
/// <param name="benchData">A copy of bench data used by the state machine</param>
/// <param name="label">Identifies the initial state</param>
#if DN100
public static void InitializeBoardEtc(ControlCom2VB.ControlCom2panel ctrlBrdComponent)
#elif MUNICH || FUZHOU_150
public static void InitializeBoardEtc(ControlComponent3Munich.UserControl1 ctrlBrdComponent)
#elif FUZHOU_300
public static void InitializeBoardEtc(ControlComponent3F300.UserControl1 ctrlBrdComponent)
#elif IZRAEL_200 || PUCHONG_200 || GENESIS || BERLIN || SLM_150 || PETERSBURG_200
2017-01-24 14:05:30 +00:00
public static void InitializeBoardEtc(ControlComponent_Izrael2014.UserControl1 ctrlBrdComponent)
#else /// all newer benches
2015-05-05 15:15:37 +00:00
public static void InitializeBoardEtc(ControlComponent_Torino2015.UserControl1 ctrlBrdComponent)
#endif
{
/// Load the list of components (entities) from the database.
/// Then create the components (derived from IComponent).
components = BenchControl.TbfComponents.LoadComponentsFromDB(Config.FluentCommon.CreateSession(Users.Entities.DBKind.Config));
MasterValves = GenericDevices.ValveBase.MasterValves(components);
CoupledValves = GenericDevices.ValveBase.CoupledValves(components);
2015-06-29 09:26:08 +00:00
ExtendedValves = GenericDevices.ValveBase.ExtendedValves(components);
/// Find all balances (to initialize tank capacities in the control board)
/// Find the control board
IList<IScaleOrTank> tanks = new List<IScaleOrTank>();
SequenceBase.FlowMeters = new List<IFlowMeter>();
SequenceBase.RegulValves = new List<IRegulValve>();
SequenceBase.PumpsWithFM = new List<IPumpFM>();
SequenceBase.WaterMeters = new List<IWaterMeter>();
SequenceBase.Cameras = new List<ICamera>();
UInt128 valvesToInvert = 0;
foreach (var cmpnt in components)
{
if (cmpnt is Elde.ControlBoardDev) ControlBoard = cmpnt as Elde.ControlBoardDev;
if (cmpnt is IBenchInfo) ProcessData.BenchInfo = cmpnt as IBenchInfo;
if (cmpnt is IErrorFlags) ProcessData.ErrorFlagsComp = cmpnt as IErrorFlags;
if ((cmpnt is Output.DB.SensusOracle.Database) && (ProcessData.OracleDB == null))
{
ProcessData.OracleDB = cmpnt as Output.DB.SensusOracle.Database;
}
if (cmpnt is IFlowMeter) SequenceBase.FlowMeters.Add(cmpnt as IFlowMeter);
if ((cmpnt is IRegulValve) && !(cmpnt is BenchControl.Elde.RegulValveTandem.RegulValveTandem))
{
SequenceBase.RegulValves.Add(cmpnt as IRegulValve);
}
if (cmpnt is IPumpFM && !(cmpnt is Elde.PumpTandem.Pump))
{
SequenceBase.PumpsWithFM.Add(cmpnt as IPumpFM);
}
if (cmpnt is IWaterMeter) SequenceBase.WaterMeters.Add(cmpnt as IWaterMeter);
if (cmpnt is ICamera) SequenceBase.Cameras.Add(cmpnt as ICamera);
if (cmpnt is GenericDevices.IAmbient) Ambient = cmpnt as GenericDevices.IAmbient;
if (cmpnt is IScaleOrTank)
{
IScaleOrTank tank = cmpnt as IScaleOrTank;
tanks.Add(tank);
if (tank.ScaleNr == 0) Tank1 = tank;
else if (tank.ScaleNr == 1) Tank2 = tank;
else if (tank.ScaleNr == 2) Tank3 = tank;
}
Elde.Valve.Valve eldeValve = (cmpnt as Elde.Valve.Valve);
if ((eldeValve != null) && eldeValve.Inverted) valvesToInvert |= eldeValve.Mask;
cmpnt.StartChangeHandler(); /// Start handling parameter change events
}
/// Create an array with tank capacities
double[] tankCapacities = new double[tanks.Count];
for (int i = 0; i < tankCapacities.Length; i++) tankCapacities[i] = tanks[i].Capacity;
/// Pre-initialize the control board (= buffer the arguments ctrlBrdComponent, tankCapacities)
if (ControlBoard != null)
{
ControlBoard.InitializeComponent(ctrlBrdComponent, valvesToInvert, tankCapacities);
}
else
{
throw new Exception("Control board component is missing");
}
}
public static string CurrentlyInitializedDeviceName;
///
public static string InitializeDevices()
{
CurrentlyInitializedDeviceName = "-";
bool anyComponentIsInSimulMode = false;
StringBuilder inSimulMode = new StringBuilder();
/// Add all devices to the state machine and initialize them
foreach (var cmpnt in components)
{
if (cmpnt.DebugLevel == DebugMode.Simulate)
{
inSimulMode.AppendFormat("{0}{1}", anyComponentIsInSimulMode ? ", " : "", cmpnt.Name);
anyComponentIsInSimulMode = true;
}
IDevice device = cmpnt as IDevice;
if (device != null)
{
CurrentlyInitializedDeviceName = device.Name;
UiBridge.Bridge.OnActivity(null, device.Name); /// Info
device.Initialize();
AddDevice(device); /// Only components that were initialized are added
}
}
CurrentlyInitializedDeviceName = "---";
///
/// (1) Propagate debug levels from parents to children when necessary
/// (2) Set drain valves DrainValve1, DrainValve2 and DrainValve3
///
foreach (var cmpnt in components)
{
if (cmpnt.Cfg is IChildComponentCfg && !string.IsNullOrEmpty(cmpnt.Cfg.ParentName) &&
(cmpnt.Cfg.DebugLevel == DebugMode.Inherit || cmpnt.Cfg.DebugLevel == DebugMode.AutoDetect))
{
foreach (var par in components)
{
if (par.Cfg.Name.Equals(cmpnt.Cfg.ParentName)) { cmpnt.Cfg.DebugLevel = par.Cfg.DebugLevel; break; }
}
}
if (cmpnt is IScaleOrTank)
{
IScaleOrTank tank = cmpnt as IScaleOrTank;
if (tank.ScaleNr == 0)
{
DrainValve1 = tank.DrainValve;
log.WarnFormat("InitializeBoardEtc() ... Scale1={0} Draining valve1={1}",
tank.Name, (DrainValve1 != null) ? DrainValve1.Name : "---");
}
else if (tank.ScaleNr == 1)
{
DrainValve2 = tank.DrainValve;
log.WarnFormat("InitializeBoardEtc() ... Scale2={0} Draining valve2={1}",
tank.Name, (DrainValve2 != null) ? DrainValve2.Name : "---");
}
else if (tank.ScaleNr == 2)
{
DrainValve3 = tank.DrainValve;
log.WarnFormat("InitializeBoardEtc() ... Scale3={0} Draining valve3={1}",
tank.Name, (DrainValve3 != null) ? DrainValve3.Name : "---");
}
}
}
if (anyComponentIsInSimulMode)
return inSimulMode.ToString();
else
return null;
}
/// <summary>
/// Stop devices that were started by InitializeDevices().
/// Works correctly also after exeption from InitializeDevices() as only ...
/// ... correctly started devices were added in 'devices' list.
/// </summary>
public static void StopDevices()
{
foreach (var dev in devices)
{
UiBridge.Bridge.OnActivity(null, string.Format("1. {0}", dev.Name)); /// Info
dev.StopDevice();
}
}
/// <summary>
/// Stop devices that were started by InitializeDevices().
/// Works correctly also after exeption from InitializeDevices() as only ...
/// ... correctly started devices were added in 'devices' list.
/// </summary>
public static void StopDevices2()
{
foreach (var dev in devices)
{
UiBridge.Bridge.OnActivity(null, string.Format("2. {0}", dev.Name)); /// Info
dev.StopDevice2();
}
}
/// <summary>
/// Checks remote and local configuration DB paths and transitions for compatibility
/// </summary>
/// <returns>true when DB-s are compatible</returns>
public static bool IsRemoteDBCompatible(out string message)
{
IList<Config.Entities.FeedingPath> remoteFeedingPaths;
IList<Config.Entities.BenchPath> remoteBenchPaths;
IList<Config.Entities.OutputPath> remoteOutputPaths;
IList<Config.Entities.MetersPath> remoteMetersPaths;
IList<Config.Entities.TransitionSequence> remoteTransitions;
try
{
ISession remoteSession = Config.FluentCommon.CreateSession(Users.Entities.DBKind.RemoteConfig);
remoteFeedingPaths = remoteSession.QueryOver<Config.Entities.FeedingPath>().List();
remoteBenchPaths = remoteSession.QueryOver<Config.Entities.BenchPath>().List();
remoteOutputPaths = remoteSession.QueryOver<Config.Entities.OutputPath>().List();
remoteMetersPaths = remoteSession.QueryOver<Config.Entities.MetersPath>().List();
remoteTransitions = remoteSession.QueryOver<Config.Entities.TransitionSequence>().List();
}
catch (Exception)
{
message = "Cannot open remote database";
return false;
}
ISession localSession = Config.FluentCommon.CreateSession(Users.Entities.DBKind.Config);
var localFeedingPaths = localSession.QueryOver<Config.Entities.FeedingPath>().List();
var localBenchPaths = localSession.QueryOver<Config.Entities.BenchPath>().List();
var localOutputPaths = localSession.QueryOver<Config.Entities.OutputPath>().List();
var localMetersPaths = localSession.QueryOver<Config.Entities.MetersPath>().List();
var localTransitions = localSession.QueryOver<Config.Entities.TransitionSequence>().List();
string subMsg;
if (!IsCompatible(localFeedingPaths, remoteFeedingPaths, out subMsg))
{
message = string.Format("Feeding: {0}", subMsg);
return false;
}
if (!IsCompatible(localBenchPaths, remoteBenchPaths, out subMsg))
{
message = string.Format("Bench: {0}", subMsg);
return false;
}
if (!IsCompatible(localOutputPaths, remoteOutputPaths, out subMsg))
{
message = string.Format("Output: {0}", subMsg);
return false;
}
if (!IsCompatible(localMetersPaths, remoteMetersPaths, out subMsg))
{
message = string.Format("Sensors: {0}", subMsg);
return false;
}
if (!IsCompatible(localTransitions, remoteTransitions, out subMsg))
{
message = string.Format("Transitions: {0}", subMsg);
return false;
}
message = string.Empty;
return true;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">FeedingPath, BenchPath, OutputPath, MetersPath or TransitionSequence</typeparam>
/// <param name="localList">Local list of paths or transitions</param>
/// <param name="remoteList">Remote list of paths or transitions</param>
/// <param name="message">Message specifying a cause of incompatibility</param>
/// <returns>true when lists are compatible, otherwise false</returns>
private static bool IsCompatible<T>(IList<T> localList, IList<T> remoteList, out string message)
{
message = "Local list is empty";
if ((localList == null) || (localList.Count == 0) || !(localList[0] is IHasName)) return false;
message = "Remote list is empty";
if ((remoteList == null) || (remoteList.Count == 0) || !(remoteList[0] is IHasName)) return false;
///
/// Each path or transition on a remote list must exist on a local list
///
foreach (var rItem in remoteList)
{
bool exists = false;
foreach (var lItem in localList)
{
if ((rItem as IHasName).Name == (lItem as IHasName).Name)
{
exists = true;
break;
}
}
if (!exists)
{
message = string.Format("Remote item {0} does not exist on a local list", (rItem as IHasName).Name);
return false;
}
}
message = string.Empty;
return true;
}
/// <summary>
/// Start the state machine
/// </summary>
public static void Start()
{
if (stateMachineRunning) return;
workerThread = new Thread(Worker);
workerThread.CurrentCulture = Thread.CurrentThread.CurrentCulture;
workerThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
workerThread.Start();
stateMachineRunning = true;
}
/// <summary>
/// Loads all paths and transitions from the DB.
/// Updates StatMachine.feedingPaths ... StatMachine.meterPaths, StatMachine.TransitionSequences
/// </summary>
public static void LoadPathsAndTransitions(ISession session)
{
feedingPaths = session.QueryOver<Config.Entities.FeedingPath>().OrderBy(x => x.ItemNr).Asc.List();
benchPaths = session.QueryOver<Config.Entities.BenchPath>().OrderBy(x => x.ItemNr).Asc.List();
outputPaths = session.QueryOver<Config.Entities.OutputPath>().OrderBy(x => x.ItemNr).Asc.List();
metersPaths = session.QueryOver<Config.Entities.MetersPath>().OrderBy(x => x.ItemNr).Asc.List();
TransitionSequences = session.QueryOver<TransitionSequence>().OrderBy(x => x.ItemNr).Asc.List();
TransitionSteps = session.QueryOver<TransitionStep>().OrderBy(x => x.ItemNr).Asc.List();
#if HEAT_METERS
heatMetersPaths = session.QueryOver<Config.Entities.HeatMetersPath>().OrderBy(x => x.ItemNr).Asc.List();
#endif
}
/// <summary>
/// Loads selected procedure from the DB.
/// Updates StateMachine.Procedure and StateMachine.Tests
/// </summary>
public static bool LoadProcedure(ISession session, string procedureName)
{
Procedure = null;
IList<Procedure> selectedProcs = session.QueryOver<Procedure>()
.Where(x => (x.ProcedureState == ProcedureState.Active))
.And(x => (x.Name == procedureName))
.List();
if (selectedProcs.Count != 1) return false;
Procedure = selectedProcs[0];
Tests = selectedProcs[0].Tests;
return true;
}
public static void LoadProcedureParams(Procedure procedure)
{
foreach (var cmpnt in components)
{
cmpnt.Cfg.LoadProcedureParamsFromDB(procedure);
}
}
public static void LoadTestParams(Test test)
{
foreach (var cmpnt in components)
{
cmpnt.Cfg.LoadTestParamsFromDB(test);
}
}
/// <summary>
/// Processes the selection done by the bench control panel in the main sequence.
/// </summary>
/// <param name="selection">Selection.Q1, .Q2, .Q3 or .Test</param>
/// <returns>The selected test or null</returns>
public static Config.Entities.Test GetTest(string selectedTestName, out int repetNr)
{
repetNr = 1;
foreach (var test in Tests)
{
if (test.Name.Equals(selectedTestName)) return test; /// Test name specified, keep repetNr = 1
for (int i = 1; i <= test.Repeats; i++)
{
if (Utils.TestTitle(test, i).Equals(selectedTestName))
{
repetNr = i;
return test;
}
}
}
return null;
}
/// <summary>
/// Called from the sequence to update paths based on the selected test
/// </summary>
/// <param name="test">Selected test</param>
/// <param name="pfeed"></param>
/// <param name="pben"></param>
/// <param name="pout"></param>
/// <param name="pmtrs"></param>
2015-06-25 02:42:28 +00:00
/// <param name="transitionBefore">Transition sequence entity</param>
/// <param name="transitionAfter">Transition sequence entity</param>
/// <param name="errorMsg">Error message in case of incorrect configuration</param>
/// <returns>true when loaded configuration is correct (all four paths are defined !=null, etc.)</returns>
public static bool GetPaths(Test test,
bool heatMetersPathRequired,
out FeedingPath pfeed,
out BenchPath pben,
out OutputPath pout,
out MetersPath pmtrs,
out HeatMetersPath phmtrs,
out TransitionSequence transitionBefore,
out TransitionSequence transitionBetween,
out TransitionSequence transitionAfter,
out string errorMsg)
{
pfeed = null;
pben = null;
pout = null;
phmtrs = null;
transitionBefore = null;
transitionBetween = null;
transitionAfter = null;
foreach (var path in feedingPaths)
{
if (test.FeedingPath == path.Name) { pfeed = new FeedingPath(path, components); break; }
}
foreach (var path in benchPaths)
{
if (test.BenchPath == path.Name) { pben = new BenchPath(path, components); break; }
}
foreach (var path in outputPaths)
{
if (test.OutputPath == path.Name) { pout = new OutputPath(path, components); break; }
}
pmtrs = GetMetersPath(test);
if (pfeed == null) errorMsg = Strings.Cannot_load_feeding_path;
else if (pben == null) errorMsg = Strings.Cannot_load_bench_path;
else if (pout == null) errorMsg = Strings.Cannot_load_output_path;
else if (pmtrs == null) errorMsg = Strings.Cannot_load_sensor_path;
else errorMsg = string.Empty;
if ((pfeed == null) || (pben == null) || (pout == null) || (pmtrs == null))
{
return false;
}
#if HEAT_METERS
foreach (var path in heatMetersPaths)
{
if (test.HeatMetersPath == path.Name) { phmtrs = new HeatMetersPath(path, components); break; }
}
#endif
if (heatMetersPathRequired && phmtrs == null)
{
errorMsg = "Cannot load heat meters sensors";
return false;
}
foreach (var tr in TransitionSequences)
{
if (tr.Name == test.RelTransBefore) transitionBefore = tr;
if (tr.Name == test.RelTransBetween) transitionBetween = tr;
if (tr.Name == test.TransitionAfter) transitionAfter = tr;
}
if (pout.Scale == null)
{
errorMsg = string.Format("No balance specified in path {0}", test.OutputPath);
return false;
}
if (Program.LocalSettings.RealDensity < 500.0f || Program.LocalSettings.RealDensity > 2000.0f)
{
errorMsg = string.Format("Density was not specified");
return false;
}
errorMsg = string.Empty;
return true;
}
/// <summary>
/// Updates paths based on the selected test
/// </summary>
public static MetersPath GetMetersPath(Test test)
{
MetersPath pmtrs = null;
foreach (var path in metersPaths)
{
if (test.MetersPath == path.Name) { pmtrs = new MetersPath(path, components); break; }
}
if (pmtrs != null)
{
int count = Math.Min(Config.Data.WMsCount, pmtrs.RegisterReaders.Length);
for (int i = 0; i < count; i++)
{
if ((pmtrs.RegisterReaders[i] != null) &&
(pmtrs.RegisterReaders[i].Cfg.DebugLevel == DebugMode.DetectedOff))
{
pmtrs.RegisterReaders[i] = null;
}
}
}
return pmtrs;
}
/// <summary>
/// Stops the state machine (and the worker thread)
/// </summary>
public static void Stop()
{
if (stateMachineRunning) quitStateMachine = true;
}
/*
* This is and example sequence of RunDeviceBefore() / RunOperations() / RunDeviceAfter() calls
* as they are executed during normal run from the progran start to the end.
*
foreach (var device in devices) device.RunDeviceBefore(); . . . . . . in StateMachine.Worker()
State.Create(...).AddOperation(...).AddOperation(...).EnterState() . . in the sequence in Execute(...)
foreach (var device in devices) device.RunDeviceAfter(); . . . . . . . in WaitRunDevsRunOps()
WaitNextTick() (may throw QuitStateMachineException) . . . . . . . . in WaitRunDevsRunOps()
foreach (var device in devices) device.RunDeviceBefore(); . . . . . . in WaitRunDevsRunOps()
IList<Event> events = State.RunOperations(); . . . . . . . . . . . . . in WaitRunDevsRunOps()
foreach (var device in devices) device.RunDeviceAfter(); . . . . . . . in WaitRunDevsRunOps()
WaitNextTick() (may throw QuitStateMachineException) . . . . . . . . in WaitRunDevsRunOps()
foreach (var device in devices) device.RunDeviceBefore(); . . . . . . in WaitRunDevsRunOps()
IList<Event> events = State.RunOperations(); . . . . . . . . . . . . . in WaitRunDevsRunOps()
State.Create(...).AddOperation(...).AddOperation(...).EnterState() . . in the sequence in Execute(...)
foreach (var device in devices) device.RunDeviceAfter(); . . . . . . . in WaitRunDevsRunOps()
WaitNextTick() (may throw QuitStateMachineException) . . . . . . . . in WaitRunDevsRunOps()
foreach (var device in devices) device.RunDeviceBefore(); . . . . . . in WaitRunDevsRunOps()
IList<Event> events = State.RunOperations(); . . . . . . . . . . . . . in WaitRunDevsRunOps()
foreach (var device in devices) device.RunDeviceAfter(); . . . . . . . in WaitRunDevsRunOps()
WaitNextTick() (assume QuitStateMachineException thrown) . . . . . . in WaitRunDevsRunOps()
State.StopOperations(); . . . . . . . . . . . . . . . . . . . . . . . in StateMachine.Worker() catch()
foreach (var device in devices) device.StopDevice(); . . . . . . . . . in StateMachine.Worker() catch()
*/
/// <summary>
/// Worker thread: calls Start(), Run() and Stop() methods of operations.
/// It uses 'currentState', 'nextState' and 'quitStateMachine' static fields.
/// </summary>
static void Worker()
{
startDateTime = DateTime.Now;
CycleStartTimeStamp = startDateTime; /// To prevent it is undefined
wlog.InfoFormat(" currentTime = {0}s startDateTime = {1}", currentTimeSec.ToString(), startDateTime.ToString());
/// Run all devices for the first time
foreach (var device in devices) device.RunDeviceBefore();
try
{
SequenceBase.ReferenceFlowmetersCount = SequenceBase.FlowMeters.Count;
SequenceBase.CalibratedLtrPerRefPulse = new float[SequenceBase.ReferenceFlowmetersCount];
foreach (var flowmtr in SequenceBase.FlowMeters)
{
int ix = flowmtr.Idx1;
if (ix > 0 && ix <= SequenceBase.ReferenceFlowmetersCount)
{
SequenceBase.CalibratedLtrPerRefPulse[ix - 1] = flowmtr.NominalFlow / 7200.0f;
}
}
(new Sequences.MainSeq()).Execute(null);
}
catch (QuitStateMachineException)
{
}
}
/// <summary>
/// Do stuff that is repeated in the state execution loops most often
/// </summary>
/// <returns>List of Event-s returned from the state operations, null == quit</returns>
public static IList<Event> WaitRunDevsRunOps()
{
foreach (var device in devices) device.RunDeviceAfter();
if (WaitNextTick())
{
///
/// Executed when the state machine is stopped
///
wlog.Fatal("quitStateMachine == true ... The last StopOperaions() start now");
State.StopOperations();
stateMachineRunning = false;
wlog.Fatal("StopOperaions() completed ... stateMachineRunning = false)");
throw new QuitStateMachineException();
}
foreach (var device in devices) device.RunDeviceBefore();
IList<Event> events = State.RunOperations();
return events;
///
/// This is followed by a state change in the sequence
///
}
/// <summary>
/// Wait time period - synchronize
/// </summary>
/// <returns>true when interrupted by 'quitStateMachine', otherwise false</returns>
public static bool WaitNextTick()
{
currentTimeSec += Period;
TimeSpan timeFromStart = TimeSpan.FromSeconds(currentTimeSec);
DateTime nextLoopDateTime = startDateTime + timeFromStart;
while (DateTime.Now < nextLoopDateTime)
{
Thread.Sleep(100);
if (quitStateMachine) return true;
}
return false;
}
}
}