Remove `SharedComponents` references and legacy `LiveLogCache` logic: - Eliminate unused `SharedComponents` references across the solution to streamline dependencies. - Comment out `LiveLogCache` interactions in multiple modules, transitioning to alternative or undefined logging mechanisms. - Add optional `regReadersOptional` parameters to `ShowCycleBeginFormOp` methods for improved flexibility. - Introduce `ITestMethodSmart` interface to support smart reader functionality. - Add new `LogCacheAppender` configuration to `log4netConfig.xml` for diagnostic use. - Update project files to remove outdated references and include newly introduced files.
985 lines
41 KiB
C#
985 lines
41 KiB
C#
///
|
|
/// 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;
|
|
using AppDiagnostic;
|
|
|
|
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); } }
|
|
|
|
public DivResolution DivResolution { get; set; }
|
|
public int DivResolutionMs
|
|
{
|
|
get
|
|
{
|
|
switch (DivResolution)
|
|
{
|
|
case DivResolution._1ms: return 1;
|
|
default:
|
|
case DivResolution._2ms: return 2;
|
|
case DivResolution._4ms: return 4;
|
|
case DivResolution._8ms: return 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
short config { get { return (short)((cbCfg.DrainValvesCount << 8) + ((int)DivResolution << 6) + ((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
|
|
|
|
public Activity Activity { get { return activity; } } /// Control board activity
|
|
Activity activity;
|
|
public void SetActivity(Activity a) { activity = a; }
|
|
|
|
/// Quido RS
|
|
TBF.Rig.Modbus.QuidoRS.QuidoRS quidoRS;
|
|
|
|
/// 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
|
|
UInt128 invInputsMask; /// Typically 0xff000000 0000ffff ffffffff ffffffff, inputs are mapped to route bits 80..119
|
|
UInt128 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<Action> 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
|
|
|
|
/// <summary>
|
|
/// Select devices used for control and measurement
|
|
/// </summary>
|
|
/// <param name="outPath"></param>
|
|
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; } }
|
|
|
|
|
|
/// <summary>
|
|
/// Constructor for user interfaces (change settings only).
|
|
/// Initialize(), RunDeviceXY() and StopDevice() methods of the created object will not be called.
|
|
/// </summary>
|
|
public UniCB() { }
|
|
|
|
/// <summary>
|
|
/// Constructor invoked when starting the test bench and creating components.
|
|
/// </summary>
|
|
public UniCB(Generic.IComponentCfg cfg)
|
|
: base(cfg)
|
|
{
|
|
cbCfg = cfg as UniCBCfg;
|
|
Data = new GeneralData();
|
|
log.Warn(this.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specify inverted valves
|
|
/// </summary>
|
|
/// <param name="valvesToInvert"></param>
|
|
public void SpecifyInvertedValves(UInt128 valvesToInvert)
|
|
{
|
|
this.valvesToInvert = valvesToInvert;
|
|
}
|
|
|
|
#region IDevice interface
|
|
|
|
/// <summary>
|
|
/// Initialize control board device hardware
|
|
/// </summary>
|
|
public override void Initialize()
|
|
{
|
|
quidoRS = TbfComponents.FindComponent(cbCfg.QuidoRS) as TBF.Rig.Modbus.QuidoRS.QuidoRS;
|
|
|
|
devices = new OutputPath(string.Empty, 0);
|
|
actionQueue = new Queue<Action>();
|
|
actionQueue.Enqueue(Action.Stop(false)); /// Stop all
|
|
outputsInitialized = false;
|
|
|
|
divTransitionData = null;
|
|
scopeAnalyzerData = null;
|
|
switchCounterData = null;
|
|
|
|
routeMask = 0x000000ff1effff00;
|
|
if (cbCfg.RelaysFInstalled) routeMask |= 0x0000ff0000000000;
|
|
if (cbCfg.RelaysGInstalled) routeMask |= 0x00ff000000000000;
|
|
if (cbCfg.RelaysHInstalled) routeMask |= 0xff00000000000000;
|
|
if (cbCfg.RelaysIInstalled) routeMask |= 0x00000000000000ff;
|
|
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 (quidoRS != null) routeMask |= (((UInt128)0xffff) << 64);
|
|
|
|
invInputsMask = (UInt128)0xffffffffffffffffL;
|
|
invInputsMask |= (((UInt128)0xffff) << 64);
|
|
invInputsMask |= (((UInt128)0xff) << 120);
|
|
|
|
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();
|
|
|
|
if (!string.IsNullOrEmpty(cbCfg.QuidoRS) && quidoRS == null)
|
|
{
|
|
throw new Exception(string.Format("Cannot find Quido RS component named {0}", cbCfg.QuidoRS));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reecive and process data from the control board serial port
|
|
/// </summary>
|
|
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, DivResolutionMs);
|
|
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, DivResolutionMs);
|
|
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 = ((route & (((UInt128)0xffffffffffffffff) << 64)) | Data.Vystupy) ^ valvesToInvert;
|
|
///
|
|
if (!IsUIBlocked && bitsChangedByUI != 0)
|
|
{
|
|
route = (route & ~bitsChangedByUI) | (displayedRoutePlusUI & bitsChangedByUI);
|
|
bitsChangedByUI = 0;
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Map Inputs bits 0..39 to Route bits 80..119
|
|
///
|
|
route = (route & invInputsMask) | ((UInt128)Data.Vstupy) << 80;
|
|
|
|
///
|
|
/// Map UniCB indication to Route bit BitNr (default 120)
|
|
///
|
|
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 = ((Data.Vstupy & 0x10000) == 0);
|
|
///
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to the control board serial port
|
|
/// </summary>
|
|
public void RunDeviceAfter()
|
|
{
|
|
///
|
|
/// 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, ProcessData.CustomBitmaps));
|
|
|
|
/// 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 (quidoRS != null) quidoRS.SetOutputs((route >> 64) & 0xFFFF);
|
|
|
|
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 = 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
|
|
|
|
combinedAction.RegulationMinStep = devices.RegulMinStep;
|
|
|
|
byte[] outData = OutputMessage.GetMessage(combinedAction, config, (ulong)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 = (ulong)(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()
|
|
{
|
|
/// Called from ProcessData.ClearProcessValues() at the beginning of each test
|
|
SetActivity(Activity.Idle);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from Start() and Run() functions of SetValvesOp, i.e. from State.RunOperations().
|
|
/// </summary>
|
|
/// <param name="valvesToOpen">Bits of valves to be open are set</param>
|
|
/// <param name="valvesToClose">Bits of valves to be closed are set</param>
|
|
/// <param name="logicalFnValves">A list of logical function valves (to be calculated and applied afterwards)</param>
|
|
/// <returns>Bits of changed valves are set in the return value</returns>
|
|
public UInt128 SetValves(UInt128 valvesToOpen, UInt128 valvesToClose, IList<BuiltIn.ValveEx.Valve> 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
|
|
///
|
|
/// <param name="delay">Delay of flow setting start in [s], default = 0</param>
|
|
public IOperation SetFlowOp(double flowLimLo, double flowLimHi, DoubleBox measuredFlow, int timeout)
|
|
{
|
|
return SetFlowOp(flowLimLo, flowLimHi, measuredFlow, timeout, 0);
|
|
}
|
|
public IOperation SetFlowOp(double flowLimLo, double flowLimHi, DoubleBox measuredFlow, int timeout, int delay)
|
|
{
|
|
return Devices.RegValve.SetFlowOp(Devices.FlowMeter, flowLimLo, flowLimHi, measuredFlow, timeout, delay);
|
|
}
|
|
|
|
public IOperation StopFlowRegulationOp()
|
|
{
|
|
StopAll(false);
|
|
//scheduledActionQueue.Enqueue(Action.Stop());
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a standing start-stop test
|
|
/// </summary>
|
|
/// <returns>Operation running a test</returns>
|
|
public IOperation StandingStartStopTestOp(Test test, int pulsesCount, bool withDiverter)
|
|
{
|
|
return new StandingStartStopTestOp(this, devices, pulsesCount, withDiverter);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a flying start-stop test (opitonally with a diverter, optionally prolonged)
|
|
/// </summary>
|
|
/// <returns>Operation running a test</returns>
|
|
public IOperation FlyingStartStopTestOp(Test test, double qFrom, double qTo, int pulsesCount, bool withDiverter = false,
|
|
bool isDelayedStart = false, bool isProlonged = false, int massPulsesCount = 0,
|
|
bool readDivTransition = false, Statistics plotterStart = null, Statistics plotterEnd = null)
|
|
{
|
|
return new FlyingStartStopTestOp(this, devices, qFrom, qTo, pulsesCount, withDiverter,
|
|
isDelayedStart, isProlonged, massPulsesCount, readDivTransition, plotterStart, plotterEnd);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns operation with events: Event.MeasurementCompleted or Event.None
|
|
/// </summary>
|
|
/// <returns>Operation</returns>
|
|
public IOperation QueryMeasurementEndOp()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public IOperation StopPreviousOp()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IValveControl interface
|
|
|
|
/// <summary>
|
|
/// Open/close one valve.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <param name="valveToOpen">Valve to be opened</param>
|
|
/// <param name="valveToClose">Valve to be closed</param>
|
|
/// <returns>Newly created operation instance reference casted to IOperaton</returns>
|
|
public IOperation SetValvesOp(IValve valveOpen, IValve valveClose)
|
|
{
|
|
return new BuiltIn.SetValvesOp(this, valveOpen, valveClose);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open/close multiple valves.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <param name="valvesToOpen">A list of valves to be opened</param>
|
|
/// <param name="valvesToClose">A list of valves to be closed</param>
|
|
/// <returns>Newly created operation instance reference casted to IOperaton</returns>
|
|
public IOperation SetValvesOp(IList<IValve> valvesOpen, IList<IValve> valvesClose)
|
|
{
|
|
return new BuiltIn.SetValvesOp(this, valvesOpen, valvesClose);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open start/stop valve.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <returns>Created operation</returns>
|
|
public IOperation OpenStartValveOp() { return OpenStartValveOp(null, null); }
|
|
public IOperation OpenStartValveOp(DateTimeBox openTimeStamp, FloatBox switchTime)
|
|
{
|
|
return new BuiltIn.SetValvesOp(this, true, devices, openTimeStamp, switchTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close start/stop valve.
|
|
/// Events: ValvesSet, ValvesBusy, Error
|
|
/// </summary>
|
|
/// <returns>Created operation</returns>
|
|
public IOperation CloseStartValveOp() { return CloseStartValveOp(null, null); }
|
|
public IOperation CloseStartValveOp(DateTimeBox closeTimeStamp, FloatBox switchTime)
|
|
{
|
|
return new BuiltIn.SetValvesOp(this, false, devices, closeTimeStamp, switchTime);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void MeasureFlow(bool isFromUI, int flowMeterId, int regulationMinStep = 0)
|
|
{
|
|
if (IsUIBlocked && isFromUI) return;
|
|
|
|
actionQueue.Enqueue(Action.MeasureFlow(isFromUI, flowMeterId, regulationMinStep));
|
|
log.InfoFormat("Enqueue( MeasureFlow(fm={0}) )", flowMeterId);
|
|
|
|
foreach (var a in actionQueue) log.DebugFormat(" {0}", a);
|
|
}
|
|
|
|
/// <summary>
|
|
/// To be used with a regulation valve controlled by incremental pulses.
|
|
/// Stability time is fixed: 200 ms
|
|
/// </summary>
|
|
public void SetFlow(bool isFromUI, int regVId, double freqLo, double freqHi, int regulationMinStep = 0)
|
|
{
|
|
if (IsUIBlocked && isFromUI) return;
|
|
|
|
actionQueue.Enqueue(Action.SetFlow(isFromUI, regVId, freqLo, freqHi, Convert.ToInt32(Math.Round(Devices.PidCoef)), 200, regulationMinStep));
|
|
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 isDelayedStart, bool isStartStop, bool isProlonged,
|
|
int totalPulsesCount, int massPulsesCount = -1)
|
|
{
|
|
if (IsUIBlocked && isFromUI) return;
|
|
|
|
actionQueue.Enqueue(Action.StartTest(isFromUI, flowMId, divId, divThreshold,
|
|
isSyncMethod, isDivUsed, isDelayedStart, 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, int regulationMinStep = 0)
|
|
{
|
|
if (IsUIBlocked && isFromUI) return;
|
|
|
|
if (regVId < TBF.Rig.Uni.RegValveAnalog.RegValve.RVAnalogIdx)
|
|
{
|
|
actionQueue.Enqueue(Action.RegVlvStop(isFromUI, regVId, regulationMinStep));
|
|
log.InfoFormat("Enqueue( RegVlvStop(rv={0}) )", regVId);
|
|
}
|
|
else if (regVId == TBF.Rig.Uni.RegValveAnalog.RegValve.RVAnalogIdx)
|
|
{
|
|
int dacVal = Data.StavDA[0];
|
|
actionQueue.Enqueue(Action.RegVlvMoveToPos(isFromUI, regVId, dacVal, regulationMinStep));
|
|
log.InfoFormat("Enqueue( RegVlvMoveToPos(rv={0}, positionLo={1}) )", regVId, dacVal);
|
|
}
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform incremental movement of a regulation valve
|
|
/// </summary>
|
|
/// <param name="rvId">Reg. valve ID</param>
|
|
/// <param name="time">Time in seconds, positive value opens the reg. valve</param>
|
|
public void RegVlvIncrMove(bool isFromUI, int regVId, double time, int regulationMinStep = 0)
|
|
{
|
|
if (IsUIBlocked && isFromUI || regVId >= TBF.Rig.Uni.RegValveAnalog.RegValve.RVAnalogIdx) return;
|
|
|
|
var newAction = Action.RegVlvIncrMove(isFromUI, regVId, time, regulationMinStep);
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move a regulation valve to specfied position
|
|
/// </summary>
|
|
/// <param name="rvId">Reg. valve ID</param>
|
|
/// <param name="position">Reg. valve position (0 .. 1.0)</param>
|
|
public void RegVlvMoveToPos(bool isFromUI, int regVId, int adcValLo, int adcValHi = -1, int regulationMinStep = 0)
|
|
{
|
|
if (IsUIBlocked && isFromUI || regVId > TBF.Rig.Uni.RegValveAnalog.RegValve.RVAnalogIdx) return;
|
|
|
|
actionQueue.Enqueue(Action.RegVlvMoveToPos(isFromUI, regVId, adcValLo, adcValHi, regulationMinStep));
|
|
log.InfoFormat("Enqueue( RegVlvMoveToPos(rv={0}, positionLo={1}, positionHi={2}) )", regVId, adcValLo, adcValHi);
|
|
|
|
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, 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
|
|
}
|
|
}
|
|
}
|