/// /// Copyright (c) 2020 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using log4net; using Common; using Config.Entities; using Dirichlet.Numerics; using TBF.Boxes; 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; TBF.Rig.Modbus.Common.Modbus modbus; const int quidosCount = 4; QuidoVariant[] quidoVariants; int[] modbusAddresses; int[] offsetsOutputs; int[] offsetsInputs; UInt128[] routeOutputMasks; UInt128[] routeInputMasks; int[] lastStateMachineTimes; bool outputsInitialized; /// UInt128 currentOutputs; UInt128 benchModelRoute; UInt128 valvesToInvert; /// Route = valesToInvert ^ RequiredRouteWithoutInvertedVlaves 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; } public void SpecifyInvertedValves(UInt128 valvesToInvert) { this.valvesToInvert = valvesToInvert; this.benchModelRoute = valvesToInvert; } public override void Initialize() { modbus = TbfComponents.FindComponent(cbCfg.ParentName) as TBF.Rig.Modbus.Common.Modbus; if (modbus == null) throw new Exception("Cannot find " + Name + " parent (Modbus)"); quidoVariants = new QuidoVariant[quidosCount] { cbCfg.Variant1, cbCfg.Variant2, cbCfg.Variant3, cbCfg.Variant4 }; modbusAddresses = new int[quidosCount] { cbCfg.ModbusAddress1, cbCfg.ModbusAddress2, cbCfg.ModbusAddress3, cbCfg.ModbusAddress4 }; offsetsOutputs = new int[quidosCount] { cbCfg.OffsetOutputs1, cbCfg.OffsetOutputs2, cbCfg.OffsetOutputs3, cbCfg.OffsetOutputs4 }; offsetsInputs = new int[quidosCount] { cbCfg.OffsetInputs1, cbCfg.OffsetInputs2, cbCfg.OffsetInputs3, cbCfg.OffsetInputs4 }; routeOutputMasks = new UInt128[quidosCount] { 0, 0, 0, 0 }; routeInputMasks = new UInt128[quidosCount] { 0, 0, 0, 0 }; lastStateMachineTimes = new int[quidosCount] { 0, 0, 0, 0 }; for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++) { if (quidoVariants[quidoIx] != QuidoVariant.None) { modbus.ComponentNames[modbusAddresses[quidoIx]] = string.Format("{0}{1}", Name, quidoIx + 1); } } for (int i = 0; i < 128; i++) { bool conflictAvoidanceFlag = false; for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++) { if (i >= offsetsOutputs[quidoIx] && i < offsetsOutputs[quidoIx] + OutputsCount(quidoIx)) { if (conflictAvoidanceFlag) throw new Exception("Overlapping input/output bits"); conflictAvoidanceFlag = true; routeOutputMasks[quidoIx] = routeOutputMasks[quidoIx] | ((UInt128)1 << i); } if (i >= offsetsInputs[quidoIx] && i < offsetsInputs[quidoIx] + InputsCount(quidoIx)) { if (conflictAvoidanceFlag) throw new Exception("Overlapping input/output bits"); conflictAvoidanceFlag = true; routeInputMasks[quidoIx] = routeInputMasks[quidoIx] | ((UInt128)1 << i); } } } outputsInitialized = false; runningOp = SelectedOp.None; 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); } }; if (cbCfg.DebugLevel == DebugMode.Normal) log.FatalFormat("{0} initialized: {1}", cbCfg.Name, this); else log.FatalFormat("{0} simulated: {1}", cbCfg.Name, this); } /// /// IDevice interface /// public void RunDeviceBefore() { for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++ ) { if (modbus.ReceivedTelegrams[modbusAddresses[quidoIx]].Count > 0) { byte[] telegram = modbus.ReceivedTelegrams[modbusAddresses[quidoIx]].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("Quido{0} detected: Address={1}, Message={2}", quidoIx + 1, modbusAddress, msg); Debug.WriteLine(s); log.Warn(s); } if ((telegram.Length == 6) && (telegram[1] == 0x02) && (telegram[2] == telegram.Length - 5)) { UInt128 inputData = telegram[3]; benchModelRoute = (benchModelRoute & ~routeInputMasks[quidoIx]) | ((inputData << offsetsInputs[quidoIx]) & routeInputMasks[quidoIx]); string s = string.Format("Quido{0} inputs: {1}", quidoIx + 1, telegram[3].ToString("X2")); Debug.WriteLine(s); log.Warn(s); } } } } public void RunDeviceAfter() { if (!outputsInitialized) { SendAllOutputs(benchModelRoute); outputsInitialized = true; } else { for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++) { if (quidoVariants[quidoIx] != QuidoVariant.None) { if ((currentOutputs & routeOutputMasks[quidoIx]) != (benchModelRoute & routeOutputMasks[quidoIx]) || (StateMachine.Time - lastStateMachineTimes[quidoIx]) >= 5) { SendOutputs(quidoIx, benchModelRoute, offsetsOutputs[quidoIx]); } else { RequestInputs(quidoIx); } } } } ProcessData.UpdateMeasuredValuesAndSetpoints(); TBF.UiBridge.Bridge.OnStateMachineTick(this, new TBF.UiBridge.StateMachineTickEventArgs(benchModelRoute, ProcessData.MsrmntAvailableFlags, ProcessData.MeasuredValues, ProcessData.AltStrings, ProcessData.Setpoints)); } public void StopDevice() { SendAllOutputs(0); } public void StopDevice2() { } private void RequestAllInputs() { for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++) { if (quidoVariants[quidoIx] != QuidoVariant.None) { RequestInputs(quidoIx); } } } private void RequestInputs(int quidoIx) { byte addr = (byte)modbusAddresses[quidoIx]; byte cmd = (byte)Function.ReadSingleInput; modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 2, 0, 0 }, string.Format("{0}{1}", Name, quidoIx + 1)); } /// /// Set outputs of QuidoRS board /// /// output value private void SendAllOutputs(UInt128 route) { for (int quidoIx = 0; quidoIx < quidosCount; quidoIx++) { if (quidoVariants[quidoIx] != QuidoVariant.None) { SendOutputs(quidoIx, route, offsetsOutputs[quidoIx]); } } } private void SendOutputs(int quidoIx, UInt128 outputs, int offset) { if (cbCfg.DebugLevel == Common.DebugMode.Normal) { byte addr = (byte)modbusAddresses[quidoIx]; byte cmd = (byte)Function.ForceMultipleCoils; byte loByte = (byte)(outputs >> offset); switch (quidoVariants[quidoIx]) { case QuidoVariant.QuidoRS_4_4: modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 4, 1, loByte, 0, 0 }, string.Format("{0}{1}", Name, quidoIx + 1)); break; case QuidoVariant.QuidoRS_8_8: modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 8, 1, loByte, 0, 0 }, string.Format("{0}{1}", Name, quidoIx + 1)); break; case QuidoVariant.QuidoRS_2_16: modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 16, 2, loByte, (byte)(outputs >> (offset + 8)), 0, 0 }, string.Format("{0}{1}", Name, quidoIx + 1)); break; case QuidoVariant.QuidoRS_2_32: modbus.SendMessage(new byte[] { addr, cmd, 0, 0, 0, 32, 4, loByte, (byte)(outputs >> (offset + 8)), (byte)(outputs >> (offset + 16)), (byte)(outputs >> (offset + 24)), 0, 0 }, string.Format("{0}{1}", Name, quidoIx + 1)); break; } } currentOutputs = (currentOutputs & ~routeOutputMasks[quidoIx]) | (outputs & routeOutputMasks[quidoIx]); lastStateMachineTimes[quidoIx] = StateMachine.Time; } int InputsCount(int quidoIx) { switch (quidoVariants[quidoIx]) { case QuidoVariant.QuidoRS_4_4: return 4; case QuidoVariant.QuidoRS_8_8: return 8; case QuidoVariant.QuidoRS_2_16: return 2; case QuidoVariant.QuidoRS_2_32: return 2; default: return 0; } } int OutputsCount(int quidoIx) { switch (quidoVariants[quidoIx]) { case QuidoVariant.QuidoRS_4_4: return 4; case QuidoVariant.QuidoRS_8_8: return 8; case QuidoVariant.QuidoRS_2_16: return 16; case QuidoVariant.QuidoRS_2_32: return 32; default: return 0; } } /// /// 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 <= ProcessData.WMsCount) ? 0 : 0; } public int PulsesWM(int wmNr1) { return (wmNr1 > 0 && wmNr1 <= ProcessData.WMsCount) ? 0 : 0; } public double TestTimeWM(int wmNr1) { return (wmNr1 > 0 && wmNr1 <= ProcessData.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() { return OpenStartValveOp(null); } public IOperation OpenStartValveOp(DateTimeBox openTimeStamp) { IOperation op = new BuiltIn.SetValvesOp(this, true, devices, openTimeStamp); 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() { return CloseStartValveOp(null); } public IOperation CloseStartValveOp(DateTimeBox closeTimeStamp) { IOperation op = new BuiltIn.SetValvesOp(this, false, devices, closeTimeStamp); 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(Common.Utils.SignificantDigitsToFmt(reqrdFlowLo, 2)); string hiLimStr = reqrdFlowHi.ToString(Common.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, int pulsesCount, bool withDiverter) { 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 void StopFlowControl(bool isFromUI, int rvId) { } public void StopAll(bool isFromUI) { } 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 = (Devices.Scale is IScale) ? (Devices.Scale as IScale).Mass : 0; 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; } } }