537 lines
21 KiB
C#
537 lines
21 KiB
C#
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>
|
|
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.FlowMeters = new List<IFlowMeter>();
|
|
SequenceBase.RegulValves = new List<IRegulValve>();
|
|
SequenceBase.PumpsWithFM = new List<IPumpFM>();
|
|
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 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; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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) (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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
}
|