/// /// Copyright (c) 2021 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using log4net; using Common; using Config.Entities; using Dirichlet.Numerics; using SchematicDrawing; using TBF.Boxes; using TBF.Rig.GenericDevices; using TBF.Rig.Sequences; using TBF.UiBridge; namespace TBF.Rig.ControlBoard.Uni { public class UniCB : ComponentBase, IAdvancedControlBoard, SchematicDrawing.IDrawingItCmpntWithMeasuredVal { private static readonly ILog log = LogManager.GetLogger(typeof(UniCB)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } /// TBF component configuration and useful wrappers readonly UniCBCfg cbCfg; string comPort { get { return string.Format("COM{0}", cbCfg.ComPortNr); } } short config { get { return (short)((cbCfg.DrainValvesCount << 8) + ((cbCfg.DivertersCount - 1) << 3) + cbCfg.RegValvesCount - 4); } } /// IDrawingItCmpnt interface implementation public SchematicDrawing.IDrawingItem DrawingItem { get { return cbCfg as SchematicDrawing.IDrawingItem; } } public bool MsrmntAvailable { get { return true; } } public double MeasuredVal { get { return 0; } } StringBuilder altString = new StringBuilder(); public string AltString { get { if (cbCfg != null && cbCfg.ShowState) { altString.Clear(); altString.AppendFormat("State: {0}.{1}.{2}.{3}\n", (State >> 48) & 0xFF, (State >> 40) & 0xFF, (State >> 32) & 0xFF, Convert.ToUInt32(State & 0xFFFFFFFF).ToString("X6")); altString.AppendFormat("DI: {0}\n", Utils.ToBin40(DigitalInputs)); altString.AppendFormat("DO: {0}\n", Utils.ToBin40((ulong)Route >> 8)); altString.AppendFormat("AI0: {0:D3} AI1: {1:D3} AI2: {2:D3} AI3: {3:D3} AI4: {4:D3} AI5: {5:D3}\n", AnalogInput(0), AnalogInput(1), AnalogInput(2), AnalogInput(3), AnalogInput(4), AnalogInput(5)); altString.AppendFormat("AI8: {0:D3} AI9: {1:D3} AI10: {2:D3} AI11: {3:D3}\n", AnalogInput(8), AnalogInput(9), AnalogInput(10), AnalogInput(11)); altString.AppendFormat("Frequency: {0:F1} Hz Pulses: {1}", RefFrequency, RefPulses); return altString.ToString(); } return string.Empty; } } /// /// Serial communication /// SerialPort serialPort; /// Seral port or null, used in DebugMode.Normal, check serialPort.IsOpen before using it TextReader textReader; /// Used instead of serialPort in DebugMode.Simulate const int BUFFER_SIZE = 4000; /// Size of input buffers byte[] buffer; /// Buffer for data received in one attempt in RunDeviceBefore() byte[] rcvdData; /// Buffer for all recevied but nut processed data. May contain data from several read attempts int[] cumulativeSums; /// Cumulative sums to verify checksums easily int rcvdBytesCount; /// Count of valid bytes in rcvdData buffer int age0BytesCount; /// Just received bytes count int age1BytesCount; /// Count of bytes received 1 second ago DateTime lastSentTime; /// Time stamp of the last sent command/request frame DateTime lastReceivedTime; /// Time stamp of the last received valid frame int lastRcvdStMTime; /// StateMachine.Time time stamp of the last received valid frame bool outputsInitialized; /// Flag indicating that outputs were sent at least once /// /// Received data /// public GeneralData Data; DivTransitionData divTransitionData; ScopeAnalyzerData scopeAnalyzerData; SwitchCountersData switchCounterData; public UInt64 State { get { return Data.State; } } /// Control board state /// Current flow meter ID public int FlowmeterId { get { return Convert.ToInt32(Data.State & (ulong)StatusP.FlowmtrNrMask); } } public UInt64 DigitalInputs { get { return Data.Vstupy; } } /// Digital inputs public Int16 AnalogInput(int channel) { if (Data.AnalogInput != null && channel >= 0 && channel < Data.AnalogInput.Length) { return Data.AnalogInput[channel]; } else return 0; } /// /// Digital outputs / Route / States of electromagnetic valves /// UInt128 routeMask; /// Set in the constructor, bits of outputs that can be controlled by Action.ChangeRoute are set. /// Regulation valve control outputs and diverters cannot be controlled by Action.ChangeRoute /// Depends on the control board configuration ulong valvesToInvert; /// From configurations of valve and pump components. Inversion is applied when creating /// a message to be sent by serial port UInt128 displayedRoutePlusUI; /// A route displayed on the screen plus the modifications from UI (inversions are not applied) UInt128 bitsChangedByUI; /// Ones in this word indicate which valves/pumpes were changed in UI by mouse clicks UInt128 outputLatch; /// A copy of data sent to physical digital outputs (inversions are not applied) /// /// 128-bit word representing the current state of all valves and pumps (inversions are not applied) /// UInt128 route; public UInt128 Route { get { return route; } } /// /// Queue of higher level commands for the control board /// Queue actionQueue; bool isEmergencyStop; /// UI actions are blocked when true public bool IsUIBlocked { get; set; } /// Measured values valid during a test public double RefFrequency { get { return Data.ReferenceFreq[0]; } } public double RefFrequency1 { get { return Data.ReferenceFreq[1]; } } public double RefFrequency2 { get { return Data.ReferenceFreq[2]; } } public double RefFrequency3 { get { return Data.ReferenceFreq[3]; } } /// Measured values valid during and after a test public int RefPulses { get { return Data.EtPulses[0]; } } public int RefPulses1 { get { return Data.EtPulses[0] - Data.EtPulses2 - Data.EtPulses3; } } public int RefPulses2 { get { return Data.EtPulses2; } } public int RefPulses3 { get { return Data.EtPulses3; } } public int MassRefPulses { get { return Data.EtPulsesK; } } /// Last test results public double TestTime { get { return Data.Ttime; } } public float ValveOpenCloseTime { set; get; } /// Start valve open or close time in sec /// /// Select devices used for control and measurement /// /// public OutputPath Devices { set { devices = value; } get { return devices; } } OutputPath devices; IFlowMeter flowMeter { get { return devices.FlowMeter; } } IRegValve regValve { get { return devices.RegValve; } } IDiverter diverter { get { return devices.Diverter; } } /// /// Constructor for user interfaces (change settings only). /// Initialize(), RunDeviceXY() and StopDevice() methods of the created object will not be called. /// public UniCB() { } /// /// Constructor invoked when starting the test bench and creating components. /// public UniCB(Generic.IComponentCfg cfg) : base(cfg) { cbCfg = cfg as UniCBCfg; Data = new GeneralData(); log.Warn(this.ToString()); } /// /// Specify inverted valves /// /// public void SpecifyInvertedValves(UInt128 valvesToInvert) { this.valvesToInvert = (ulong)valvesToInvert; } #region IDevice interface /// /// Initialize control board device hardware /// public override void Initialize() { devices = new OutputPath(string.Empty, 0); actionQueue = new Queue(); actionQueue.Enqueue(Action.Stop(false)); /// Stop all outputsInitialized = false; divTransitionData = null; scopeAnalyzerData = null; switchCounterData = null; routeMask = 0x0FF1EFFFF00; for (int i = 4; i < cbCfg.RegValvesCount; i++) routeMask &= ~(((UInt128)3) << (2 * i)); for (int i = 1; i < cbCfg.DivertersCount; i++) routeMask &= ~(((UInt128)1) << (24 + i)); if (cbCfg.RelaysFInstalled) routeMask |= 0xFF0000000000; if (cbCfg.RelaysGInstalled) routeMask |= 0xFF000000000000; if (cbCfg.RelaysHInstalled) routeMask |= 0xFF00000000000000; lastRcvdStMTime = StateMachine.Time; /// To indicate the serial connection state lastSentTime = DateTime.Now; lastReceivedTime = DateTime.Now; /// Handler handling mouse clicks on the schematc drawinng - valve or pump state changes TBF.UiBridge.Bridge.RouteChangeHandler += delegate(object sndr, TBF.UiBridge.RouteChangeArgs args) { UInt128 oneBitMask = ((UInt128)1) << args.BitNr; lock (this) { bitsChangedByUI |= oneBitMask; displayedRoutePlusUI = displayedRoutePlusUI ^ oneBitMask; } }; if (cbCfg.DebugLevel == DebugMode.Normal) { buffer = new byte[BUFFER_SIZE]; rcvdData = new byte[BUFFER_SIZE]; cumulativeSums = new int[BUFFER_SIZE]; rcvdBytesCount = 0; age0BytesCount = 0; age1BytesCount = 0; serialPort = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One); serialPort.DtrEnable = true; serialPort.WriteTimeout = 4000; serialPort.ParityReplace = 0xFF; serialPort.Open(); log.FatalFormat("{0} initialized, routeMask = {1}", Name, Utils.ToBin88(routeMask)); } else if (cbCfg.DebugLevel == DebugMode.Replay) { buffer = new byte[BUFFER_SIZE]; rcvdData = new byte[BUFFER_SIZE]; cumulativeSums = new int[BUFFER_SIZE]; rcvdBytesCount = 0; age0BytesCount = 0; age1BytesCount = 0; serialPort = null; try { textReader = new StreamReader("C:\\TBF\\Simulate\\unicbsim.txt"); } catch (Exception) { throw new Exception("Missing file C:\\TBF\\Simulate\\unicbsim.txt"); } log.FatalFormat("{0} in replay mode, routeMask = {1}", Name, Utils.ToBin88(routeMask)); } else { serialPort = null; log.FatalFormat("{0} simulated: {1}", Name, this); } } /// /// Reecive and process data from the control board serial port /// public void RunDeviceBefore() { /// /// Read data from the control board /// bool generalDataReceived = false; if ((cbCfg.DebugLevel == DebugMode.Normal && serialPort != null && serialPort.IsOpen) || cbCfg.DebugLevel == DebugMode.Replay) { int age2BytesCount = age1BytesCount; age1BytesCount = age0BytesCount; if (cbCfg.DebugLevel == DebugMode.Normal) { age0BytesCount = serialPort.BytesToRead; /// Read new received bytes and append them to an intermediate buffer 'buffer' if (age0BytesCount > 0) { age0BytesCount = serialPort.Read(buffer, 0, Math.Min(age0BytesCount, BUFFER_SIZE)); } } else if (cbCfg.DebugLevel == DebugMode.Replay) { /// Simulation age0BytesCount = 0; string hexValues = textReader.ReadLine(); if (hexValues != null) { string[] hexValuesSplit = hexValues.Split(' '); foreach (var hex in hexValuesSplit) { buffer[age0BytesCount++] = Convert.ToByte(hex, 16); } } } else { age0BytesCount = 0; /// No data } StringBuilder dataLog = new StringBuilder(); StringBuilder caption = new StringBuilder(); string summary = string.Format("{0}-{1}+{2}={3} bytes:", rcvdBytesCount, age2BytesCount, age0BytesCount, rcvdBytesCount - age2BytesCount + age0BytesCount); dataLog.Append(summary); for (int i = 0; i < summary.Length; i++) caption.Append(" "); /// Remove data 2 seconds old for (int i = 0; i < age1BytesCount; i++) { rcvdData[i] = rcvdData[age2BytesCount + i]; dataLog.Append(string.Format(" {0:X2}", rcvdData[i])); } rcvdBytesCount -= age2BytesCount; /// Append new received data (0 seconds old) for (int i = 0; i < age0BytesCount && rcvdBytesCount < BUFFER_SIZE; i++, rcvdBytesCount++) { rcvdData[rcvdBytesCount] = buffer[i]; dataLog.Append(string.Format(" {0:X2}", buffer[i])); } /// Calculate cumulative sums (to verify checksums easily) int cumulativeSum = 0; for (int i = 0; i < rcvdBytesCount; i++) { cumulativeSum += rcvdData[i]; cumulativeSums[i] = cumulativeSum; } /// Try to detect various kinds of data in rcvdData buffer in a loop, start at offset 0 int offset = 0; int lastValidOffset = 0; /// Offset of the first byte after the last successfully parsed frame do { bool anyProcessed = false; string msg = string.Empty; if (ScopeAnalyzerData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset)) { /// Scope analyzer data received scopeAnalyzerData = ScopeAnalyzerData.GetData(rcvdData, offset); caption.Append(ScopeAnalyzerData.Caption()); offset += ScopeAnalyzerData.MessageLen; anyProcessed = true; } else if (GeneralData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset)) { /// Regular data received generalDataReceived = true; Data.UpdateData(rcvdData, offset); caption.Append(GeneralData.Caption()); offset += GeneralData.MessageLen; anyProcessed = true; } else if (SwitchCountersData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset)) { /// Switch counter data received switchCounterData = SwitchCountersData.GetData(rcvdData, offset); caption.Append(SwitchCountersData.Caption()); offset += SwitchCountersData.MessageLen; anyProcessed = true; } else if (DivTransitionData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset)) { /// Diverter transition data received divTransitionData = DivTransitionData.GetData(rcvdData, offset); caption.Append(DivTransitionData.Caption()); offset += DivTransitionData.MessageLen; anyProcessed = true; } if (anyProcessed) { lastValidOffset = offset; lastRcvdStMTime = StateMachine.Time; lastReceivedTime = DateTime.Now; } else { caption.Append(" "); offset++; } } while (offset < rcvdBytesCount); log.Debug(caption.ToString()); log.Debug(dataLog.ToString()); /// Remove processed data from the receive buffer for (int i = lastValidOffset; i < rcvdBytesCount; i++) { rcvdData[i - lastValidOffset] = rcvdData[i]; } rcvdBytesCount -= lastValidOffset; if (lastValidOffset <= age1BytesCount) { age1BytesCount -= lastValidOffset; } else { age1BytesCount = 0; age0BytesCount -= (lastValidOffset - age1BytesCount); } } /// /// Combine data from the current route, from digital outputs and from UI /// lock (this) { if (generalDataReceived) route = (UInt128)(Data.Vystupy ^ valvesToInvert); /// if (!IsUIBlocked && bitsChangedByUI != 0) { route = (route & ~bitsChangedByUI) | (displayedRoutePlusUI & bitsChangedByUI); bitsChangedByUI = 0; } } if (cbCfg.DebugLevel == DebugMode.Normal && (StateMachine.Time - lastRcvdStMTime) >= 5) { /// No connection to control board ... set indicator bit route |= ((UInt128)1) << cbCfg.BitNr; } /// /// Handle emergency stop signal /// bool emergencyStop = ((State & (UInt64)StatusP.EmgcyStopActive) != 0); /// if (emergencyStop) route |= (((UInt128)1) << (cbCfg.BitNr + 1)); else route &= ~(((UInt128)1) << (cbCfg.BitNr + 1)); /// if (emergencyStop && !isEmergencyStop) { TBF.UiBridge.Bridge.Ui2Bench(TBF.UiBridge.UI2BenchCmd.Stop); /// The same as pressing 'STOP' button in BenchControlPanel } /// isEmergencyStop = emergencyStop; log.DebugFormat("Route synced with RRoute and UI changes, new route = {0}", Utils.ToBin88(route)); } /// /// Send data to the control board serial port /// public void RunDeviceAfter() { DateTime now = DateTime.Now; if (now < lastSentTime + TimeSpan.FromSeconds(0.7) || (lastReceivedTime < lastSentTime && now < lastSentTime + TimeSpan.FromSeconds(1.7))) { log.Error("RunDeviceAfter() ... skipping too early write of a serial command or a data requect"); return; } /// /// Combine data from the current route and from UI /// lock (this) { if (!IsUIBlocked && bitsChangedByUI != 0) { route = (route & ~bitsChangedByUI) | (displayedRoutePlusUI & bitsChangedByUI); bitsChangedByUI = 0; } displayedRoutePlusUI = route; bitsChangedByUI = 0; } ProcessData.UpdateMeasuredValuesAndSetpoints(); Bridge.OnStateMachineTick(this, new StateMachineTickEventArgs(route, ProcessData.MsrmntAvailableFlags, ProcessData.MeasuredValues, ProcessData.AltStrings, ProcessData.Setpoints)); /// Make sure the serial port is open if (cbCfg.DebugLevel == DebugMode.Normal && (serialPort == null || !serialPort.IsOpen)) { /// Serial port is closed or does not exist => re-open the serial port try { if (serialPort == null) serialPort = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One); serialPort.DtrEnable = true; serialPort.WriteTimeout = 4000; serialPort.ParityReplace = 0xFF; serialPort.Open(); } catch (Exception) { /// Re-opening serial port failed return; } } if (!outputsInitialized || (outputLatch & routeMask) != (route & routeMask)) /// || (StateMachine.Time - lastStateMachineTime) >= 15) { var actionsToModify = actionQueue.Where(x => (x.ActionId == ActionID.ChangeRoute)); bool oneModified = false; foreach (var a in actionsToModify) { if (oneModified) { log.ErrorFormat("ChangeRoute !!! TWO actions of the same type in the queue"); break; /// Do not modify more actions, this should never happen } oneModified = true; a.Route = (ulong)route; log.InfoFormat("ChangeRoute(route={0:X}) ... an action in the queue modified", route); } if (!oneModified) { actionQueue.Enqueue(Action.ChangeRoute(false, (ulong)route)); log.InfoFormat("ChangeRoute(route={0:X}) ... a new action enqueued", (ulong)route); } foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } var combinedAction = Action.FetchNonConflictingActions(actionQueue); /// Default action is RequestDataOnly byte[] outData = OutputMessage.GetMessage(combinedAction, config, valvesToInvert); if (outData != null && outData.Length > 0) { if ((combinedAction.ActionId & ActionID.ChangeRoute) != 0) { outputsInitialized = true; outputLatch = combinedAction.Route; if (cbCfg.DebugLevel != DebugMode.Normal) { Data.Vystupy = combinedAction.Route ^ valvesToInvert; } } if ((combinedAction.ActionId & ActionID.StartTest) != 0) { Data.RestartReadingOfWMPulses = true; } if (cbCfg.DebugLevel == DebugMode.Normal) { serialPort.Write(outData, 0, outData.Length); lastSentTime = DateTime.Now; } log.Debug(" " + OutputMessage.Caption()); log.Debug(Telegram.LogTelegram(string.Format("Sent {0}: ", lastSentTime.ToString("HH:mm:ss")), outData)); } log.DebugFormat(" Queue content:"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void StopDevice() { if (serialPort != null) serialPort.Close(); serialPort = null; } public void StopDevice2() { } #endregion #region IControlBoard interface /// /// Interface functions /// public void ClearProcessValues() { } public double TestTimeWM(int wmNr1) { if (wmNr1 >= 1 && wmNr1 < Data.ImpulseTime.Length) { return Data.ImpulseTime[wmNr1]; } return 0; } public int RefPulsesWM(int wmNr1) { if (wmNr1 >= 1 && wmNr1 < Data.EtPulses.Length) { return Data.EtPulses[wmNr1]; } return 0; } public int PulsesWM(int wmNr1) { if (wmNr1 >= 1 && wmNr1 < Data.WMeterPuls.Length) { return Data.WMeterPuls[wmNr1]; } return 0; } /// /// Called from Start() and Run() functions of SetValvesOp, i.e. from State.RunOperations(). /// /// Bits of valves to be open are set /// Bits of valves to be closed are set /// A list of logical function valves (to be calculated and applied afterwards) /// Bits of changed valves are set in the return value public UInt128 SetValves(UInt128 valvesToOpen, UInt128 valvesToClose, IList logicalFnValves) { UInt128 oldRoute = route; route = (route | valvesToOpen) & (~valvesToClose); foreach (var logFnV in logicalFnValves) logFnV.UpdateRoute(ref route); log.DebugFormat("SetValves(x,x), new route = {0}", Utils.ToBin40((ulong)route >> 8)); UInt128 retVal = oldRoute ^ route; log.DebugFormat("{0}.SetValves({1}, {2}) returns {3}", Name, valvesToOpen.ToString("X"), valvesToClose.ToString("X"), retVal.ToString("X")); return retVal; } /// /// Operations /// public IOperation SetFlowOp(double flowLimLo, double flowLimHi, TBF.Boxes.DoubleBox measuredFlow, int timeout) { return Devices.RegValve.SetFlowOp(Devices.FlowMeter, flowLimLo, flowLimHi, measuredFlow, timeout); } public IOperation StopFlowRegulationOp() { StopAll(false); //scheduledActionQueue.Enqueue(Action.Stop()); return null; } /// /// Start a standing start-stop test /// /// Operation running a test public IOperation StandingStartStopTestOp(Test test, int pulsesCount, bool withDiverter) { return new StandingStartStopTestOp(this, devices, pulsesCount, withDiverter); } /// /// Start a flying start-stop test (opitonally with a diverter, optionally prolonged) /// /// Operation running a test public IOperation FlyingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount, bool withDiverter = false, bool isProlonged = false, int massPulsesCount = 0, bool readDivTransition = false, TBF.Rig.Sequences.Statistics plotterStart = null, TBF.Rig.Sequences.Statistics plotterEnd = null) { return new FlyingStartStopTestOp(this, devices, qFrom, qTo, pulsesCount, withDiverter, isProlonged, massPulsesCount, readDivTransition, plotterStart, plotterEnd); } /// /// Returns operation with events: Event.MeasurementCompleted or Event.None /// /// Operation public IOperation QueryMeasurementEndOp() { return null; } public IOperation StopPreviousOp() { return null; } #endregion #region IValveControl interface /// /// Open/close one valve. /// Events: ValvesSet, ValvesBusy, Error /// /// Valve to be opened /// Valve to be closed /// Newly created operation instance reference casted to IOperaton public IOperation SetValvesOp(IValve valveOpen, IValve valveClose) { return new BuiltIn.SetValvesOp(this, valveOpen, valveClose); } /// /// Open/close multiple valves. /// Events: ValvesSet, ValvesBusy, Error /// /// A list of valves to be opened /// A list of valves to be closed /// Newly created operation instance reference casted to IOperaton public IOperation SetValvesOp(IList valvesOpen, IList valvesClose) { return new BuiltIn.SetValvesOp(this, valvesOpen, valvesClose); } /// /// Open start/stop valve. /// Events: ValvesSet, ValvesBusy, Error /// /// Created operation public IOperation OpenStartValveOp() { return OpenStartValveOp(null); } public IOperation OpenStartValveOp(DateTimeBox openTimeStamp) { return new BuiltIn.SetValvesOp(this, true, devices, openTimeStamp); } /// /// Close start/stop valve. /// Events: ValvesSet, ValvesBusy, Error /// /// Created operation public IOperation CloseStartValveOp() { return CloseStartValveOp(null); } public IOperation CloseStartValveOp(DateTimeBox closeTimeStamp) { return new BuiltIn.SetValvesOp(this, false, devices, closeTimeStamp); } #endregion public void MeasureFlow(bool isFromUI, int flowMeterId) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.MeasureFlow(isFromUI, flowMeterId)); log.InfoFormat("Enqueue( MeasureFlow(fm={0}) )", flowMeterId); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } /// /// To be used with a regulation valve controlled by incremental pulses. /// Stability time is fixed: 200 ms /// public void SetFlow(bool isFromUI, int regVId, double freqLo, double freqHi) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.SetFlow(isFromUI, regVId, freqLo, freqHi, Convert.ToInt32(Math.Round(Devices.PidCoef)), 200)); log.InfoFormat("Enqueue( SetFlow(rv={0}, fLo={1}, fHi={2}, pid={3}) )", regVId, freqLo, freqHi, Convert.ToInt32(Math.Round(Devices.PidCoef))); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void StartTest(bool isFromUI, int flowMId, int divId, int divThreshold, bool isSyncMethod, bool isDivUsed, bool isStartStop, bool isProlonged, int totalPulsesCount, int massPulsesCount = -1) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.StartTest(isFromUI, flowMId, divId, divThreshold, isSyncMethod, isDivUsed, isStartStop, isProlonged, totalPulsesCount, massPulsesCount)); log.InfoFormat("Enqueue( StartTest(fm={0}, div={1}, Sync={2}, withDiv={3}, s/s={4}, prolonged={5} pulsesCount={6} massPulsesCount={7}) )", flowMId, divId, isSyncMethod, isDivUsed, isStartStop, isProlonged, totalPulsesCount, massPulsesCount); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void StopFlowControl(bool isFromUI, int regVId) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.RegVlvStop(isFromUI, regVId)); log.InfoFormat("Enqueue( RegVlvStop(rv={0}) )", regVId); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void StopAll(bool isFromUI) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.Stop(isFromUI)); log.InfoFormat("Enqueue( Stop() )"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } /// /// Perform incremental movement of a regulation valve /// /// Reg. valve ID /// Time in seconds, positive value opens the reg. valve public void RegVlvIncrMove(bool isFromUI, int regVId, double time) { if (IsUIBlocked && isFromUI) return; var newAction = Action.RegVlvIncrMove(isFromUI, regVId, time); var actionsToModify = actionQueue.Where(x => (x.ActionId == ActionID.RegVlvIncrMove && x.RegVId == newAction.RegVId)); bool oneModified = false; foreach (var a in actionsToModify) { if (oneModified) { log.ErrorFormat("RegVlvIncrMove !!! TWO actions of the same type in the queue"); break; /// Do not modify more actions, this should never happen } a.RVMovePar1 += newAction.RVMovePar1; a.RVMovePar2 += newAction.RVMovePar2; oneModified = true; log.InfoFormat("RegVlvIncrMove(UI={0}, RV={1}, time={2}) ... an action in the queue modified", isFromUI, regVId, time); } if (!oneModified) { actionQueue.Enqueue(newAction); log.InfoFormat("RegVlvIncrMove(UI={0}, RV={1}, time={2}) ... a new action enqueued", isFromUI, regVId, time); } foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } /// /// Move a regulation valve to specfied position /// /// Reg. valve ID /// Reg. valve position (0 .. 1.0) public void RegVlvMoveToPos(bool isFromUI, int regVId, double positionLo, double positionHi = -1) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.RegVlvMoveToPos(isFromUI, regVId, positionLo, positionHi)); log.InfoFormat("Enqueue( RegVlvMoveToPos(rv={0}, positionLo={1}, positionHi={2}) )", regVId, positionLo, positionHi); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void SwitchDiverter(bool isFromUI, int divNr1, bool toTank) { if (IsUIBlocked && isFromUI) return; if (toTank) { actionQueue.Enqueue(Action.StartTest(isFromUI, 0, divNr1, 127, true, true, false, false, int.MaxValue, 0)); } else { actionQueue.Enqueue(Action.Stop(isFromUI)); } log.InfoFormat("Enqueue( SwitchDiverter(div#={0}, toTank={1}) )", divNr1, toTank); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void DelayStartOrStop(bool isFromUI) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.DelayStartOrStop(isFromUI)); log.InfoFormat("Enqueue( DelayStartOrStop() )"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void GetDiverterTransitionData(bool isFromUI, TBF.Rig.Uni.Diverter.Diverter diverter, bool toTank, Statistics plotter = null) { if (IsUIBlocked && isFromUI) return; DivTransitionData.Diverter = diverter; DivTransitionData.ToTank = toTank; DivTransitionData.Plotter = plotter; DivTransitionData.DiverterAssociationValidUntil = StateMachine.Time + 5; actionQueue.Enqueue(Action.GetDiverterTransitionData(isFromUI)); log.InfoFormat("Enqueue( GetDiverterTransitionData(div#={0}, toTank={1}) )", diverter.DiverterNr, toTank); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void GetScopeAnalyzerData(bool isFromUI) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.GetScopeAnalyzerData(isFromUI)); log.InfoFormat("Enqueue( GetScopeAnalyzerData() )"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void ResetScopeAnalyzer(bool isFromUI) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.ResetScopeAnalyzer(isFromUI)); log.InfoFormat("Enqueue( ResetScopeAnalyzer() )"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void GetSwitchCounterData(bool isFromUI) { if (IsUIBlocked && isFromUI) return; actionQueue.Enqueue(Action.GetSwitchCounterData(isFromUI)); log.InfoFormat("Enqueue( GetSwitchCounterData() )"); foreach (var a in actionQueue) log.DebugFormat(" {0}", a); } public void SetFiltersPidShortPulses(int[] filters, float pidCoef, int shortPulses) { /// TODO } } }