Refactorization : BenchControl -> Rig

This commit is contained in:
Milan Hanajik
2021-03-05 16:12:24 +01:00
parent 84c8ec26b6
commit 6cf3bd6160
1461 changed files with 6330 additions and 6330 deletions
+436
View File
@@ -0,0 +1,436 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using log4net;
using Dirichlet.Numerics;
namespace TBF.Rig.ControlBoard.Uni
{
public enum ActionID
{
/// Basic
ChangeRoute = 1, /// Change digital outputs (route)
MeasureFlow = 2, /// Activate a flow meter to measure the flow
SetFlow = 4, /// Set flow
StartTest = 8, /// Start a test
StopTest = 0x10, /// Stop a test or a flow regulation
RegVlvIncrMove = 0x20,
RegVlvMoveToPos = 0x40,
RegVlvStop = 0x80,
SwitchDiverter = 0x100, /// Switch diverter to tank/sink
/// Advanced
GetDiverterTransitionData,
GetScopeAnalyzerData,
ResetScopeAnalyzer,
GetSwitchCounterData,
}
public class Action
{
private static readonly ILog log = LogManager.GetLogger(typeof(Action));
public ActionID ActionId { get; set; }
public ulong Route { get; set; } /// ChangeRoute argument
public int FlowMId { get; set; } /// SetFlow, MeasureFlow and StartTest argument
public int RegVId { get; set; } /// SetFlow and StartTest argument
public int DivId { get; set; } /// StartTest and SwitchDiverter argument
public bool IsSyncMethod { get; set; } /// StartTest argument
public bool IsDivUsed { get; set; } /// StartTest argument
public bool ToTank { get; set; } /// SwitchDiverter argument
public int TotalPulsesCount { get; set; } /// StartTest argument
public int MassPulsesCount { get; set; } /// StartTest argument
public RegValveMode RegVMode { get; set; } /// SetFlow, RegVlvIncrMove, RegVlvMoveToPos, RegVlvStop
public int RVMovePar1 { get; set; } /// SetFlow, RegVlvIncrMove, RegVlvMoveToPos
public int RVMovePar2 { get; set; } /// SetFlow, RegVlvIncrMove, RegVlvMoveToPos
public int PID { get; set; } /// SetFlow
public int TolerRV { get; set; } /// SetFlow
public int StabTime { get; set; } /// SetFlow
public int DivThreshold { get; set; } /// StartTest argument
int StartStop { get; set; } /// Implicitly used by StartTest and StopTest: -1=not defined, 0=stop, 1=start
public Action()
{
/// Values -1 indicate the related arguments are not used yet
FlowMId = -1;
RegVId = -1;
DivId = -1;
RegVMode = RegValveMode.None;
StartStop = -1;
/// These values are used by one action type only. No conflicts possible
Route = 0;
TotalPulsesCount = 0;
MassPulsesCount = 0;
RVMovePar1 = 0;
RVMovePar2 = 0;
}
public bool IsStart()
{
return (ActionId & (ActionID.SetFlow | ActionID.StartTest)) != 0;
}
public bool IsStop()
{
return (ActionId & ActionID.StopTest) != 0;
}
///----------------------------------------------------------------------------------------------
public static Action ChangeRoute(ulong route)
{
return new Action
{
ActionId = ActionID.ChangeRoute,
Route = route,
};
}
void SetChangeRouteArgs(Action action)
{
Route = action.Route;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedChangeRouteArgs(Action newAction)
{
return Route == newAction.Route;
}
///----------------------------------------------------------------------------------------------
public static Action MeasureFlow(int flowMeterId)
{
return new Action
{
ActionId = ActionID.MeasureFlow,
FlowMId = flowMeterId,
};
}
void SetMeasureFlowArgs(Action action)
{
FlowMId = action.FlowMId;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedMeasureFlowArgs(Action newAction)
{
return (FlowMId == -1 || FlowMId == newAction.FlowMId);
}
///----------------------------------------------------------------------------------------------
public static Action SetFlow(int flowMeterId, int regValveId, double freqLo, double freqHi,
int pid, int tolerRV, int stabTime)
{
int rvMovePar1 = Convert.ToInt32(Math.Round(13.1072 * freqLo));
int rvMovePar2 = Convert.ToInt32(Math.Round(13.1072 * freqHi));
return new Action
{
ActionId = ActionID.SetFlow,
FlowMId = flowMeterId,
RegVId = regValveId,
RegVMode = RegValveMode.TargetFrequency,
RVMovePar1 = rvMovePar1,
RVMovePar2 = rvMovePar2,
PID = pid,
TolerRV = tolerRV,
StabTime = stabTime,
};
}
void SetSetFlowArgs(Action action)
{
FlowMId = action.FlowMId;
RegVId = action.RegVId;
RegVMode = action.RegVMode;
RVMovePar1 = action.RVMovePar1;
RVMovePar2 = action.RVMovePar2;
PID = action.PID;
TolerRV = action.TolerRV;
StabTime = action.StabTime;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedSetFlowArgs(Action newAction)
{
return (FlowMId == -1 || FlowMId == newAction.FlowMId) &&
(RegVId == -1 || RegVId == newAction.RegVId) &&
(RegVMode == RegValveMode.None || RegVMode == newAction.RegVMode) &&
(RVMovePar1 == -1 || RVMovePar1 == newAction.RVMovePar1) &&
(RVMovePar2 == -1 || RVMovePar2 == newAction.RVMovePar2);
}
///----------------------------------------------------------------------------------------------
public static Action StartTest(int flowMId, int divId, int divThreshold, bool isSyncMethod,
bool isDivUsed, int totalPulsesCount, int massPulsesCount = -1)
{
return new Action
{
ActionId = ActionID.StartTest,
FlowMId = flowMId,
DivId = divId,
DivThreshold = divThreshold,
IsSyncMethod = isSyncMethod,
IsDivUsed = isDivUsed,
TotalPulsesCount = totalPulsesCount,
MassPulsesCount = (massPulsesCount == -1) ? totalPulsesCount : massPulsesCount,
StartStop = 1,
};
}
void SetStartTestArgs(Action action)
{
FlowMId = action.FlowMId;
DivId = action.DivId;
DivThreshold = action.DivThreshold;
IsSyncMethod = action.IsSyncMethod;
IsDivUsed = action.IsDivUsed;
TotalPulsesCount = action.TotalPulsesCount;
MassPulsesCount = action.MassPulsesCount;
StartStop = 1;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedStartTestArgs(Action newAction)
{
return (FlowMId == -1 || FlowMId == newAction.FlowMId) &&
(DivId == -1 || DivId == newAction.DivId) &&
(StartStop == -1 || StartStop == 1);
}
///----------------------------------------------------------------------------------------------
public static Action StopTest()
{
return new Action()
{
ActionId = ActionID.StopTest,
StartStop = 0,
};
}
void SetStopTestArgs(Action action)
{
StartStop = 0;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedStopTestArgs(Action action)
{
return (StartStop == -1 || StartStop == 0);
}
///----------------------------------------------------------------------------------------------
public static Action RegVlvIncrMove(int regValveId, double timeSec)
{
int rvMovePar = Math.Abs(Convert.ToInt32(Math.Round(timeSec / 0.050))); /// Step is 50 ms
return new Action
{
ActionId = ActionID.RegVlvIncrMove,
RegVId = regValveId,
RegVMode = (timeSec >= 0) ? RegValveMode.PulseWidth : (RegValveMode.PulseWidth | RegValveMode.NegPulseWidth),
RVMovePar1 = rvMovePar,
RVMovePar2 = rvMovePar,
};
}
void SetRegVlvIncrMoveArgs(Action action)
{
RegVId = action.RegVId;
RegVMode = action.RegVMode;
RVMovePar1 = action.RVMovePar1;
RVMovePar2 = action.RVMovePar2;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedRegVlvIncrMoveArgs(Action newAction)
{
return (RegVId == -1 || RegVId == newAction.RegVId) &&
(RegVMode == RegValveMode.None || RegVMode == newAction.RegVMode);
}
///----------------------------------------------------------------------------------------------
public static Action RegVlvMoveToPos(int regValveId, double positionLo, double positionHi = -1)
{
int rvMovePar1, rvMovePar2; /// TODO
///
if (positionHi < 0)
{
/// Center position specified, calculate a range
rvMovePar1 = 0; // TODO
rvMovePar2 = 0; // TODO
}
else
{
/// Lower and upper range of position specified
rvMovePar1 = 0; // TODO
rvMovePar2 = 0; // TODO
}
return new Action
{
ActionId = ActionID.RegVlvMoveToPos,
RegVId = regValveId,
RegVMode = RegValveMode.TargetPosition,
RVMovePar1 = rvMovePar1,
RVMovePar2 = rvMovePar2,
};
}
void SetRegVlvMoveToPosArgs(Action action)
{
RegVId = action.RegVId;
RegVMode = action.RegVMode;
RVMovePar1 = action.RVMovePar1;
RVMovePar2 = action.RVMovePar2;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedRegVlvMoveToPosArgs(Action newAction)
{
return (RegVId == -1 || RegVId == newAction.RegVId) &&
(RegVMode == RegValveMode.None || RegVMode == newAction.RegVMode);
}
///----------------------------------------------------------------------------------------------
public static Action RegVlvStop(int regValveId)
{
return new Action
{
ActionId = ActionID.RegVlvStop,
RegVId = regValveId,
RegVMode = RegValveMode.Stop,
RVMovePar1 = 0,
RVMovePar2 = 0,
};
}
void SetRegVlvStopArgs(Action action)
{
RegVId = action.RegVId;
RegVMode = action.RegVMode;
RVMovePar1 = action.RVMovePar1;
RVMovePar2 = action.RVMovePar2;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedRegVlvStopArgs(Action newAction)
{
return (RegVId == -1 || RegVId == newAction.RegVId) &&
(RegVMode == RegValveMode.None || RegVMode == newAction.RegVMode);
}
///----------------------------------------------------------------------------------------------
public static Action SwitchDiverter(int divId, bool toTank)
{
return new Action
{
ActionId = ActionID.SwitchDiverter,
DivId = divId,
ToTank = toTank,
};
}
void SetSwitchDiverterArgs(Action action)
{
DivId = action.DivId;
ToTank = action.ToTank;
}
/// Check whether new action arguments are compatible with alredy collected arguments (true = yes)
bool CheckSharedSwitchDiverterArgs(Action action)
{
return (DivId == -1 || DivId == action.DivId);
}
///----------------------------------------------------------------------------------------------
void SetArgs(Action action)
{
ActionId |= action.ActionId;
switch (action.ActionId)
{
case ActionID.ChangeRoute: SetChangeRouteArgs(action); return;
case ActionID.MeasureFlow: SetMeasureFlowArgs(action); return;
case ActionID.SetFlow: SetSetFlowArgs(action); return;
case ActionID.StartTest: SetStartTestArgs(action); return;
case ActionID.StopTest: SetStopTestArgs(action); return;
case ActionID.RegVlvIncrMove: SetRegVlvIncrMoveArgs(action); return;
case ActionID.RegVlvMoveToPos: SetRegVlvMoveToPosArgs(action); return;
case ActionID.RegVlvStop: SetRegVlvStopArgs(action); return;
case ActionID.SwitchDiverter: SetSwitchDiverterArgs(action); return;
default: return;
}
}
/// <summary>
/// Check whether new action arguments are compatible with alredy collected arguments
/// </summary>
/// <param name="action">Action to be executed together with already collected actions</param>
/// <returns>true when compatible</returns>
bool CheckSharedArgs(Action action)
{
switch (action.ActionId)
{
case ActionID.ChangeRoute: return CheckSharedChangeRouteArgs(action);
case ActionID.MeasureFlow: return CheckSharedMeasureFlowArgs(action);
case ActionID.SetFlow: return CheckSharedSetFlowArgs(action);
case ActionID.StartTest: return CheckSharedStartTestArgs(action);
case ActionID.StopTest: return CheckSharedStopTestArgs(action);
case ActionID.RegVlvIncrMove: return CheckSharedRegVlvIncrMoveArgs(action);
case ActionID.RegVlvMoveToPos: return CheckSharedRegVlvMoveToPosArgs(action);
case ActionID.RegVlvStop: return CheckSharedRegVlvStopArgs(action);
case ActionID.SwitchDiverter: return CheckSharedSwitchDiverterArgs(action);
default: return false;
}
}
///----------------------------------------------------------------------------------------------
public static Action FetchNonConflictingActions(Queue<Action> queue)
{
log.Debug("Fetching non conflicting actions:");
if (queue == null || queue.Count == 0) return null;
Action resultA = queue.Dequeue();
log.Debug(resultA.ToString());
while (queue.Count > 0)
{
var peekA = queue.Peek();
if ((resultA.ActionId & peekA.ActionId) == 0 && resultA.CheckSharedArgs(peekA) == true)
{
/// ActionId would not be repeated and arguments are compatible => apply the new action
resultA.SetArgs(peekA);
log.Debug(peekA.ToString());
queue.Dequeue();
}
else
{
/// Leave actions in the queue and quit the loop as the next scheduled action is incompatible
break;
}
}
return resultA;
}
public override string ToString()
{
string args;
switch (ActionId)
{
case ActionID.ChangeRoute:
args = string.Format("Route={0}", Route.ToString("X16"));
break;
case ActionID.MeasureFlow:
args = string.Format("Etalon={0}", FlowMId);
break;
case ActionID.SetFlow:
args = string.Format("Etalon={0} RV={1} Par1={2} Par2={3}", FlowMId, RegVId, RVMovePar1, RVMovePar2);
break;
case ActionID.StartTest:
args = string.Format("Etalon={0} TotalP={1} MassP={2}", FlowMId, TotalPulsesCount, MassPulsesCount);
break;
case ActionID.RegVlvIncrMove:
args = string.Format("Time={0}ms", (RegVMode & RegValveMode.NegPulseWidth) != 0 ? -50 * RVMovePar1 : 50 * RVMovePar1);
break;
case ActionID.RegVlvMoveToPos:
args = string.Format("Position={0}%", 0); /// TODO
break;
default:
args = string.Empty;
break;
}
return string.Format(" {0} {1}", ActionId, args);
}
}
}
@@ -0,0 +1,59 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
namespace TBF.Rig.ControlBoard.Uni
{
public class DivTransitionData
{
public const int MessageLen = 255;
public const byte MessageCode = (byte)'K';
/// Diverter transition data
const int samplesCount = 245;
int DiverterTime_ms;
byte[] CurveRaw;
DivTransitionData()
{
CurveRaw = new byte[samplesCount];
}
/// <summary>
/// Returns tru when receive data fit the message frame
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="cumulativeSums">Cumulative sums of received data bytes</param>
/// <param name="rcvdBytesCount">Received bytes count</param>
/// <param name="offset">Offset of the message</param>
/// <returns>true when frame fits data</returns>
public static bool FrameFitsData(byte[] rcvdData, int[] cumulativeSums, int rcvdBytesCount, int offset)
{
if (offset + MessageLen > rcvdBytesCount) return false;
int checksum = cumulativeSums[offset + MessageLen - 3] - cumulativeSums[offset];
return rcvdData[offset] == Const.STX &&
rcvdData[offset + 1] == 0x01 &&
rcvdData[offset + 2] == ((MessageLen - 5) & 0xFF) &&
rcvdData[offset + 3] == MessageCode &&
rcvdData[offset + MessageLen - 2] == (checksum & 0xFF) &&
rcvdData[offset + MessageLen - 1] == ((checksum >> 8) & 0xFF);
}
/// <summary>
/// Proces received data
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="offset">Offset of the message</param>
public static DivTransitionData GetData(byte[] rcvdData, int offset)
{
var data = new DivTransitionData();
data.DiverterTime_ms = Const.DivTimeResolution * rcvdData[offset + 4];
for (int i = 0; i < samplesCount; i++)
{
data.CurveRaw[i] = rcvdData[offset + 6 + i];
}
return data;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
namespace TBF.Rig.ControlBoard.Uni
{
public class Const
{
public const byte STX = 0x10;
public const int WMsCount = 20; /// water meters count
public const int ADCsCount = 16; /// ADCs count: feedback from RV-s and diverters
public const int RVsCount = 8; /// 0..7
public const int FlowmtrsCount = 7; /// ID=1..7, ID=0 ... flowmeters 1,2,3 parallel
public const int DivertersCount = 5; /// 1..5
public const int DivTimeResolution = 2; /// [ms]
public const double TimeResolution = 0.0002; /// [s]
}
public enum StatusP : ulong
{
/// byte 0
FlowmtrNrMask = 0x7, /// bits 0,1,2
TestInProgress = 0x8, /// bit 3
TestCompleted = 0x10, /// bit 4
ReadingCounters = 0x20, /// bit 5
WaitingForDiverter = 0x40, /// bit 6
TestWithDiverter = 0x80, /// bit 7
/// byte 1
TestWithRefFlowmtr = 0x100, /// bit 8
DirectionOfDiverer = 0x200, /// bit 9
SynchroMethod = 0x800, /// bit 11
WaterMeterCalib = 0x1000, /// bit 12
EmgcyStopActive = 0x2000, /// bit 13
GateOn = 0x4000, /// bit 14
TimeMeasurementEn = 0x8000, /// bit 15
/// byte 2
FrequencyMeasured = 0x80000, /// bit 19
CoaxRegValveBusy = 0x200000, /// bit 21
/// byte 3
ADCError = 0x80000000, /// bit 31
/// byte 4
TimeoutOccured = 0x100000000, /// bit 32 (after 40s without communication)
MeretAddresses = 0x200000000, /// bit 33
ScopeBufferFull = 0x400000000, /// bit 34
/// byte 5
/// byte 6
Flowmtr1Active = 0x1000000000000, /// bit 48
Flowmtr2Active = 0x2000000000000, /// bit 49
Flowmtr3Active = 0x4000000000000, /// bit 50
}
}
+53
View File
@@ -0,0 +1,53 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
namespace TBF.Rig.ControlBoard.Uni
{
public class ExtremaData
{
public const int MessageLen = 253;
public const byte MessageCode = (byte)'E';
// TODO: Implement member variables
ExtremaData()
{
}
/// <summary>
/// Returns tru when receive data fit the message frame
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="cumulativeSums">Cumulative sums of received data bytes</param>
/// <param name="rcvdBytesCount">Received bytes count</param>
/// <param name="offset">Offset of the message</param>
/// <returns>true when frame fits data</returns>
public static bool FrameFitsData(byte[] rcvdData, int[] cumulativeSums, int rcvdBytesCount, int offset)
{
if (offset + MessageLen > rcvdBytesCount) return false;
int checksum = cumulativeSums[offset + MessageLen - 3] - cumulativeSums[offset];
return rcvdData[offset] == Const.STX &&
rcvdData[offset + 1] == 0x01 &&
rcvdData[offset + 2] == ((MessageLen - 5) & 0xFF) &&
rcvdData[offset + 3] == MessageCode &&
rcvdData[offset + MessageLen - 2] == (checksum & 0xFF) &&
rcvdData[offset + MessageLen - 1] == ((checksum >> 8) & 0xFF);
}
/// <summary>
/// Proces received data
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="offset">Offset of the message</param>
public static ExtremaData GetData(byte[] rcvdData, int offset)
{
var data = new ExtremaData();
// TODO
return data;
}
}
}
+27
View File
@@ -0,0 +1,27 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System.Collections.Generic;
using TBF.Rig.Generic;
namespace TBF.Rig.ControlBoard.Uni
{
public class Factory : IComponentFactory
{
public string ClassName { get { return this.GetType().Namespace.Substring(8); } }
public override string ToString() { return ClassName; }
public void ResetStaticProperties() { UniCB.ResetStaticProperties(); }
public IComponent DummyComponent() { return new UniCB(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new UniCB(cfg); }
public IComponentCfg DefaultConfig() { return new UniCBCfg("UniCB", this); }
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
{
return ComponentCfgBase.CreateFromDbEntity(UniCBCfg.Serializer, component, this);
}
}
}
+231
View File
@@ -0,0 +1,231 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
namespace TBF.Rig.ControlBoard.Uni
{
public class GeneralData
{
public const int MessageLen = 303;
public const byte MessageCode = (byte)' ';
/// General data
public ulong State; /// bytes 5,6,7,8,9,80,81 = 7 bytes = 56 bits
public uint Chyby485;
public ulong Vystupy;
public uint[] StavDA = new uint[2];
public double[] ReferenceFreq = new double[4];
public int[] EtPulses = new int[Const.WMsCount + 1]; /// EtPulses[0]=total pulses, EtPulses[1..WMsCount] gated et.pulses for respective WM
public int EtPulsesK;
public int EtPulses2;
public int EtPulses3;
public int[] WMeterPuls = new int[Const.WMsCount + 1]; /// WMsCount = 20
public double[] ImpulseTime = new double[Const.WMsCount + 1]; /// WMsCount = 20
public uint[] RegVStatus = new uint[Const.RVsCount + 1 + 1]; /// RVsCount = 8, posledny rValveStatus[9] je prudovy
public float[] RegVMoveTime = new float[Const.RVsCount];
public int DivTime;
public double Ttime; /// s
public double TimeRef; /// s
public ulong Vstupy;
public Int16[] AnalogInput = new Int16[Const.ADCsCount];
public int[] TemperatureRaw = new int[8];
public GeneralData()
{
}
/// <summary>
/// Returns tru when receive data fit the message frame
/// </summary>
/// <param name="data">Received data</param>
/// <param name="cumulativeSums">Cumulative sums of received data bytes</param>
/// <param name="rcvdBytesCount">Received bytes count</param>
/// <param name="offset">Offset of the message</param>
/// <returns>true when frame fits data</returns>
public static bool FrameFitsData(byte[] data, int[] cumulativeSums, int rcvdBytesCount, int offset)
{
if (offset + MessageLen > rcvdBytesCount) return false;
int checksum = cumulativeSums[offset + MessageLen - 3] - cumulativeSums[offset];
return data[offset] == Const.STX &&
data[offset + 1] == 0x01 &&
data[offset + 2] == ((MessageLen - 5) & 0xFF) &&
data[offset + 3] == MessageCode &&
data[offset + MessageLen - 2] == (checksum & 0xFF) &&
data[offset + MessageLen - 1] == ((checksum >> 8) & 0xFF);
}
/// <summary>
/// Auxiliary functions
/// </summary>
/// <param name="data">Received data</param>
/// <param name="offs">Offset to the files</param>
/// <returns></returns>
Int16 GetInt16(byte[] data, int offs) { return BitConverter.ToInt16(data, offs); }
UInt16 GetUInt16(byte[] data, int offs) { return BitConverter.ToUInt16(data, offs); }
UInt32 GetUInt24(byte[] data, int offs) { return BitConverter.ToUInt32(data, offs) & 0x00FFFFFF; }
UInt32 GetUInt32(byte[] data, int offs) { return BitConverter.ToUInt32(data, offs); }
UInt64 GetUInt40(byte[] data, int offs) { return BitConverter.ToUInt64(data, offs) & 0x000000FFFFFFFFFF; }
UInt64 GetUInt64(byte[] data, int offs) { return BitConverter.ToUInt64(data, offs); }
/// <summary>
/// Proces received data
/// </summary>
/// <param name="data">Received data</param>
/// <param name="offs">Offset of the message</param>
public void UpdateData(byte[] data, int offs)
{
State = GetUInt40(data, offs + (int)Poz.State) | /// Byte 5,6,7,8,9,80,81
((ulong)data[offs + (int)Poz.Stat5] << 40) |
((ulong)data[offs + (int)Poz.Stat6] << 48);
Vystupy = GetUInt64(data, offs + (int)Poz.Vyst); /// Byte 10 .. 17
StavDA[1] = GetUInt16(data, offs + (int)Poz.SDA2); /// Byte 18,19
Vstupy = GetUInt40(data, offs + (int)Poz.Vst); /// Byte 20,21,22,23,24
/// A/D converters: Feedback from RV-s and diverters
for (int i = 0; i < Const.ADCsCount; i++) /// Byte 25 .. 56
{
#if DEBUG
AnalogInput[Const.ADCsCount - 1 - i] = (short)(11 * (15 - i));
#else
AnalogInput[Const.ADCsCount - 1 - i] = GetInt16(data, offs + (int)Poz.Analogy + 2 * i);
#endif
}
/// Groch temperature meters
for (int i = 0; i < 8; i++) /// byte 57 .. 72
{
TemperatureRaw[i] = GetInt16(data, offs + (int)Poz.Groch + 2 * i);
}
EtPulses[0] = Convert.ToInt32(GetUInt24(data, offs + (int)Poz.Et1));/// Byte 73,74,75
EtPulsesK = Convert.ToInt32(GetUInt24(data, offs + (int)Poz.EtK)); /// Byte 76,77,78
byte spare1 = data[offs + (int)Poz.spare1]; /// byte 79
byte spare2 = data[offs + (int)Poz.spare2]; /// byte 82
for (int i = 0; i < Const.RVsCount; i++) /// Byte 83 .. 90
{
RegVStatus[i + 1] = ((uint)data[offs + (int)Poz.RVs + i] >> 3) & 7;
}
/// 4-20 mA reg. valve
uint stavRegQ = (uint)((State >> 16) & 0xFF);
RegVStatus[Const.RVsCount + 1] = (stavRegQ >> 4) & 2;
/// This must preceed processing bytes 91 .. 250 as Ttime is used there
uint ttimeRaw = GetUInt32(data, offs + (int)Poz.Ttime); /// Byte 251,252,253,254
Ttime = Convert.ToDouble(ttimeRaw) * Const.TimeResolution; /// convert to [s]
ImpulseTime[0] = Ttime;
/// Water meters (pulses count, gated test time, gated etalon pulses count)
int ix1 = offs + (int)Poz.CT1; /// Byte 91 .. 250
int ix2 = offs + (int)Poz.CTR1;
for (int wmNr = 1; wmNr <= Const.WMsCount; wmNr++, ix1 += 6, ix2 += 2)
{
if ((State & (ulong)StatusP.SynchroMethod) != 0)
{
ushort pulses = GetUInt16(data, ix2);
WMeterPuls[wmNr] = Convert.ToInt32(pulses);
EtPulses[wmNr] = (pulses == 0) ? 0 : Convert.ToInt32(GetUInt24(data, ix1));
ImpulseTime[wmNr] = (pulses == 0) ? 0 : Const.TimeResolution * Convert.ToDouble(GetUInt24(data, ix1 + 3));
}
else
{
WMeterPuls[wmNr] = Convert.ToInt32(GetUInt24(data, ix1));
EtPulses[wmNr] = EtPulses[0];
ImpulseTime[wmNr] = Ttime;
}
}
StavDA[0] = GetUInt16(data, offs + (int)Poz.SDA); /// Byte 255,256
byte spare3 = data[offs + (int)Poz.spare3]; /// Byte 257
/// Impulzy perioda (???)
uint[] par1 = new uint[3];
uint[] par2 = new uint[3];
par2[0] = GetUInt16(data, offs + (int)Poz.Per1); /// Byte 258,259
par1[0] = GetUInt16(data, offs + (int)Poz.Per1 + 2); /// Byte 260,261
par2[1] = GetUInt16(data, offs + (int)Poz.Per2); /// Byte 262.263
par1[1] = GetUInt16(data, offs + (int)Poz.Per2 + 2); /// Byte 264,265
par2[2] = GetUInt16(data, offs + (int)Poz.Per3); /// Byte 266,267
par1[2] = GetUInt16(data, offs + (int)Poz.Per3 + 2); /// Byte 268,269
///
ReferenceFreq[0] = (par2[0] != 0) ? (5.0f * par1[0] / par2[0]) : 0.0f;
ReferenceFreq[1] = ReferenceFreq[2] = ReferenceFreq[3] = 0;
///
if ((State & (ulong)StatusP.TestWithRefFlowmtr) != 0 && (State & (ulong)StatusP.FlowmtrNrMask) == 0)
{
/// Measurement with parallel flowmeters
if ((State & (ulong)StatusP.Flowmtr1Active) != 0)
{
ReferenceFreq[1] = ReferenceFreq[0];
}
if ((State & (ulong)StatusP.Flowmtr2Active) != 0)
{
ReferenceFreq[2] = (par2[1] != 0) ? (5.0f * par1[1] / par2[1]) : 0.0f;
}
if ((State & (ulong)StatusP.Flowmtr3Active) != 0)
{
ReferenceFreq[3] = (par2[2] != 0) ? (5.0f * par1[2] / par2[2]) : 0.0f;
}
ReferenceFreq[0] = ReferenceFreq[1] + ReferenceFreq[2] + ReferenceFreq[3];
}
DivTime = Const.DivTimeResolution * data[offs + (int)Poz.CasKl]; /// Byte 271
byte spare4 = data[offs + (int)Poz.spare4]; /// Byte 272
Chyby485 = GetUInt16(data, offs + (int)Poz.Chyb485); /// Byte 273, 274 !!!!!!!!!!!!!!!
uint timeRefRaw = GetUInt24(data, offs + (int)Poz.Tim2); /// Byte 274,275,276 !!!!!!!!!!!!!!!
TimeRef = Convert.ToDouble(timeRefRaw) / 1000.0; /// convert to [s]
for (int i = 0; i < Const.RVsCount; i++) /// Byte 278 .. 293
{
RegVMoveTime[i] = 10 * GetUInt16(data, offs + (int)Poz.CasKoh + 2*i);
}
EtPulses2 = Convert.ToInt32(GetUInt24(data, offs + (int)Poz.ETX2)); /// Byte 294,295,296
EtPulses3 = Convert.ToInt32(GetUInt24(data, offs + (int)Poz.ETX3)); /// Byte 297,298.299 !!!!!!!!!!!!!!!
}
}
/// <summary>
/// Byte positions in rcvdData buffer
/// </summary>
public enum Poz : int
{
State = 5, /// 5 .. 9
Vyst = State + 5, /// 10 .. 17
SDA2 = Vyst + 8, /// 18,19
Vst = SDA2 + 2, /// 20 .. 24
Analogy = Vst + 5, /// 25 .. 56
Groch = Analogy + 2 * Const.ADCsCount, /// 57 (16 analog inputs)
Et1 = Groch + 16, /// 73,74,75
EtK = Et1 + 3, /// 76,77,78
spare1 = EtK + 3, /// 79
Stat5 = spare1 + 1, /// 80
Stat6 = Stat5 + 1, /// 81
spare2 = Stat6 + 1, /// 82
RVs = spare2 + 1, /// 83
CT1 = RVs + 8, /// 91
CTR1 = CT1 + 6 * Const.WMsCount, /// 211
Ttime = CTR1 + 2 * Const.WMsCount, /// 251
SDA = Ttime + 4, /// 255
spare3 = SDA + 2, /// 257
Per1 = spare3 + 1, /// 258
Per2 = Per1 + 4, /// 262
Per3 = Per2 + 4, /// 266
CasKl = Per3 + 5, /// 271
spare4 = CasKl + 1, /// 272
Chyb485 = spare4 + 1, /// 273
Tim2 = Chyb485 + 1, /// 274
CasKoh = Tim2 + 4, /// 278
ETX2 = CasKoh + 2 * Const.RVsCount, /// 294
ETX3 = ETX2 + 3, /// 297
}
}
+186
View File
@@ -0,0 +1,186 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Diagnostics;
using Dirichlet.Numerics;
namespace TBF.Rig.ControlBoard.Uni
{
public static class OutputMessage
{
public const int PayloadLen = 38;
public const byte MessageCode = (byte)' ';
public static byte[] GetMessage(Action action, short config)
{
byte[] message = new byte[PayloadLen + 5]; /// headed 3 bytes, checksum 2 bytes
/// Header + message code (4 bytes)
message[0] = Const.STX;
message[1] = 1; /// Control board addres
message[2] = (byte)PayloadLen; /// Payload length
message[3] = MessageCode;
/// Command
message[4] = GetCommand(action);
message[5] = action.IsStart() ? GetStartParams(action) : action.IsStop() ? GetStopParams(action) : (byte)0;
message[6] = (action.DivId <= 0) ? (byte)0 : (byte)(action.DivId - 1);
int totalPulsesCount = (action.TotalPulsesCount < 0) ? 0 : action.TotalPulsesCount;
message[7] = (byte)(totalPulsesCount & 0xFF);
message[8] = (byte)((totalPulsesCount >> 8) & 0xFF);
message[9] = (byte)((totalPulsesCount >> 16) & 0xFF);
int massPulsesCount = (action.MassPulsesCount < 0) ? 0 : action.MassPulsesCount;
message[10] = (byte)(massPulsesCount & 0xFF);
message[11] = (byte)((massPulsesCount >> 8) & 0xFF);
message[12] = (byte)((massPulsesCount >> 16) & 0xFF);
message[13] = 0;
message[14] = (byte)(config & 0xff);
message[15] = (byte)((config >> 8) & 0xff);
/// Route
message[16] = ((action.ActionId & ActionID.ChangeRoute) != 0) ? (byte)1 : (byte)0; /// Route change flag
message[17] = (byte)(action.Route & 0xFF);
message[18] = (byte)((action.Route >> 8) & 0xFF);
message[19] = (byte)((action.Route >> 16) & 0xFF);
message[20] = (byte)((action.Route >> 24) & 0xFF);
message[21] = (byte)((action.Route >> 32) & 0xFF);
message[22] = (byte)((action.Route >> 40) & 0xFF);
message[23] = (byte)((action.Route >> 48) & 0xFF);
message[24] = (byte)((action.Route >> 56) & 0xFF);
/// DA2
message[25] = 0;
message[26] = 0;
/// Regulation valve
message[27] = (action.RegVId < 0) ? (byte)0 : (byte)(action.RegVId | (int)action.RegVMode);
message[28] = (byte)(action.RVMovePar1 & 0xFF);
message[29] = (byte)((action.RVMovePar1 >> 8) & 0xFF);
message[30] = (byte)(action.RVMovePar2 & 0xFF);
message[31] = (byte)((action.RVMovePar2 >> 8) & 0xFF);
message[32] = action.IsStop() ? (byte)0x20 : action.IsStart() ? (byte)0x42 : (byte)0; /// ZStavReg
/// DA1
message[33] = 0; /// PrudParL
message[34] = 0; /// PrudParH
message[35] = (byte)Math.Max(1, Math.Min(255, action.PID)); /// PIDRV
message[36] = (byte)Math.Max(4, Math.Min(255, action.TolerRV)); /// TolerRV (4..20 mA reg.valve only)
message[37] = (byte)Math.Max(0, Math.Min(255, action.StabTime)); /// StabRV
message[38] = (byte)((action.DivThreshold >> 2) & 0xFF); /// HranAK
message[39] = 0;
message[40] = 0;
Debug.Assert(40 == PayloadLen + 2);
/// Checksum (2 bytes)
int checksum = 0;
for (int i = 1; i < PayloadLen + 3; i++) checksum += message[i];
message[PayloadLen + 3] = (byte)(checksum & 0xFF);
message[PayloadLen + 4] = (byte)((checksum >> 8) & 0xFF);
return message;
}
static byte GetCommand(Action action)
{
int command = 0;
if (action.IsStart()) command |= (int)Command.Start;
if (action.IsStop()) command |= (int)Command.Stop;
return (byte)command;
}
static byte GetStartParams(Action action)
{
int param = action.FlowMId & (int)StartParam.EtalonMask;
if (action.IsDivUsed)
param |= (int)StartParam.DiverterStart;
if (action.IsSyncMethod)
param |= (int)StartParam.SynchroMethod;
else
param |= (int)StartParam.StartStopMethod;
return (byte)param;
}
static byte GetStopParams(Action action)
{
int param = 0;
return (byte)param;
}
}
public enum Command : byte
{
RequestData = 0,
Start = 1,
Stop = 2,
GetDiverterTransitionData = 4,
GetScopeAnalyzerData = 7,
ResetScopeAnalyzer = 8,
GetSwitchCounterData = 9,
}
public enum StartParam : byte
{
EtalonMask = 0x07,
DiverterStart = 0x08,
SynchroMethod = 0x10,
DirectGateStart = 0x20, /// ???
StartStopMethod = 0x40,
CalibrationMode = 0x80,
}
public enum StopDevs : byte
{
None = 0,
Reference = 0x01, /// bit 0
Diverter = 0x02, /// bit 1
GatePulse = 0x08, /// bit 3
TimeMeasurement = 0x10, /// bit 4
BypassDevCoupled2Div = 0x20, /// bit 5
RegValveRegulation = 0x80, /// bit 7
All = 0xFF,
}
public enum TestMethods
{
Diverter = 0x01, /// bit 0: 1=Mass method (diverters are used), 0=Volume method
FixedStart = 0x02, /// bit 1: 1=fixed start method, 0=flying start method
Synchro = 0x04, /// bit 2: 1=Synchro method, 0=Pulse counting
MassAndContinue = 0x10, /// bit 4: 1=Method with mass measurement followed by a volume method
}
public enum ValveChgFlag : byte
{
DoNotChange = 0,
Change = 1,
SendPressureMeterAddresses = 2,
}
public enum RegValveMode : byte
{
None = 0,
TargetFrequency = 0x10, /// Low and high frequency limits (0..2000Hz,0..2200Hz) are specified
TargetPosition = 0x20, /// Low and high position (=DAC value) limits (0..100%) are specified
Stop = 0x30, /// Stop regulation
PulseWidth = 0x40, /// Pulse width 0.05 .. 2 sec. ... opening and closing (opening=0x40, closing=0xC0)
NegPulseWidth = 0x80, /// Pulse width 0.05 .. 2 sec. ... closing (closing=0xC0)
}
public enum RegValveState : byte
{
Idle = 0,
PwOrFreqRegul = 2,
DacValueRegul = 3,
}
}
@@ -0,0 +1,57 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
namespace TBF.Rig.ControlBoard.Uni
{
public class ScopeAnalyzerData
{
public const int MessageLen = 1033;
public const byte MessageCode = (byte)'S';
/// Scope analyzer data
public int KonSc;
public int ZacSc;
ScopeAnalyzerData()
{
}
/// <summary>
/// Returns tru when receive data fit the message frame
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="cumulativeSums">Cumulative sums of received data bytes</param>
/// <param name="rcvdBytesCount">Received bytes count</param>
/// <param name="offset">Offset of the message</param>
/// <returns>true when frame fits data</returns>
public static bool FrameFitsData(byte[] rcvdData, int[] cumulativeSums, int rcvdBytesCount, int offset)
{
if (offset + MessageLen > rcvdBytesCount) return false;
int checksum = cumulativeSums[offset + MessageLen - 3] - cumulativeSums[offset];
return rcvdData[offset] == Const.STX &&
rcvdData[offset + 1] == 0x01 &&
rcvdData[offset + 2] == ((MessageLen - 5) & 0xFF) &&
rcvdData[offset + 3] == MessageCode &&
rcvdData[offset + MessageLen - 2] == (checksum & 0xFF) &&
rcvdData[offset + MessageLen - 1] == ((checksum >> 8) & 0xFF);
}
/// <summary>
/// Proces received data
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="offset">Offset of the message</param>
public static ScopeAnalyzerData GetData(byte[] rcvdData, int offset)
{
var data = new ScopeAnalyzerData();
data.KonSc = Convert.ToInt32(rcvdData[4]) + 256 * Convert.ToInt32(rcvdData[5]);
data.ZacSc = Convert.ToInt32(rcvdData[6]) + 256 * Convert.ToInt32(rcvdData[7]);
return data;
}
}
}
@@ -0,0 +1,53 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
namespace TBF.Rig.ControlBoard.Uni
{
public class SwitchCountersData
{
public const int MessageLen = 303;
public const byte MessageCode = (byte)'C';
// TODO: Implement member variables
SwitchCountersData()
{
}
/// <summary>
/// Returns tru when receive data fit the message frame
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="cumulativeSums">Cumulative sums of received data bytes</param>
/// <param name="rcvdBytesCount">Received bytes count</param>
/// <param name="offset">Offset of the message</param>
/// <returns>true when frame fits data</returns>
public static bool FrameFitsData(byte[] rcvdData, int[] cumulativeSums, int rcvdBytesCount, int offset)
{
if (offset + MessageLen > rcvdBytesCount) return false;
int checksum = cumulativeSums[offset + MessageLen - 3] - cumulativeSums[offset];
return rcvdData[offset] == Const.STX &&
rcvdData[offset + 1] == 0x01 &&
rcvdData[offset + 2] == ((MessageLen - 5) & 0xFF) &&
rcvdData[offset + 3] == MessageCode &&
rcvdData[offset + MessageLen - 2] == (checksum & 0xFF) &&
rcvdData[offset + MessageLen - 1] == ((checksum >> 8) & 0xFF);
}
/// <summary>
/// Proces received data
/// </summary>
/// <param name="rcvdData">Received data</param>
/// <param name="offset">Offset of the message</param>
public static SwitchCountersData GetData(byte[] rcvdData, int offset)
{
var data = new SwitchCountersData();
// TODO
return data;
}
}
}
+596
View File
@@ -0,0 +1,596 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Text;
using log4net;
using Common;
using Config.Entities;
using Dirichlet.Numerics;
using TBF.Rig.GenericDevices;
using TBF.Rig.Sequences;
namespace TBF.Rig.ControlBoard.Uni
{
public class UniCB : ComponentBase, IControlBoard, SchematicDrawing.IDrawingItCmpntWithMeasuredVal
{
private static readonly ILog log = LogManager.GetLogger(typeof(UniCB));
public override string ToString() { return string.Format("CBoard({0})", cbCfg != null ? cbCfg.ToString(1) : string.Empty); }
/// TBF component configuration and useful wrappers
readonly UniCBCfg cbCfg;
string comPort { get { return string.Format("COM{0}", cbCfg.ComPortNr); } }
short config { get { return (short)((cbCfg.DrainValvesCount << 8) + ((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 {
altString.Clear();
altString.AppendFormat("DI: {0}\n", ToBin40(DigitalInputs));
altString.AppendFormat("DO: {0}\n", ToBin40((UInt64)Route));
altString.AppendFormat("AD 0:{0:D4} 1:{1:D4} 2:{2:D4} 3:{3:D4} 4:{4:D4} 5:{5:D4}\n",
AnalogInput(0), AnalogInput(1), AnalogInput(2), AnalogInput(3), AnalogInput(4), AnalogInput(5));
altString.AppendFormat("AD 8:{0:D4} 9:{1:D4} 10:{2:D4} 11:{3:D4}",
AnalogInput(8), AnalogInput(9), AnalogInput(10), AnalogInput(11));
return altString.ToString();
}
}
string ToBin40(UInt64 val)
{
return string.Format("{0}-{1}-{2}-{3}-{4}", ToBin8((val >> 32) & 0xFF), ToBin8((val >> 24) & 0xFF),
ToBin8((val >> 16) & 0xFF), ToBin8((val >> 8) & 0xFF), ToBin8(val & 0xFF));
}
string ToBin8(UInt64 val)
{
string str = Convert.ToString((byte)val, 2);
switch (str.Length)
{
case 1: return "0000000" + str;
case 2: return "000000" + str;
case 3: return "00000" + str;
case 4: return "0000" + str;
case 5: return "000" + str;
case 6: return "00" + str;
case 7: return "0" + str;
default: return str;
}
}
/// Uni CB state
UInt32 currentOutputs;
bool outputsInitialized;
DateTime lastOutputsChange;
int lastStateMachineTime = 0;
/// Propagated from configurations of other components
UInt128 benchModelRoute;
UInt128 valvesToInvert; /// Route = valesToInvert ^ RequiredRouteWithoutInvertedVlaves
readonly UInt128 routeMask; /// This mask masks out virtual valves, routeMask is set in the constructor
/// Serial communication
SerialPort serialPort; /// Seral port or null: Check serialPort.IsOpen before using the serial port
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;
int rcvdBytesCount; /// Count of valid bytes in rcvdData buffer
/// Message queue
Queue<Action> scheduledActionQueue;
/// Received data
public GeneralData Data;
DivTransitionData divTransitionData;
ScopeAnalyzerData scopeAnalyzerData;
SwitchCountersData switchCounterData;
public UInt64 State { get { return Data.State; } } /// Control board state
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];
}
return 0;
}
/// <summary>
/// Route: 128-bit word representing the current state of all valves and pumps
/// </summary>
public UInt128 Route { get { return benchModelRoute; } }
/// Values valid during a test
public double RefFrequency { get { return Data.ReferenceFreq[0]; } }
public double RefFlow
{
get { return (Devices != null && Devices.FlowMeter != null) ? Devices.FlowMeter.ReadFlow() : 0.0; }
}
/// Last test results
public double TestTime { set; get; }
public int RefPulses { set; get; }
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()
{
devices = new OutputPath(string.Empty, 0);
}
/// <summary>
/// Constructor invoked when starting the test bench and creating components.
/// </summary>
public UniCB(Generic.IComponentCfg cfg)
: base(cfg)
{
#if DEBUG
Debug.WriteLine("");
Debug.WriteLine("Uni CB enums");
Debug.WriteLine("");
foreach (var e in Enum.GetValues(typeof(Poz)))
{
Debug.WriteLine(string.Format("{0} ... {1}", e, ((int)e)));
}
#endif
cbCfg = cfg as UniCBCfg;
devices = new OutputPath(string.Empty, 0);
scheduledActionQueue = new Queue<Action>();
Data = new GeneralData();
divTransitionData = null;
scopeAnalyzerData = null;
switchCounterData = null;
serialPort = null;
buffer = new byte[BUFFER_SIZE];
rcvdData = new byte[BUFFER_SIZE];
cumulativeSums = new int[BUFFER_SIZE];
rcvdBytesCount = 0;
routeMask = 0;
for (int i = 0; i < 128; i++)
{
if (i < cbCfg.VirtValvesRangeLo || i > cbCfg.VirtValvesRangeHi)
{
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);
}
};
log.Warn(this.ToString());
}
/// <summary>
/// Specify inverted valves
/// </summary>
/// <param name="valvesToInvert"></param>
public void SpecifyInvertedValves(UInt128 valvesToInvert)
{
this.valvesToInvert = valvesToInvert;
this.benchModelRoute = valvesToInvert;
}
#region IDevice interface
/// <summary>
/// Initialize control board device hardware
/// </summary>
public void Initialize()
{
if (cbCfg.DebugLevel != DebugMode.Normal)
{
log.FatalFormat("{0} - Device simulated", Name);
return;
}
serialPort = new SerialPort(comPort, 9600, Parity.None, 8, StopBits.One);
serialPort.DtrEnable = true;
serialPort.WriteTimeout = 4000;
serialPort.ParityReplace = 0xFF;
serialPort.Open();
log.FatalFormat("{0} - Device successfully initialized", Name);
}
/// <summary>
/// Reecive and process data from the control board serial port
/// </summary>
public void RunDeviceBefore()
{
if (cbCfg.DebugLevel != DebugMode.Normal || serialPort == null || !serialPort.IsOpen) return;
/// Read new received bytes and append them to a buffer
int bytesCount = serialPort.BytesToRead;
if (bytesCount > 0)
{
bytesCount = serialPort.Read(buffer, 0, Math.Min(bytesCount, BUFFER_SIZE));
}
for (int i = 0; i < bytesCount && rcvdBytesCount < BUFFER_SIZE; i++, rcvdBytesCount++)
{
rcvdData[rcvdBytesCount] = 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;
if (ScopeAnalyzerData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset))
{
/// Scope analyzer data received
scopeAnalyzerData = ScopeAnalyzerData.GetData(rcvdData, offset);
string s = Telegram.LogTelegram("Scope analyzer data ", rcvdData, offset, ScopeAnalyzerData.MessageLen);
Debug.WriteLine(s);
log.Debug(s);
offset += ScopeAnalyzerData.MessageLen;
lastValidOffset = offset;
anyProcessed = true;
}
else if (GeneralData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset))
{
/// Regular data received
Data.UpdateData(rcvdData, offset);
string s = Telegram.LogTelegram("General data ", rcvdData, offset, GeneralData.MessageLen);
Debug.WriteLine(s);
log.Debug(s);
offset += GeneralData.MessageLen;
lastValidOffset = offset;
anyProcessed = true;
}
else if (SwitchCountersData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset))
{
/// Switch counter data received
switchCounterData = SwitchCountersData.GetData(rcvdData, offset);
string s = Telegram.LogTelegram("Switch counter data ", rcvdData, offset, SwitchCountersData.MessageLen);
Debug.WriteLine(s);
log.Debug(s);
offset += SwitchCountersData.MessageLen;
lastValidOffset = offset;
anyProcessed = true;
}
else if (DivTransitionData.FrameFitsData(rcvdData, cumulativeSums, rcvdBytesCount, offset))
{
/// Diverter transition data received
divTransitionData = DivTransitionData.GetData(rcvdData, offset);
string s = Telegram.LogTelegram("Diverter transition data ", rcvdData, offset, DivTransitionData.MessageLen);
Debug.WriteLine(s);
log.Debug(s);
offset += DivTransitionData.MessageLen;
lastValidOffset = offset;
anyProcessed = true;
}
if (!anyProcessed) offset++;
}
while (offset < rcvdBytesCount);
/// Remove processed data from the receive buffer
for (int i = lastValidOffset; i < rcvdBytesCount; i++)
{
rcvdData[i - lastValidOffset] = rcvdData[i];
}
rcvdBytesCount -= lastValidOffset;
}
/// <summary>
/// Send data to the control board serial port
/// </summary>
public void RunDeviceAfter()
{
if (cbCfg.DebugLevel != DebugMode.Normal) return;
/// Make sure the serial port is open
if (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 (!outputsInitialized || Data.Vystupy != (ulong)benchModelRoute || (StateMachine.Time - lastStateMachineTime) >= 15)
{
scheduledActionQueue.Enqueue(Action.ChangeRoute((ulong)benchModelRoute));
log.InfoFormat("Enqueue( ChangeRoute(route={0:X}) )", benchModelRoute);
foreach (var a in scheduledActionQueue) log.DebugFormat(" {0}", a);
}
var combinedAction = Action.FetchNonConflictingActions(scheduledActionQueue);
byte[] outData = OutputMessage.GetMessage(combinedAction, config);
if (outData != null && outData.Length > 0)
{
serialPort.Write(outData, 0, outData.Length);
string s = Telegram.LogTelegram("Sent: ", outData);
Debug.WriteLine(s);
log.Debug(s);
}
ProcessData.UpdateMeasuredValuesAndSetpoints();
TBF.UiBridge.Bridge.OnStateMachineTick(this, new TBF.UiBridge.StateMachineTickEventArgs(Route, ProcessData.MsrmntAvailableFlags,
ProcessData.MeasuredValues,
ProcessData.AltStrings,
ProcessData.Setpoints));
}
public void StopDevice()
{
if (serialPort != null) serialPort.Close();
serialPort = null;
}
public void StopDevice2()
{
}
#endregion
#region IControlBoard interface
///
/// Interface functions
///
public void ClearProcessValues() { }
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;
}
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.RouteToStr(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;
}
///
/// Operations
///
public IOperation SetFlowOp(double flowLimLo, double flowLimHi, TBF.Boxes.DoubleBox measuredFlow, int timeout)
{
return Devices.RegValve.SetFlowOp(Devices.FlowMeter,flowLimLo, flowLimHi, measuredFlow, timeout);
}
public IOperation StopFlowRegulationOp()
{
//scheduledActionQueue.Enqueue(Action.Stop());
return null;
}
/// <summary>
/// Start a test, Events: MeasurementStarted
/// </summary>
/// <param name="iParam">Int32 parameter 1</param>
/// <param name="dParam1">Double parameter 1 (alternative lower flow limit)</param>
/// <param name="dParam2">Double parameter 2 (alternative upper flow limit)</param>
/// <returns>Operation</returns>
public IOperation StartTestOp(Test test, int iParam = 0, double dParam1 = 0, double dParam2 = 0)
{
return null;
}
public IOperation ReadDiverterTransitionOp(bool isStart, Sequences.Statistics plotter, int batchNr, string testName, int repetition)
{
return null;
}
/// <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 new BuiltIn.SetValvesOp(this, true, devices);
}
/// <summary>
/// Close start/stop valve.
/// Events: ValvesSet, ValvesBusy, Error
/// </summary>
/// <returns>Created operation</returns>
public IOperation CloseStartValveOp()
{
return new BuiltIn.SetValvesOp(this, false, devices);
}
#endregion
public void MeasureFlow(int flowMeterId)
{
scheduledActionQueue.Enqueue(Action.MeasureFlow(flowMeterId));
log.InfoFormat("Enqueue( MeasureFlow(fm={0}) )", flowMeterId);
foreach (var a in scheduledActionQueue) log.DebugFormat(" {0}", a);
}
public void SetFlow(int flowMeterId, int regVId, double freqLo, double freqHi)
{
scheduledActionQueue.Enqueue(Action.SetFlow(flowMeterId, regVId, freqLo, freqHi, Convert.ToInt32(Math.Round(Devices.PidCoef)), 0, 200));
log.InfoFormat("Enqueue( SetFlow(fm={0}, rv={1}, fLo={2}, fHi={3}, pid={4}) )", flowMeterId, regVId, freqLo, freqHi, Convert.ToInt32(Math.Round(Devices.PidCoef)));
foreach (var a in scheduledActionQueue) 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(int regVId, double time)
{
scheduledActionQueue.Enqueue(Action.RegVlvIncrMove(regVId, time));
log.InfoFormat("Enqueue( RegVlvIncrMove(rv={0}, time={1}) )", regVId, time);
foreach (var a in scheduledActionQueue) 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(int regVId, double positionLo, double positionHi = -1)
{
scheduledActionQueue.Enqueue(Action.RegVlvMoveToPos(regVId, positionLo, positionHi));
log.InfoFormat("Enqueue( RegVlvMoveToPos(rv={0}, positionLo={1}, positionHi={2}) )", regVId, positionLo, positionHi);
foreach (var a in scheduledActionQueue) log.DebugFormat(" {0}", a);
}
public void DiverterToTank(int divNr1)
{
scheduledActionQueue.Enqueue(Action.SwitchDiverter(divNr1, true));
log.InfoFormat("Enqueue( SwitchDiverter(div={0}, true) )", divNr1);
foreach (var a in scheduledActionQueue) log.DebugFormat(" {0}", a);
}
public void DiverterToSink(int divNr1)
{
scheduledActionQueue.Enqueue(Action.SwitchDiverter(divNr1, false));
log.InfoFormat("Enqueue( SwitchDiverter(div={0}, false) )", divNr1);
foreach (var a in scheduledActionQueue) log.DebugFormat(" {0}", a);
}
public void ReadDiverterTransition(int divNr1)
{
/// TODO
}
}
}
+188
View File
@@ -0,0 +1,188 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using Config.Entities;
using SchematicDrawing;
using TBF.Rig.Generic;
namespace TBF.Rig.ControlBoard.Uni
{
/// <summary>
/// Holds backup and security options - serializable configuration.
/// </summary>
public class UniCBCfg : ComponentCfgBase, Generic.IComponentCfg, Config.Entities.IParamsProvider, IDrawingItemWithMeasuredVal
{
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(UniCBCfg) })[0];
public override XmlSerializer GetSerializer() { return Serializer; }
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities) { return new Configs.ParamsProvider.ComponentCfgCtrl(this, null); }
///
/// Serialized parameters
///
public int ComPortNr;
public int DivertersCount;
public int DrainValvesCount;
public int RegValvesCount;
public int VirtValvesRangeLo;
public int VirtValvesRangeHi;
/// Schematic drawing info
public Shape Shape { get; set; }
public int X { get; set; }
public int Y { get; set; }
public Sz Sz { get; set; }
public Orient Orient { get; set; }
public bool Flip { get; set; }
public int LblX { get; set; }
public int LblY { get; set; }
public Orient LblOrient { get; set; }
public int MsrdX { get; set; }
public int MsrdY { get; set; }
public Orient MsrdOrient { get; set; }
public Config.Unit MsrdUnit { get; set; }
public string MsrdFormat { get; set; }
public double MsrdValLimLo { get; set; }
public double MsrdValLimHi { get; set; }
[XmlIgnore]
public IList<GNode> GNodes { get; set; }
[XmlIgnore]
public IList<GEdge> EdgesToSet { get; set; }
/// Private parameterless constructor invoked by all other (public) constructors
UniCBCfg()
{
GNodes = new List<GNode>();
EdgesToSet = new List<GEdge>();
}
public UniCBCfg(string name, IComponentFactory factory)
: this()
{
Shape = Shape.UniCB;
Sz = Sz.L;
Name = name;
Factory = factory;
ParentName = string.Empty;
InitializeAll();
}
public string ComponentName { get { return Name; } }
public void InitializeAll()
{
ComPortNr = 1;
VirtValvesRangeLo = 90;
VirtValvesRangeHi = 99;
}
string[] paramNames = new string[]
{
"Serial port number",
"Diverters count (1..5)",
"Drain valves count (0..8)",
"Regulation valves count (4..8)",
"Virtual valves range LO [bit#]",
"Virtual valves range HI [bit#]",
};
public string ParamName(int i) { return paramNames[i]; }
public int ParamsCount() { return paramNames.Length; }
public ICollection<string> ParamValues(int i)
{
return null;
}
public string ToString(int i)
{
if (i == -1)
{
return string.Format("{0}: COM{1}, VirtValvesRngLO={2}, VirtValvesRngeHI={3}", Name, ComPortNr, VirtValvesRangeLo, VirtValvesRangeHi);
}
switch (i)
{
case 0: return ComPortNr.ToString();
case 1: return DivertersCount.ToString();
case 2: return DrainValvesCount.ToString();
case 3: return RegValvesCount.ToString();
case 4: return VirtValvesRangeLo.ToString();
case 5: return VirtValvesRangeHi.ToString();
default: return string.Empty;
}
}
public CfgUpdateFlags UpdateParam(int i, string strValue)
{
switch (i)
{
case 0: ComPortNr = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
case 1: DivertersCount = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
case 2: DrainValvesCount = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
case 3: RegValvesCount = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
case 4: VirtValvesRangeLo = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
case 5: VirtValvesRangeHi = int.Parse(strValue); return CfgUpdateFlags.RestartRqrd;
default: return CfgUpdateFlags.None;
}
}
public bool ValidateParam(int i, string strValue, out string message)
{
message = string.Empty;
int n;
switch (i)
{
case 0:
if (int.TryParse(strValue, out n) && (n >= 1)) return true;
break;
case 1:
if (int.TryParse(strValue, out n) && (n >= 1) && n <= 5) return true;
break;
case 2:
if (int.TryParse(strValue, out n) && (n >= 0) && n <= 8) return true;
break;
case 3:
if (int.TryParse(strValue, out n) && (n >= 4) && n <= 8) return true;
break;
case 4:
case 5:
if (int.TryParse(strValue, out n) && (n >= 0) && n <= 127) return true;
break;
default:
message = "Invalid index";
return false;
}
message = ParamName(i) + " is invalid";
return false;
}
void CopyContentTo(UniCBCfg prms)
{
prms.ComPortNr = ComPortNr;
prms.DivertersCount = DivertersCount;
prms.DrainValvesCount = DrainValvesCount;
prms.RegValvesCount = RegValvesCount;
prms.VirtValvesRangeLo = VirtValvesRangeLo;
prms.VirtValvesRangeHi = VirtValvesRangeHi;
}
public Config.Entities.IParamsProvider Clone()
{
var pars = new UniCBCfg();
CopyContentTo(pars);
return pars;
}
public bool UpdateEmbeddedDbEntity()
{
return true; /// =OK, do nothing
}
}
}
+55
View File
@@ -0,0 +1,55 @@
///
/// Copyright (c) 2021 Sensus Slovensko a.s.
///
using System;
using System.Diagnostics;
namespace TBF.Rig.ControlBoard.Uni
{
class ValveMoveMsg
{
public int PayloadLen { get { return 38; } }
public byte MessageCode { get { return 0x20; } }
Command cmd;
int regValveNr;
public ValveMoveMsg(Command cmd, int regValveNr)
{
this.cmd = cmd;
this.regValveNr = regValveNr;
}
public byte[] GetMessage()
{
byte[] message = new byte[PayloadLen + 5]; /// headed 3 bytes, checksum 2 bytes
/// Header (3 bytes)
message[0] = Const.STX;
message[1] = 1; /// Control board addres
message[2] = (byte)PayloadLen; /// Payload length
/// Command
message[3] = MessageCode;
message[4] = (byte)cmd;
/// ...
message[40] = 0;
Debug.Assert(40 == PayloadLen + 2);
/// Checksum (2 bytes)
int checksum = 0;
for (int i = 1; i < PayloadLen + 3; i++) checksum += message[i];
message[PayloadLen + 3] = (byte)(checksum & 0xFF);
message[PayloadLen + 4] = (byte)((checksum >> 8) & 0xFF);
return message;
}
public override string ToString()
{
return string.Format("ValveMoveMsg : {0}", cmd);
}
}
}