/// /// Copyright (c) 2020 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using log4net; using Config.Entities; using Dirichlet.Numerics; using TBF.Rig.Generic; using TBF.Rig.GenericDevices; using TBF.Rig.Modbus; using TBF.Rig.Sequences; namespace TBF.Rig.ControlBoard.Papouch { public class PapouchCB : ComponentBase, IControlBoard, IOperation { private static readonly ILog log = LogManager.GetLogger(typeof(PapouchCB)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } readonly PapouchCBCfg cbCfg; readonly GenericDevices.IModbus modbus; UInt32 currentOutputs; bool outputsInitialized; DateTime lastOutputsChange; int lastStateMachineTime = 0; /// UInt128 benchModelRoute; UInt128 valvesToInvert; /// Route = valesToInvert ^ RequiredRouteWithoutInvertedVlaves UInt128 routeMask; /// This mask masks out virtual valves, routeMask is set in the constructor /// OutputPath devices; enum SelectedOp { None, SetFlow, QueryMeasurementEnd, } SelectedOp scheduledOp; SelectedOp runningOp; /// bool opCompleted; /// true = Operation was completed and waits for Stop(); public PapouchCB() { } /// /// Constructor /// public PapouchCB(Generic.IComponentCfg cfg, IList components) : base(cfg) { cbCfg = cfg as PapouchCBCfg; modbus = (GenericDevices.IModbus)TbfComponents.FindComponent(cfg.ParentName, components); if (modbus == null) throw new Exception("Cannot find " + Name + " parent"); log.Warn(this.ToString()); } public void SpecifyInvertedValves(UInt128 valvesToInvert) { this.valvesToInvert = valvesToInvert; this.benchModelRoute = valvesToInvert; } /// /// IDevice interface /// public override void Initialize() { outputsInitialized = false; runningOp = SelectedOp.None; routeMask = 0; for (int i = 0; i < 128; i++) { if (i < cbCfg.VirtualValvesRangeLo || i > cbCfg.VirtualValvesRangeHi) { routeMask = routeMask | ((UInt128)1 << i); } } TBF.UiBridge.Bridge.RouteChangeHandler += delegate(object sndr, TBF.UiBridge.RouteChangeArgs args) { UInt128 mask = ((UInt128)1) << args.BitNr; if ((benchModelRoute & mask) == 0) { benchModelRoute |= mask; } else { benchModelRoute &= (~mask); } }; } public void RunDeviceBefore() { if (cbCfg.DebugLevel == Config.Entities.DebugMode.Simulate) return; if (modbus.ReceivedTelegrams[cbCfg.ModbusAddress].Count > 0) { byte[] telegram = modbus.ReceivedTelegrams[cbCfg.ModbusAddress].Dequeue(); if ((telegram.Length >= 8) && (telegram[1] == 0x11) && (telegram[2] == telegram.Length - 5)) { int msgLen = telegram.Length - 7; byte modbusAddress = telegram[3]; string msg = Encoding.ASCII.GetString(telegram, 5, msgLen); string s = string.Format("QuidoRS detected: Address={0}, Message={1}", modbusAddress, msg); Debug.WriteLine(s); log.Warn(s); } if ((telegram.Length == 6) && (telegram[1] == 0x02) && (telegram[2] == telegram.Length - 5)) { int inputData = telegram[3]; string s = string.Format("QuidoRS inputs: {0}", inputData); Debug.WriteLine(s); log.Warn(s); } } } public void RunDeviceAfter() { if (currentOutputs != (UInt32)(benchModelRoute & 0xFFFFFFFF) || !outputsInitialized || (StateMachine.Time - lastStateMachineTime) >= 3) { SetOutputs((UInt32)(benchModelRoute & 0xFFFFFFFF)); } else { RequestInputs(); } ProcessData.UpdateMeasuredValuesAndSetpoints(); TBF.UiBridge.Bridge.OnStateMachineTick(this, new TBF.UiBridge.StateMachineTickEventArgs(benchModelRoute, ProcessData.MsrmntAvailableFlags, ProcessData.MeasuredValues, ProcessData.AltStrings, ProcessData.Setpoints)); } public void StopDevice() { SendOutputs(0); } public void StopDevice2() { } /// /// Set outputs of QuidoRS board /// /// output value public void SetOutputs(UInt32 outputs) { lock (this) { currentOutputs = outputs; } SendOutputs(currentOutputs); } /// /// Set bit(s) specified by a bitMask. Leave other bits unaffeced. /// /// Specifies bit(s) to set /// output value public void SetBit(UInt32 bitMask) { lock (this) { currentOutputs = currentOutputs | bitMask; } SendOutputs(currentOutputs); } /// /// Reset bit(s) specified by a bitMask. Leave other bits unaffeced. /// /// Specifies bit(s) to reset /// output value public void ResetBit(UInt32 bitMask) { lock (this) { currentOutputs = currentOutputs & (~bitMask); } SendOutputs(currentOutputs); } private void RequestInputs() { byte addr = cbCfg.ModbusAddress; byte cmd = (byte)Function.ReadSingleInput; modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 2, 0, 0 }); } private void SendOutputs(UInt32 outputs) { byte addr = cbCfg.ModbusAddress; byte cmd = (byte)Function.ForceMultipleCoils; byte dataLo = (byte)(outputs & 0xFF); byte data2 = (byte)(outputs >> 8); byte data3 = (byte)(outputs >> 16); byte dataHi = (byte)(outputs >> 24); if (cbCfg.Variant == QuidoVariant.QuidoRS_4_4) { modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 4, 1, dataLo, 0, 0 }); } else if (cbCfg.Variant == QuidoVariant.QuidoRS_8_8) { modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 8, 1, dataLo, 0, 0 }); } else if (cbCfg.Variant == QuidoVariant.QuidoRS_2_16) { modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 16, 2, dataLo, data2, 0, 0 }); } else if (cbCfg.Variant == QuidoVariant.QuidoRS_2_32) { modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 32, 4, dataLo, data2, data3, dataHi, 0, 0 }); } outputsInitialized = true; lastOutputsChange = DateTime.Now; lastStateMachineTime = StateMachine.Time; } /// /// IControlBoard interface /// public OutputPath Devices { set { devices = value; } get { return devices; } } /// /// Route: 128-bit word representing the current state of all valves and pumps /// public UInt128 Route { get { return benchModelRoute; } } public UInt64 DigitalInputs { get { return 0; } } public int FlowmeterId { get { return 0; } } public double RefFrequency { get { return 0; } } /// /// Last test result /// public int RefPulses { get { return 0; } } public double TestTime { get { return 1.0; } } public int RefPulsesWM(int wmNr1) { return (wmNr1 > 0 && wmNr1 <= Config.Data.WMsCount) ? 0 : 0; } public int PulsesWM(int wmNr1) { return (wmNr1 > 0 && wmNr1 <= Config.Data.WMsCount) ? 0 : 0; } public double TestTimeWM(int wmNr1) { return (wmNr1 > 0 && wmNr1 <= Config.Data.WMsCount) ? 1.0 : 1.0; } public void ClearProcessValues() { } /// /// Set valves to new positions. /// /// 'UInt128' with bits of valves to open set to '1' /// 'UInt128' with bits of valves to close set to '1' /// 'UInt128' with bits of valves that have changed set to '1' public UInt128 SetValves(UInt128 valvesToOpen, UInt128 valvesToClose, IList extendedValves) { UInt128 oldRoute = benchModelRoute; benchModelRoute = (((benchModelRoute ^ valvesToInvert) | valvesToOpen) & (~valvesToClose)) ^ valvesToInvert; foreach (var extV in extendedValves) extV.UpdateRoute(ref benchModelRoute); log.DebugFormat("SetValves(x,x), new route = {0}", Utils.ToBin40((ulong)benchModelRoute)); UInt128 retVal = oldRoute ^ benchModelRoute; log.DebugFormat("{0}.SetValves({1}, {2}) returns {3}", Name, valvesToOpen.ToString("X"), valvesToClose.ToString("X"), retVal.ToString("X")); return retVal; } /// /// Open one valve and/or close one valve. /// Events: ValvesSet /// /// Valve to be opened /// Valve to be closed /// Created operation public IOperation SetValvesOp(IValve valveOpen, IValve valveClose) { IOperation op = new BuiltIn.SetValvesOp(this, valveOpen, valveClose); log.DebugFormat("{0}.SetValvesOp(vO, vC) returns {1}", Name, (op != null) ? op.ToString() : "null"); return op; } /// /// Open and/or close multiple valves. /// Events: ValvesSet /// /// A list of valves to be opened /// A list of valves to be closed /// Created operation public IOperation SetValvesOp(IList valvesOpen, IList valvesClose) { IOperation op = new BuiltIn.SetValvesOp(this, valvesOpen, valvesClose); log.DebugFormat("{0}.SetValvesOp(vlvsO, vlvsC) returns {1}", Name, (op != null) ? op.ToString() : "null"); return op; } /// /// Open start/stop valve. /// Events: ValvesSet, ValvesBusy, Error /// /// Created operation public IOperation OpenStartValveOp() { IOperation op = new BuiltIn.SetValvesOp(this, true, devices); log.DebugFormat("{0}.OpenStartValveOp({1}) returns {2}", Name, devices.StartValve1.Name, (op != null) ? op.ToString() : "null"); return op; } /// /// Close start/stop valve. /// Events: ValvesSet, ValvesBusy, Error /// /// Created operation public IOperation CloseStartValveOp() { IOperation op = new BuiltIn.SetValvesOp(this, false, devices); log.DebugFormat("{0}.CloseStartValveOp({1}) returns {2}", Name, devices.StartValve1.Name, (op != null) ? op.ToString() : "null"); return op; } public float ValveOpenCloseTime { get { return 0.1f; } } /// /// Returns operation to display a prompt message to set the flow. /// /// Lower limit /// Upper limit /// Reference to a box for the measured flow or null /// Flow set timeout /// Operation public IOperation SetFlowOp(double reqrdFlowLo, double reqrdFlowHi, TBF.Boxes.DoubleBox measuredFlow, int timeout) { string loLimStr = reqrdFlowLo.ToString(Config.Utils.SignificantDigitsToFmt(reqrdFlowLo, 2)); string hiLimStr = reqrdFlowHi.ToString(Config.Utils.SignificantDigitsToFmt(reqrdFlowHi, 2)); this.promptMsg = string.Format("Set flow measured by {0} withing {1} and {2} m3/h by regulation valve {3}", devices.FlowMeter.Name, loLimStr, hiLimStr, devices.RegValve.Name); this.backColor = System.Drawing.Color.LightSalmon; scheduledOp = SelectedOp.SetFlow; return this; } public IOperation StopFlowRegulationOp() { return new Operations.ReturnGivenEventOp(Event.FlowRegulationStopped); } /// /// Start a standing start-stop test /// /// Operation public IOperation StandingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount) { currentTest = test; return new Operations.ReturnGivenEventOp(Event.TestInProgress); } /// /// Start a flying start-stop test ... not supported by Papouch control board /// /// null public IOperation FlyingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount) { return null; } /// /// Start a flying start-stop test with diverter ... not supported by Papouch control board /// /// null public IOperation FlyingStartStopTestWithDivOp(Test test, double qFrom, double qTo, int pulsesCount, int massPulsesCount = 0, bool readDivTransition = true) { return null; } public IOperation QueryMeasurementEndOp() { scheduledOp = SelectedOp.QueryMeasurementEnd; return this; } public IOperation ReadDiverterTransitionOp(bool isStart, Sequences.Statistics plotter, int batchNr, string testName, int repetition) { return null; } public IOperation StopPreviousOp() { return new Operations.ReturnGivenEventOp(Event.PreviousStopped); } Test currentTest; string promptMsg; System.Drawing.Color backColor; TBF.Rig.Operations.MessageBoxForm messageBoxForm; /// delegate void MessageBoxFormDlgt(PapouchCB myRef); /// void OpenFormDlg(PapouchCB myRef) { myRef.messageBoxForm = new TBF.Rig.Operations.MessageBoxForm(promptMsg, backColor); messageBoxForm.Show(); } /// /// Start selected operation /// public void Start() { if (scheduledOp == SelectedOp.None || runningOp != SelectedOp.None) { throw new Exception("Sequence error"); } runningOp = scheduledOp; scheduledOp = SelectedOp.None; if (runningOp == SelectedOp.SetFlow) { Program.MainWnd.Invoke(new MessageBoxFormDlgt(OpenFormDlg), this); } else if (runningOp == SelectedOp.QueryMeasurementEnd) { } } /// /// Run selected operation /// /// Event.None or Event.FlowReached or Event.MeasurementCompleted public Event Run() { if (runningOp == SelectedOp.SetFlow) { if (opCompleted || messageBoxForm.Completed) { opCompleted = true; messageBoxForm = null; return Event.FlowReached; } } else if (runningOp == SelectedOp.QueryMeasurementEnd) { if (opCompleted) return Event.TestCompleted; double currentMass = TBF.Rig.Sequences.ProcessData.Mass.Val; if (currentMass - TBF.Rig.Sequences.ProcessData.StartMass.Val >= currentTest.Volume) { opCompleted = true; return Event.TestCompleted; } } return Event.None; } /// /// Stop this operation /// public void Stop() { if (runningOp == SelectedOp.SetFlow) { if (messageBoxForm != null) { messageBoxForm.OnCloseThisForm(this, null); messageBoxForm = null; } } runningOp = SelectedOp.None; opCompleted = false; } } }