tbf/TestBenchFramework/BenchControl/StateMachine.cs

529 lines
20 KiB
C#
Raw Normal View History

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
{
}
/// <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<Entities.FeedingPath> feedingPaths;
static IList<Entities.BenchPath> benchPaths;
static IList<Entities.OutputPath> outputPaths;
static IList<Entities.MetersPath> metersPaths;
public static IList<Entities.TransitionSequence> 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<IValve> MasterValves; /// list of directly controlled valves
public static IList<IValve> 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<State> states; /// list of states
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 Entities.Procedure Procedure; /// Procedure
public static IList<Entities.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>
2014-08-01 06:36:17 +00:00
public static int Time { get { return currentTimeSec; } }
/// <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
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<IBalance> balances = new List<IBalance>();
SequenceBase.RegulValves = new List<IRegulValve>();
SequenceBase.WaterMeters = new List<IWaterMeter>();
SequenceBase.Cameras = new List<ICamera>();
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 IRegulValve) SequenceBase.RegulValves.Add(cmpnt as IRegulValve);
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; }
}
}
}
}
/// <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;
}
public static void LoadProcedure(bool loadPathsOnly)
{
ISession session = FluentCommon.CreateSession(Database.Procedures);
WtSession = session;
feedingPaths = session.CreateQuery("FROM FeedingPath ORDER BY ItemNr").List<Entities.FeedingPath>();
benchPaths = session.CreateQuery("FROM BenchPath ORDER BY ItemNr").List<Entities.BenchPath>();
outputPaths = session.CreateQuery("FROM OutputPath ORDER BY ItemNr").List<Entities.OutputPath>();
metersPaths = session.CreateQuery("FROM MetersPath ORDER BY ItemNr").List<Entities.MetersPath>();
TransitionSequences = session.CreateQuery("FROM TransitionSequence ORDER BY ItemNr").List<Entities.TransitionSequence>();
// 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<Entities.Procedure> selectedProcs = session
.CreateQuery("FROM Procedure WHERE Name = :name")
.SetParameter("name", TBF.UiBridge.Bridge.SelectedProcedureName)
.List<Entities.Procedure>();
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)
{
if (cmpnt.Cfg.Name.Equals("ROI2"))
{
int a = 5;
}
(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);
}
}
/// <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 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;
}
/// <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>
/// <returns>true when all four paths are defined (non null)</returns>
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);
}
/// <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;
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
{
(new Sequences.MainSeq()).Execute(null);
}
catch (QuitStateMachineException)
{
State.StopOperations();
foreach (var device in devices) device.StopDevice();
quitStateMachine = false;
stateMachineRunning = false;
}
}
/// <summary>
/// Do stuff that is repeated in the state execution loops most often
/// </summary>
/// <returns>List of Event-s returned from the state operations</returns>
public static IList<Event> WaitRunDevsRunOps()
{
foreach (var device in devices) device.RunDeviceAfter();
WaitNextTick();
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 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);
}
}
}