tbf/TBF/Rig/ControlBoard/Papouch/PapouchCB.cs
2021-04-03 00:54:18 +02:00

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;
}
}
}