/// /// Copyright (c) 2013-2022 Sensus Slovensko a.s. /// using System; using System.Text; using System.Threading; using System.Collections.Generic; using log4net; using NHibernate; using Common; using Config.Entities; using TBF.Rig.Generic; using TBF.Rig.GenericDevices; using TBF.Rig.Sequences; using Dirichlet.Numerics; using TBF.Resources; using TBF.Rig.RegisterReaders.iPerlReaderUNI; using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; namespace TBF.Rig { public enum MachineState { Undefined = 0, Disabled, StartingUp, /// Start-up: Initialize() of all devices + start of the worker thread FailedToStart, /// Most likely Initialize() of any device thrown an exception Running, /// RunDeviceBefore() and RunDeviceAfter() of all devices, Start()/Run()/Stop() of operations ShuttingDown, /// Shutdown: StopDevice() and StopDevice2() of all devices Off, /// Off after the shutdown } /// /// This class controls real test bench behavior. /// It is based on a state machine /// 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 components; /// list of all components static IList devices; /// list of devices /// /// Bench paths static IList feedingPaths; static IList benchPaths; static IList outputPaths; static IList metersPaths; #if HEAT_METERS static IList heatMetersPaths; #endif public static IList TransitionSequences; public static IList TransitionSteps; /// Public components public static ControlBoard.IControlBoard ControlBoardMain; public static TestMethods.CombinedWithDetection.TestMethod QriseDetection; public static TestMethods.CombinedWithDetection.TestMethod QfallDetection; public static GenericDevices.IAmbient Ambient; public static IOperation BenchWaitingOp; public static IOperation BenchErrorOp; 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; public static IList MasterValves = new List(); /// list of directly controlled valves public static IList CoupledValves = new List(); /// list of coupled valves public static IList LogicalFnValves = new List(); /// list of logical function valves public static IList LoopStartNames; public static IList LoopEndNames; /// Test method classes fo all test methods public static Dictionary TestMethod2Class; /// Time and synchronization public const int Period = 1; /// State machine period in sec. 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 public static MachineState MachineState; /// see enum MachineState for explanation /// Worker thread and database session static Thread workerThread; static IList states; /// list of states public static DateTime CycleStartTimeStamp; public static IList DefaultValvesOpen { get { return BuiltIn.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 DefaultValvesClose { get { return BuiltIn.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) ); } } /// /// Loaded by LoadProcedure() or IOperation LoadProcedureOp(...) /// public static Procedure Procedure; /// Procedure public static TestInstance[] TestInstances; /// Tests public static bool IsRemoteProcedure; /// Hardware devices connected to the PC controlling the bench. public static IList Components { get { return components; } } public static IList Devices { get { return devices; } } /// /// DateTime of time instance when the state machine worker thread starts /// public static DateTime StartDateTime { get { return startDateTime; } } /// /// Current state name /// public static int Time { get { return currentTimeSec; } set { currentTimeSec = value; } } /// /// Constructor /// static StateMachine() { currentTimeSec = 0; /// devices = new List(); states = new List(); TestMethod2Class = new Dictionary(); ProcessData.IperlHeads = new List(); ProcessData.SmartHeadsUni = new List(); SequenceBase.FlowMeters = new List(); SequenceBase.RegVPositions = new List(); SequenceBase.PumpsWithFM = new List(); SequenceBase.WaterMeters = new List(); SequenceBase.Cameras = new List(); LoopStartNames = new List(); LoopEndNames = new List(); } /// /// Add a state to the state machine. /// In this way a sequence can be created programtically. /// /// State public static void AddState(State state) { if (state != null && !states.Contains(state)) states.Add(state); } /// /// Remove the state from the state machine. /// /// State public static void RemoveState(State state) { if (state != null && state != State.CurrentState && states.Contains(state)) states.Remove(state); } /// /// Get a state from a label /// /// /// The matching state or null 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; } /// /// 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(). /// /// Mode of operation /// A copy of bench data used by the state machine /// Identifies the initial state public static void InitializeBoardEtc(IList cmpntEntities) { /// Load the list of components (entities) from the database. /// Then create the components (derived from IComponent). // Process components from the database components = Rig.TbfComponents.LoadComponentsFromDB(cmpntEntities); MasterValves = BuiltIn.ValveBase.GetMasterValves(components); CoupledValves = BuiltIn.ValveBase.CoupledValves(components); LogicalFnValves = BuiltIn.ValveBase.LogicalFnValves(components); ProcessData.ComponentsWithMeasuredVal.Clear(); ProcessData.ComponentsWithSetpoint.Clear(); ProcessData.ComponentsWithCustomBmp.Clear(); /// Find all balances (to initialize tank capacities in the control board) /// Find the control board UInt128 valvesToInvert = 0; foreach (var cmpnt in components) { if (cmpnt is ControlBoard.IControlBoard) ControlBoardMain = cmpnt as ControlBoard.IControlBoard; if (cmpnt is IBenchInfo) ProcessData.BenchInfo = cmpnt as IBenchInfo; if (cmpnt is IErrorFlags) ProcessData.ErrorFlagsComp = cmpnt as IErrorFlags; if (cmpnt is IStatisticsMonitoring) ProcessData.StatisticsMonitoringComp = cmpnt as IStatisticsMonitoring; if (cmpnt is TestMethods.CombinedWithDetection.TestMethod) { if ((cmpnt as TestMethods.CombinedWithDetection.TestMethod).IsRise) { if (QriseDetection == null) QriseDetection = cmpnt as TestMethods.CombinedWithDetection.TestMethod; } else { if (QfallDetection == null) QfallDetection = cmpnt as TestMethods.CombinedWithDetection.TestMethod; } } if (cmpnt is SchematicDrawing.IDrawingItCmpntWithMeasuredVal) { ProcessData.ComponentsWithMeasuredVal.Add(cmpnt as SchematicDrawing.IDrawingItCmpntWithMeasuredVal); } if (cmpnt is SchematicDrawing.IDrawingItCmpntWithSetpoint) { ProcessData.ComponentsWithSetpoint.Add(cmpnt as SchematicDrawing.IDrawingItCmpntWithSetpoint); } if (cmpnt is SchematicDrawing.IDrawingItCmpntWithCustomBmp) { ProcessData.ComponentsWithCustomBmp.Add(cmpnt as SchematicDrawing.IDrawingItCmpntWithCustomBmp); } if ((cmpnt is Output.DB.SensusOracle.Database) && (ProcessData.OracleDB == null)) { /// The 1st Oracle database component found --> store the reference ProcessData.OracleDB = cmpnt as Output.DB.SensusOracle.Database; } if ((cmpnt is Output.DB.ProductionTracing.Tracing) && (ProcessData.TracingDB == null)) { /// The 1st production tracing database component found --> store the reference ProcessData.TracingDB = cmpnt as Output.DB.ProductionTracing.Tracing; } if (cmpnt is ITestMethod) { TestMethod2Class.Add(cmpnt.Name, cmpnt.ClassName); } if (cmpnt is TestMethods.iPerlCommunication.iPerlHead.IperlHead iPerlHead) { ProcessData.IperlHeads.Add(iPerlHead); } if (cmpnt is ISmartReader smartReader) // All SmartReader components are added to the list of SmartReaders { ProcessData.SmartHeadsUni.Add(smartReader); } if (cmpnt is IFlowMeter) SequenceBase.FlowMeters.Add(cmpnt as IFlowMeter); if ((cmpnt is IRegValve) && !(cmpnt is Rig.Uni.RegValveTandem.RegValveTandem)) { SequenceBase.RegVPositions.Add(new RegValvePosition(cmpnt as IRegValve)); } if (cmpnt is IPumpFM && !(cmpnt is BuiltIn.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 GenericDevices.IParallelOutput && cmpnt.Name.ToLower().Contains("tower")) { BenchWaitingOp = (cmpnt as GenericDevices.IParallelOutput).ShowBenchWaitingOp(); BenchErrorOp = (cmpnt as GenericDevices.IParallelOutput).ShowBenchErrorOp(); } if (cmpnt is ITestMethod && cmpnt.ClassName == "TestMethods.OuterLoop.Start") LoopStartNames.Add(cmpnt.Name); if (cmpnt is ITestMethod && cmpnt.ClassName == "TestMethods.OuterLoop.End") LoopEndNames.Add(cmpnt.Name); BuiltIn.Valve.Valve plainValve = (cmpnt as BuiltIn.Valve.Valve); if ((plainValve != null) && plainValve.Inverted) valvesToInvert |= plainValve.Mask; log.Debug( string.Format( "InitializeBoardEtc() ... {0} {1}", cmpnt.Name, cmpnt.ClassName ) ); cmpnt.StartChangeHandler(); /// Start handling parameter change events } /// Pre-initialize the control board (= buffer the arguments ctrlBrdComponent, tankCapacities) if (ControlBoardMain == null) { throw new Exception("Control board component is missing"); } else { ControlBoardMain.SpecifyInvertedValves(valvesToInvert); } } [Obsolete("Use InitializeBoardEtc() instead.")] public static void InitializeBoardEtc_Fake(IList componentList) { components = componentList; // base find components - find control board!, ... foreach (var cmpnt in components) { if (cmpnt is ControlBoard.IControlBoard) ControlBoardMain = cmpnt as ControlBoard.IControlBoard; if (cmpnt is IBenchInfo) ProcessData.BenchInfo = cmpnt as IBenchInfo; if (cmpnt is IErrorFlags) ProcessData.ErrorFlagsComp = cmpnt as IErrorFlags; if (cmpnt is IStatisticsMonitoring) ProcessData.StatisticsMonitoringComp = cmpnt as IStatisticsMonitoring; } } public static string CurrentlyInitializedComponentName; /// public static string InitializeDevices() { 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; } if (cmpnt is IDevice) { devices.Add(cmpnt as IDevice); /// Only components that were initialized are added UiBridge.Bridge.OnActivity(null, cmpnt.Name); /// Info } CurrentlyInitializedComponentName = cmpnt.Name; cmpnt.Initialize(); } CurrentlyInitializedComponentName = "---"; /// /// (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 (Tank1 == null) { Tank1 = tank; DrainValve1 = tank.DrainValve; log.WarnFormat("InitializeBoardEtc() ... Scale1={0} Draining valve1={1}", tank.Name, (DrainValve1 != null) ? DrainValve1.Name : "---"); } else if (Tank2 == null) { Tank2 = tank; DrainValve2 = tank.DrainValve; log.WarnFormat("InitializeBoardEtc() ... Scale2={0} Draining valve2={1}", tank.Name, (DrainValve2 != null) ? DrainValve2.Name : "---"); } else if (Tank3 == null) { Tank3 = tank; 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; } /// /// Stop devices that were started by InitializeDevices(). /// Works correctly also after exeption from InitializeDevices() as only ... /// ... correctly started devices were added in 'devices' list. /// public static void StopDevices() { /// StopDevices() functions of devices are called in /// reversed order so that the parent compnent's StopDevices() /// is called after calling StopDevices() of it's children. for (int i = devices.Count - 1; i >= 0; i--) { var dev = devices[i]; UiBridge.Bridge.OnActivity(null, string.Format("1. {0}", dev.Name)); /// Info dev.StopDevice(); } } /// /// Stop devices that were started by InitializeDevices(). /// Works correctly also after exeption from InitializeDevices() as only ... /// ... correctly started devices were added in 'devices' list. /// public static void StopDevices2() { /// StopDevices2() functions of devices are called in /// reversed order so that the parent compnent's StopDevices2() /// is called after calling StopDevices2() of it's children. for (int i = devices.Count - 1; i >= 0; i--) { var dev = devices[i]; UiBridge.Bridge.OnActivity(null, string.Format("2. {0}", dev.Name)); /// Info dev.StopDevice2(); } } /// /// Checks remote and local configuration DB paths and transitions for compatibility /// /// true when DB-s are compatible public static bool IsRemoteDBCompatible(out string message) { IList remoteFeedingPaths; IList remoteBenchPaths; IList remoteOutputPaths; IList remoteMetersPaths; IList remoteTransitions; try { ISession remoteSession = TBF.DB.CreateSession(DBKind.RemoteConfig); remoteFeedingPaths = remoteSession.QueryOver().List(); remoteBenchPaths = remoteSession.QueryOver().List(); remoteOutputPaths = remoteSession.QueryOver().List(); remoteMetersPaths = remoteSession.QueryOver().List(); remoteTransitions = remoteSession.QueryOver().List(); } catch (Exception) { message = "Cannot open shared database"; return false; } ISession localSession = TBF.DB.CreateSession(DBKind.Config); var localFeedingPaths = localSession.QueryOver().List(); var localBenchPaths = localSession.QueryOver().List(); var localOutputPaths = localSession.QueryOver().List(); var localMetersPaths = localSession.QueryOver().List(); var localTransitions = localSession.QueryOver().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; } /// /// /// /// FeedingPath, BenchPath, OutputPath, MetersPath or TransitionSequence /// Local list of paths or transitions /// Remote list of paths or transitions /// Message specifying a cause of incompatibility /// true when lists are compatible, otherwise false private static bool IsCompatible(IList localList, IList 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; } /// /// Start the state machine /// public static void Start() { if (MachineState != MachineState.StartingUp) { /// Unexpected MachineState (not MachineState.StartingUp) --> prevent worker thread from starting /// MachineState = MachineState.FailedToStart; return; } workerThread = new Thread(Worker); workerThread.CurrentCulture = Thread.CurrentThread.CurrentCulture; workerThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; workerThread.Start(); } /// /// Loads all paths and transitions from the DB. /// Updates StatMachine.feedingPaths ... StatMachine.meterPaths, StatMachine.TransitionSequences /// public static void LoadPathsAndTransitions(ISession session) { feedingPaths = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); benchPaths = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); outputPaths = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); metersPaths = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); TransitionSequences = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); TransitionSteps = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); #if HEAT_METERS heatMetersPaths = session.QueryOver().OrderBy(x => x.ItemNr).Asc.List(); #endif } /// /// Loads selected procedure from the DB. /// Updates StateMachine.Procedure and StateMachine.Tests /// public static bool LoadProcedure(ISession session, string procedureName, bool isRemote, IList autoTests = null) { Procedure = null; IList selectedProcs = session.QueryOver() .Where(x => (x.ProcedureState == ProcedureState.Active)) .And(x => (x.Name == procedureName)) .List(); if (selectedProcs.Count == 1) { IsRemoteProcedure = isRemote; Procedure = selectedProcs[0]; TestInstances = selectedProcs[0].UpdateTestInstances(LoopStartNames, LoopEndNames, autoTests); return true; } else { return false; } } 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); } } /// /// Called from the sequence to update paths based on the selected test /// /// Selected test /// /// /// /// /// Transition sequence entity /// Transition sequence entity /// Error message in case of incorrect configuration /// true when loaded configuration is correct (all four paths are defined !=null, etc.) 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) { ITestMethod tm = TbfComponents.FindComponent(test.Method) as ITestMethod; bool isHydroTest = (tm != null) ? tm.DoTransitions() : false; 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 (isHydroTest && (pfeed == null)) errorMsg = string.Format(Strings.Test_0_config_error_1_is_missing, test.Name, Strings.Feeding_chdr); else if (isHydroTest && (pben == null)) errorMsg = string.Format(Strings.Test_0_config_error_1_is_missing, test.Name, Strings.Bench_chdr); else if (isHydroTest && (pout == null)) errorMsg = string.Format(Strings.Test_0_config_error_1_is_missing, test.Name, Strings.Output_chdr); else if (pmtrs == null) errorMsg = string.Format(Strings.Test_0_config_error_1_is_missing, test.Name, Strings.Sensors_chdr); else errorMsg = string.Empty; if ((isHydroTest && (pfeed == null || pben == null || pout == null)) || (pmtrs == null)) { log.ErrorFormat("GetPaths({0},...) returns false, wrong configuration", test.Name); 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) { log.ErrorFormat("GetPaths({0},...) returns false, missing heat meters sensors", test.Name); errorMsg = "Cannot load heat meters sensors"; return false; } foreach (var tr in TransitionSequences) { if (tr.Name == test.TransBefore) transitionBefore = tr; if (tr.Name == test.TransBetween) transitionBetween = tr; if (tr.Name == test.TransAfter) transitionAfter = tr; } if (isHydroTest && pout.Scale == null) { log.ErrorFormat("GetPaths({0},...) returns false, missing scales in output path {1}", test.Name, test.OutputPath); errorMsg = string.Format("No balance specified in path {0}", test.OutputPath); return false; } if (Formulas.RealDensity() < 500.0f || Formulas.RealDensity() > 2000.0f) { log.ErrorFormat("GetPaths({0},...) returns false, density was not specified", test.Name); errorMsg = string.Format("Density was not specified"); return false; } log.WarnFormat("GetPaths({0},...) returns true, paths loaded OK, isHydroTest={1}", test.Name, isHydroTest); errorMsg = string.Empty; return true; } /// /// Updates paths based on the selected test /// 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(TBF.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; } /* * 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 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 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 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() */ /// /// Worker thread: calls Start(), Run() and Stop() methods of operations. /// It uses 'currentState', 'nextState' and 'quitStateMachine' static fields. /// static void Worker() { MachineState = MachineState.Running; startDateTime = DateTime.Now; CycleStartTimeStamp = startDateTime; /// Do not leave CycleStartTimeStamp uninitialized 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(); SequenceBase.ReferenceFlowmetersCount = SequenceBase.FlowMeters.Count; SequenceBase.CalibratedLtrPerRefPulse = new double[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.0; } } /// /// Run the main sequence /// (new Sequences.MainSeq()).Execute(); StateMachine.MachineState = MachineState.Off; /// Update StateMachine.MachineState TBF.UiBridge.Bridge.Bench2UI(TBF.UiBridge.ButtonsEtc.Shutdown); /// Let UI know the state machine is shut down now } /// /// Do stuff that is repeated in the state execution loops most often /// /// List of Event-s returned from the state operations, null == quit public static IList WaitRunDevsRunOps(bool lastTime = false) { /// RunDeviceAfter() functions of devices are called in /// reversed order so that the parent compnent's RunDeviceAfter() /// is called after calling RunDeviceAfter() of it's children. for (int i = devices.Count - 1; i >= 0; i--) { devices[i].RunDeviceAfter(); } WaitNextTick(); if (lastTime) { /// /// Executed when the state machine is being stopped /// State.StopOperations(); return null; } foreach (var device in devices) { device.RunDeviceBefore(); } return State.RunOperations(); /// Returns events from all running operations /// This is followed by a state change in the sequence } /// /// Wait time period - synchronize /// /// true when interrupted by 'quitStateMachine', otherwise false public static void WaitNextTick() { int currentTimeSecFromClock = Convert.ToInt32(Math.Round((DateTime.Now - startDateTime).TotalSeconds - 0.2)); currentTimeSec = Math.Max(currentTimeSec, currentTimeSecFromClock) + Period; DateTime nextTickDateTime = startDateTime + TimeSpan.FromSeconds(currentTimeSec); while (DateTime.Now < nextTickDateTime) { Thread.Sleep(100); } } } }