using System; using System.Text; using System.Threading; using System.Diagnostics; using System.Collections.Generic; using System.Windows.Forms; using log4net; using NHibernate; using TBF.BenchControl.Generic; using TBF.BenchControl.GenericDevices; using TBF.BenchControl.Sequences; namespace TBF.BenchControl { public class QuitStateMachineException : Exception { } /// /// 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; public static IList TransitionSequences; /// Public components public static Elde.ControlBoardDev ControlBoard; public static GenericDevices.IAmbient Ambient; public static GenericDevices.IBalance Balance1; public static GenericDevices.IBalance Balance2; public static GenericDevices.IBalance Balance3; public static GenericDevices.IValve EmptyTankValve1; public static GenericDevices.IValve EmptyTankValve2; public static GenericDevices.IValve EmptyTankValve3; public static IList MasterValves; /// list of directly controlled valves public static IList CoupledValves; /// list of coupled valves /// 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 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; public static ISession WtSession; static IList states; /// list of states public static IList 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 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) ); } } /// /// Loaded by LoadProcedure() or IOperation LoadProcedureOp(...) /// public static Entities.Procedure Procedure; /// Procedure public static IList Tests; /// Tests /// 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; } } /// /// Constructor /// static StateMachine() { devices = new List(); states = new List(); currentTimeSec = 0; quitStateMachine = false; } /// /// Add a device to the state machine. /// /// Device public static void AddDevice(IDevice device) { if (device != null && !devices.Contains(device)) devices.Add(device); } /// /// 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 #if DN100 public static void InitializeBoardEtc(ControlCom2VB.ControlCom2panel ctrlBrdComponent) #elif MUNICH public static void InitializeBoardEtc(ControlComponent3Munich.UserControl1 ctrlBrdComponent) #elif FUZHOU150 public static void InitializeBoardEtc(ControlComponent3Munich.UserControl1 ctrlBrdComponent) #elif FUZHOU300 public static void InitializeBoardEtc(ControlComponent3F300.UserControl1 ctrlBrdComponent) #endif { /// Load the list of components (entities) from the database. /// Then create the components (derived from IComponent). components = BenchControl.TbfComponents.LoadComponentsFromDB(FluentCommon.CreateSession(Database.Bench)); MasterValves = GenericDevices.ValveBase.MasterValves(components); CoupledValves = GenericDevices.ValveBase.CoupledValves(components); /// Find all balances (to initialize tank capacities in the control board) /// Find the control board IList balances = new List(); SequenceBase.FlowMeters = new List(); SequenceBase.RegulValves = new List(); SequenceBase.PumpsWithFM = new List(); SequenceBase.WaterMeters = new List(); SequenceBase.Cameras = new List(); ulong valvesToInvert = 0; foreach (var cmpnt in components) { if (cmpnt is Elde.ControlBoardDev) ControlBoard = cmpnt as Elde.ControlBoardDev; if (cmpnt is BenchId.Component) SequenceBase.BenchId = cmpnt as BenchId.Component; if (cmpnt is IFlowMeter) SequenceBase.FlowMeters.Add(cmpnt as IFlowMeter); if (cmpnt is IRegulValve) SequenceBase.RegulValves.Add(cmpnt as IRegulValve); if (cmpnt is IPumpFM) 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 IBalance) { IBalance balance = cmpnt as IBalance; balances.Add(balance); if (balance.BalanceNr == 0) Balance1 = balance; else if (balance.BalanceNr == 1) Balance2 = balance; else if (balance.BalanceNr == 2) Balance3 = balance; } Elde.Valve.Valve eldeValve = (cmpnt as Elde.Valve.Valve); if ((eldeValve != null) && eldeValve.Inverted) valvesToInvert |= eldeValve.Mask; } /// Create an array with tank capacities float[] tankCapacities = new float[balances.Count]; for (int i = 0; i < tankCapacities.Length; i++) tankCapacities[i] = balances[i].Capacity; /// Pre-initialize the control board (= buffer the arguments ctrlBrdComponent, tankCapacities) ControlBoard.InitializeComponent(ctrlBrdComponent, valvesToInvert, tankCapacities); } public static void InitializeDevices() { /// Add all devices to the state machine and initialize them foreach (var cmpnt in components) { if (cmpnt is IDevice) { AddDevice(cmpnt as IDevice); (cmpnt as IDevice).Initialize(); } } /// Propagate debug levels from parents to children when necessary foreach (var cmpnt in components) { if (cmpnt.Cfg is IChildComponentCfg && !string.IsNullOrEmpty(cmpnt.Cfg.ParentName) && (cmpnt.Cfg.DebugLevel == Entities.DebugMode.Inherit || cmpnt.Cfg.DebugLevel == Entities.DebugMode.AutoDetect)) { foreach (var par in components) { if (par.Cfg.Name.Equals(cmpnt.Cfg.ParentName)) { cmpnt.Cfg.DebugLevel = par.Cfg.DebugLevel; break; } } } } } /// /// Start the state machine /// 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; } public static void LoadProcedure(bool loadPathsOnly) { ISession session = FluentCommon.CreateSession(Database.Procedures); WtSession = session; feedingPaths = session.CreateQuery("FROM FeedingPath ORDER BY ItemNr").List(); benchPaths = session.CreateQuery("FROM BenchPath ORDER BY ItemNr").List(); outputPaths = session.CreateQuery("FROM OutputPath ORDER BY ItemNr").List(); metersPaths = session.CreateQuery("FROM MetersPath ORDER BY ItemNr").List(); TransitionSequences = session.CreateQuery("FROM TransitionSequence ORDER BY ItemNr").List(); // Automatic detection of empty tank valves foreach (var opath in outputPaths) { if ((EmptyTankValve1 == null) && (Balance1 != null) && (opath.Balance == Balance1.Cfg.Name)) { EmptyTankValve1 = TbfComponents.FindComponent(opath.EmptyTankValve) as IValve; } if ((EmptyTankValve2 == null) && (Balance2 != null) && (opath.Balance == Balance2.Cfg.Name)) { EmptyTankValve2 = TbfComponents.FindComponent(opath.EmptyTankValve) as IValve; } if ((EmptyTankValve3 == null) && (Balance3 != null) && (opath.Balance == Balance3.Cfg.Name)) { EmptyTankValve3 = TbfComponents.FindComponent(opath.EmptyTankValve) as IValve; } } if (loadPathsOnly) return; IList selectedProcs = session .CreateQuery("FROM Procedure WHERE Name = :name") .SetParameter("name", TBF.UiBridge.Bridge.SelectedProcedureName) .List(); if (selectedProcs.Count != 1) return; Procedure = selectedProcs[0]; Tests = selectedProcs[0].Tests; } public static void LoadProcedureParams(Entities.Procedure procedure) { foreach (var cmpnt in components) { if (cmpnt is ComponentBase) (cmpnt as ComponentBase).GetProcedureParams(procedure); } } public static void LoadTestParams(Entities.Test test) { foreach (var cmpnt in components) { if (cmpnt is ComponentBase) (cmpnt as ComponentBase).GetTestParams(test); } } /// /// Processes the selection done by the bench control panel in the main sequence. /// /// Selection.Q1, .Q2, .Q3 or .Test /// The selected test or null public static Entities.Test GetTest(Sequences.MainSeq.Selection selection) { if ((selection == Sequences.MainSeq.Selection.Q1) && (Tests.Count >= 1)) { return Tests[0]; } else if ((selection == Sequences.MainSeq.Selection.Q2) && (Tests.Count >= 2)) { return Tests[1]; } else if ((selection == Sequences.MainSeq.Selection.Q3) && (Tests.Count >= 3)) { return Tests[2]; } else if (selection == Sequences.MainSeq.Selection.Test) { foreach (var test in Tests) if (test.Name.Contains(TBF.UiBridge.Bridge.SelectedTestName)) return test; } return null; } /// /// Called from the sequence to update paths based on the selected test /// /// Selected test /// /// /// /// /// true when all four paths are defined (non null) public static bool GetPaths(Entities.Test test, out FeedingPath pfeed, out BenchPath pben, out OutputPath pout, out MetersPath pmtrs, out Entities.TransitionSequence transitionStart, out Entities.TransitionSequence transitionEnd) { pfeed = null; pben = null; pout = null; pmtrs = null; transitionStart = null; transitionEnd = 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; } } foreach (var path in metersPaths) { if (test.MetersPath == path.Name) { pmtrs = new MetersPath(path, components); break; } } if (pmtrs != null) { for (int i = 0; i < Program.WMsCount; i++) { if ((pmtrs.RegisterReaders[i] != null) && (pmtrs.RegisterReaders[i].Cfg.DebugLevel == Entities.DebugMode.DetectedOff)) { pmtrs.RegisterReaders[i] = null; } } } foreach (var transition in TransitionSequences) { if (transition.Name == test.TransitionStart) transitionStart = transition; if (transition.Name == test.TransitionEnd) transitionEnd = transition; } return (pfeed != null) && (pben != null) && (pout != null) && (pmtrs != null); } /// /// Stops the state machine (and the worker thread) /// 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 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() { startDateTime = DateTime.Now; 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.LtrPerRefPulse = new float[SequenceBase.ReferenceFlowmetersCount]; foreach (var flowmtr in SequenceBase.FlowMeters) { int ix = flowmtr.Position; if (ix > 0 && ix <= SequenceBase.ReferenceFlowmetersCount) { SequenceBase.LtrPerRefPulse[ix - 1] = flowmtr.NominalFlow / 7200.0f; } } (new Sequences.MainSeq()).Execute(null); } catch (QuitStateMachineException) { State.StopOperations(); foreach (var device in devices) device.StopDevice(); quitStateMachine = false; stateMachineRunning = false; } } /// /// Do stuff that is repeated in the state execution loops most often /// /// List of Event-s returned from the state operations public static IList WaitRunDevsRunOps() { foreach (var device in devices) device.RunDeviceAfter(); WaitNextTick(); foreach (var device in devices) device.RunDeviceBefore(); IList events = State.RunOperations(); return events; /// 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() { currentTimeSec += Period; TimeSpan timeFromStart = TimeSpan.FromSeconds(currentTimeSec); DateTime nextLoopDateTime = startDateTime + timeFromStart; while (DateTime.Now < nextLoopDateTime) { if (quitStateMachine) { quitStateMachine = false; wlog.Info("quitStateMachine == true ... going to stop the StateMachine()"); throw new QuitStateMachineException(); } Thread.Sleep(100); } wlog.DebugFormat(" currentTime = {0}s", currentTimeSec); } } }