502 lines
18 KiB
C#
502 lines
18 KiB
C#
///
|
|
/// 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() { }
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
public PapouchCB(Generic.IComponentCfg cfg, IList<Generic.IComponent> 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() { }
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Set outputs of QuidoRS board
|
|
/// </summary>
|
|
/// <param name="outputs">output value</param>
|
|
public void SetOutputs(UInt32 outputs)
|
|
{
|
|
lock (this)
|
|
{
|
|
currentOutputs = outputs;
|
|
}
|
|
SendOutputs(currentOutputs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set bit(s) specified by a bitMask. Leave other bits unaffeced.
|
|
/// </summary>
|
|
/// <param name="bitMask">Specifies bit(s) to set</param>
|
|
/// <param name="outputs">output value</param>
|
|
public void SetBit(UInt32 bitMask)
|
|
{
|
|
lock (this)
|
|
{
|
|
currentOutputs = currentOutputs | bitMask;
|
|
}
|
|
SendOutputs(currentOutputs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset bit(s) specified by a bitMask. Leave other bits unaffeced.
|
|
/// </summary>
|
|
/// <param name="bitMask">Specifies bit(s) to reset</param>
|
|
/// <param name="outputs">output value</param>
|
|
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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Route: 128-bit word representing the current state of all valves and pumps
|
|
/// </summary>
|
|
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() { }
|
|
|
|
/// <summary>
|
|
/// Set valves to new positions.
|
|
/// </summary>
|
|
/// <param name="valvesToOpen">'UInt128' with bits of valves to open set to '1'</param>
|
|
/// <param name="valvesToClose">'UInt128' with bits of valves to close set to '1'</param>
|
|
/// <returns>'UInt128' with bits of valves that have changed set to '1'</returns>
|
|
public UInt128 SetValves(UInt128 valvesToOpen, UInt128 valvesToClose, IList<BuiltIn.ValveEx.Valve> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open one valve and/or close one valve.
|
|
/// Events: ValvesSet
|
|
/// </summary>
|
|
/// <param name="valveOpen">Valve to be opened</param>
|
|
/// <param name="valveClose">Valve to be closed</param>
|
|
/// <returns>Created operation</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open and/or close multiple valves.
|
|
/// Events: ValvesSet
|
|
/// </summary>
|
|
/// <param name="valvesOpen">A list of valves to be opened</param>
|
|
/// <param name="valvesClose">A list of valves to be closed</param>
|
|
/// <returns>Created operation</returns>
|
|
public IOperation SetValvesOp(IList<IValve> valvesOpen, IList<IValve> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open start/stop valve.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <returns>Created operation</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close start/stop valve.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <returns>Created operation</returns>
|
|
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; } }
|
|
|
|
|
|
/// <summary>
|
|
/// Returns operation to display a prompt message to set the flow.
|
|
/// </summary>
|
|
/// <param name="reqrdFlowLo">Lower limit</param>
|
|
/// <param name="reqrdFlowHi">Upper limit</param>
|
|
/// <param name="measuredFlow">Reference to a box for the measured flow or null</param>
|
|
/// <param name="timeout">Flow set timeout</param>
|
|
/// <returns>Operation</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a standing start-stop test
|
|
/// </summary>
|
|
/// <returns>Operation</returns>
|
|
public IOperation StandingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount)
|
|
{
|
|
currentTest = test;
|
|
return new Operations.ReturnGivenEventOp(Event.TestInProgress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a flying start-stop test ... not supported by Papouch control board
|
|
/// </summary>
|
|
/// <returns>null</returns>
|
|
public IOperation FlyingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a flying start-stop test with diverter ... not supported by Papouch control board
|
|
/// </summary>
|
|
/// <returns>null</returns>
|
|
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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Start selected operation
|
|
/// </summary>
|
|
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)
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Run selected operation
|
|
/// </summary>
|
|
/// <returns>Event.None or Event.FlowReached or Event.MeasurementCompleted </returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop this operation
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
if (runningOp == SelectedOp.SetFlow)
|
|
{
|
|
if (messageBoxForm != null)
|
|
{
|
|
messageBoxForm.OnCloseThisForm(this, null);
|
|
messageBoxForm = null;
|
|
}
|
|
}
|
|
|
|
runningOp = SelectedOp.None;
|
|
opCompleted = false;
|
|
}
|
|
}
|
|
}
|