Add parameter and management IsOffline for components: Modbus>TempMeter>Groch, Meret and Modbus>PressureMeter>Meret and Rig>Keithley>TempMeter and add indicator redColor itemValue for SchematicDrawing
This commit is contained in:
parent
8fffb9e366
commit
0c41c750e8
@ -13,5 +13,6 @@ namespace SchematicDrawing
|
||||
Common.Unit MsrdUnit { get; }
|
||||
double MsrdValLimLo { get; }
|
||||
double MsrdValLimHi { get; }
|
||||
bool IsOffline { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,7 +672,11 @@ namespace SchematicDrawing
|
||||
SizeF size = e.Graphics.MeasureString(strVal, measuredValueFont);
|
||||
Rectangle rect = new Rectangle(itm.X + itm.MsrdX, itm.Y + itm.MsrdY, (int)Math.Round(size.Width) + 4, (int)Math.Round(size.Height) + 2);
|
||||
ItemElementRectangles.Add(new ItemElementRect(item, Element.MeasuredVal, rect));
|
||||
if (measuredValues[msrdValIx] >= itm.MsrdValLimLo && measuredValues[msrdValIx] <= itm.MsrdValLimHi)
|
||||
if ((item as IDrawingItemWithMeasuredVal).IsOffline)
|
||||
{
|
||||
e.Graphics.FillRectangle(measuredValueErrorBackColor, rect);
|
||||
}
|
||||
else if (measuredValues[msrdValIx] >= itm.MsrdValLimLo && measuredValues[msrdValIx] <= itm.MsrdValLimHi)
|
||||
{
|
||||
e.Graphics.FillRectangle(measuredValueBackColor, rect);
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\micha\.nuget\packages\</NuGetPackageFolders>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\MFRNIAK\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\micha\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Users\MFRNIAK\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -73,8 +73,11 @@ namespace TBF.Rig.Ambient.Comet
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AmbientCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AmbientCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
Format = string.Empty;
|
||||
|
||||
@ -72,6 +72,9 @@ namespace TBF.Rig.Ambient.Greco
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AmbientCfg()
|
||||
{
|
||||
|
||||
@ -66,6 +66,9 @@ namespace TBF.Rig.ControlBoard.Uni
|
||||
[XmlIgnore]
|
||||
public IList<GEdge> EdgesToSet { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
UniCBCfg()
|
||||
{
|
||||
|
||||
@ -54,9 +54,12 @@ namespace TBF.Rig.Dummy.FlowMeter
|
||||
double msrdValLimLo = 0;
|
||||
double msrdValLimHi;
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
FlowMeterCfg()
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
FlowMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
///
|
||||
using Common;
|
||||
using log4net;
|
||||
using SchematicDrawing;
|
||||
///
|
||||
/// Copyright (c) 2016-2020 Sensus Metering Systems
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Common;
|
||||
using log4net;
|
||||
using TBF.Boxes;
|
||||
using TBF.Rig.Keithley.Multimeter_2010_RS232;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.Keithley.Multimeter_2010_RS232;
|
||||
|
||||
namespace TBF.Rig.Keithley.TempMeter
|
||||
{
|
||||
public class TempMeter : ComponentBase, GenericDevices.ITempMeter, GenericDevices.IHasCalendarEvents, SchematicDrawing.IDrawingItCmpnt
|
||||
{
|
||||
public class TempMeter : ComponentBase, GenericDevices.ITempMeter, GenericDevices.IHasCalendarEvents, IDrawingItCmpntWithMeasuredVal
|
||||
{
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(TempMeter));
|
||||
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
||||
|
||||
@ -36,9 +37,17 @@ namespace TBF.Rig.Keithley.TempMeter
|
||||
/// Dictionaries to maintain separate operations for each FloatBox argument
|
||||
Dictionary<DoubleBox, IOperation> operations;
|
||||
Dictionary<DoubleBox, IOperation> operations2;
|
||||
public bool IsOffline
|
||||
{
|
||||
get { return tempMtrCfg.IsOffline; }
|
||||
set { tempMtrCfg.IsOffline = value; }
|
||||
}
|
||||
|
||||
int invalidStreak = 0;
|
||||
const int OfflineAfterInvalid = 30;
|
||||
|
||||
|
||||
public TempMeter() { }
|
||||
public TempMeter() { }
|
||||
|
||||
public TempMeter(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
|
||||
: base(cfg)
|
||||
@ -93,39 +102,66 @@ namespace TBF.Rig.Keithley.TempMeter
|
||||
|
||||
public double ReadTemperature()
|
||||
{
|
||||
double resistance = Multimeter.ReadResistance(tempMtrCfg.Channel);
|
||||
// In non-Normal modes do not perform offline detection here
|
||||
if (Cfg.DebugLevel != DebugMode.Normal)
|
||||
{
|
||||
if (Cfg.DebugLevel == DebugMode.Simulate)
|
||||
return 20.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
double temperature = 0;
|
||||
if (resistance > 0 && resistance < 2000)
|
||||
{
|
||||
if (tempMtrCfg.UseITS90)
|
||||
{
|
||||
temperature = Formulas.PlatinumResistanceTM_ITS90_R2T(resistance,
|
||||
tempMtrCfg.R001C,
|
||||
tempMtrCfg.a7,
|
||||
tempMtrCfg.b7,
|
||||
tempMtrCfg.c7);
|
||||
}
|
||||
else
|
||||
{
|
||||
temperature = Formulas.PlatinumResistanceTM_ITS27_R2T(resistance,
|
||||
tempMtrCfg.R0,
|
||||
tempMtrCfg.A,
|
||||
tempMtrCfg.B);
|
||||
}
|
||||
double resistance;
|
||||
|
||||
log.DebugFormat("Ch = {0}, R = {1} Ohm, T = {2} °C", Channel, resistance.ToString("F3"), temperature.ToString("F3"));
|
||||
}
|
||||
try
|
||||
{
|
||||
resistance = Multimeter.ReadResistance(tempMtrCfg.Channel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
invalidStreak++;
|
||||
|
||||
return temperature;
|
||||
if (invalidStreak >= OfflineAfterInvalid)
|
||||
IsOffline = true;
|
||||
|
||||
log.WarnFormat("Keithley TempMeter {0} ch={1}: ReadResistance failed ({2}/{3}): {4}",
|
||||
Name, Channel, invalidStreak, OfflineAfterInvalid, ex.Message);
|
||||
|
||||
return 0; // or keep last value, or double.NaN
|
||||
}
|
||||
|
||||
// Valid resistance => online
|
||||
invalidStreak = 0;
|
||||
IsOffline = false;
|
||||
|
||||
double temperature;
|
||||
if (tempMtrCfg.UseITS90)
|
||||
{
|
||||
temperature = Formulas.PlatinumResistanceTM_ITS90_R2T(resistance,
|
||||
tempMtrCfg.R001C,
|
||||
tempMtrCfg.a7,
|
||||
tempMtrCfg.b7,
|
||||
tempMtrCfg.c7);
|
||||
}
|
||||
else
|
||||
{
|
||||
temperature = Formulas.PlatinumResistanceTM_ITS27_R2T(resistance,
|
||||
tempMtrCfg.R0,
|
||||
tempMtrCfg.A,
|
||||
tempMtrCfg.B);
|
||||
}
|
||||
|
||||
log.DebugFormat("Ch = {0}, R = {1} Ohm, T = {2} °C",
|
||||
Channel, resistance.ToString("F3"), temperature.ToString("F3"));
|
||||
|
||||
return temperature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events: TempDone, Error
|
||||
/// </summary>
|
||||
/// <param name="temp">Reference to a variable for the temperature in Celsius</param>
|
||||
/// <returns>ReadTempOp instance reference casted to IOperaton</returns>
|
||||
public IOperation ReadTempOp(ref DoubleBox temp)
|
||||
/// <summary>
|
||||
/// Events: TempDone, Error
|
||||
/// </summary>
|
||||
/// <param name="temp">Reference to a variable for the temperature in Celsius</param>
|
||||
/// <returns>ReadTempOp instance reference casted to IOperaton</returns>
|
||||
public IOperation ReadTempOp(ref DoubleBox temp)
|
||||
{
|
||||
IOperation operation;
|
||||
if (operations.TryGetValue(temp, out operation))
|
||||
|
||||
@ -62,8 +62,11 @@ namespace TBF.Rig.Keithley.TempMeter
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -81,9 +81,12 @@ namespace TBF.Rig.MettlerToledo.Standard
|
||||
[XmlIgnore]
|
||||
public double MsrdValLimHi { get { return Capacity; } set { } }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
BalanceCfg()
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
BalanceCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -68,8 +68,11 @@ namespace TBF.Rig.Modbus.Ambient.Comet
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AmbientCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AmbientCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
Format = string.Empty;
|
||||
|
||||
@ -61,8 +61,11 @@ namespace TBF.Rig.Modbus.Meret.AdjustableScale
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AdjustableMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
AdjustableMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
///
|
||||
using AppDiagnostic;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using log4net;
|
||||
using SchematicDrawing;
|
||||
using SharedComponents;
|
||||
///
|
||||
/// Copyright (c) 2016-2020 Sensus Slovensko a.s.
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using log4net;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using SchematicDrawing;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Boxes;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Rig.GenericDevices;
|
||||
|
||||
namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
@ -35,9 +37,17 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
public string AltString { get { return string.Empty; } }
|
||||
|
||||
int ticketNumber; /// 0 .. number of devices registered for regular polling - 1
|
||||
public bool IsOffline
|
||||
{
|
||||
get { return pressureMtrCfg.IsOffline; }
|
||||
set { pressureMtrCfg.IsOffline = value; }
|
||||
}
|
||||
|
||||
int silentCycles = 0;
|
||||
const int OfflineAfterSilentCycles = 30;
|
||||
|
||||
|
||||
public PressureMeter() { }
|
||||
public PressureMeter() { }
|
||||
|
||||
public PressureMeter(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
|
||||
: base(cfg)
|
||||
@ -59,6 +69,9 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
modbus.ComponentNames[pressureMtrCfg.ModbusAddress] = Name;
|
||||
ticketNumber = modbus.RegisterForPolling();
|
||||
log.FatalFormat("{0} initialized: {1}", Name, this);
|
||||
|
||||
IsOffline = true;
|
||||
silentCycles = OfflineAfterSilentCycles;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -68,25 +81,85 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
|
||||
public void RunDeviceBefore()
|
||||
{
|
||||
if (DebugLevel == DebugMode.Normal && modbus.ReceivedTelegrams[pressureMtrCfg.ModbusAddress].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[pressureMtrCfg.ModbusAddress].Dequeue();
|
||||
if (telegram.Length == 9 && telegram[1] == 4 && telegram[2] == 4)
|
||||
{
|
||||
/// Swap byte order
|
||||
byte t1 = telegram[3];
|
||||
byte t2 = telegram[4];
|
||||
byte t3 = telegram[5];
|
||||
byte t4 = telegram[6];
|
||||
telegram[3] = t4;
|
||||
telegram[4] = t3;
|
||||
telegram[5] = t2;
|
||||
telegram[6] = t1;
|
||||
if (DebugLevel != DebugMode.Normal)
|
||||
return;
|
||||
|
||||
receivedPressure = Units.ConvertFrom(Unit.kPa, System.BitConverter.ToSingle(telegram, 3));
|
||||
bool gotValidResponse = false;
|
||||
|
||||
if (DebugLevel == DebugMode.Normal && modbus.ReceivedTelegrams[pressureMtrCfg.ModbusAddress].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[pressureMtrCfg.ModbusAddress].Dequeue();
|
||||
if (telegram.Length == 9 && telegram[1] == 4 && telegram[2] == 4)
|
||||
{
|
||||
/// Swap byte order
|
||||
byte t1 = telegram[3];
|
||||
byte t2 = telegram[4];
|
||||
byte t3 = telegram[5];
|
||||
byte t4 = telegram[6];
|
||||
telegram[3] = t4;
|
||||
telegram[4] = t3;
|
||||
telegram[5] = t2;
|
||||
telegram[6] = t1;
|
||||
|
||||
receivedPressure = Units.ConvertFrom(Unit.kPa, System.BitConverter.ToSingle(telegram, 3));
|
||||
log.WarnFormat("Pressure: {0}={1}bar", Name, receivedPressure.ToString("F3"));
|
||||
|
||||
gotValidResponse = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string telHex = telegram == null ? "<null>" : BitConverter.ToString(telegram);
|
||||
|
||||
string reason;
|
||||
if (telegram == null) reason = "null telegram";
|
||||
else if (telegram.Length != 9) reason = $"unexpected length (expected 9, got {telegram.Length})";
|
||||
else if (telegram.Length > 1 && telegram[1] != 4) reason = $"unexpected function (expected 0x04, got 0x{telegram[1]:X2})";
|
||||
else if (telegram.Length > 2 && telegram[2] != 4) reason = $"unexpected bytecount (expected 0x04, got 0x{telegram[2]:X2})";
|
||||
else reason = "unknown mismatch";
|
||||
|
||||
//LiveLogCache.Instance.AddLog(string.Format(
|
||||
// ">> RunDeviceBefore() >> component: {0} bad received telegram: {1} data={2}",
|
||||
// Name,
|
||||
// reason,
|
||||
// telHex));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool debugOk = (DebugLevel == DebugMode.Normal);
|
||||
int addr = pressureMtrCfg.ModbusAddress;
|
||||
|
||||
try
|
||||
{
|
||||
var q = modbus.ReceivedTelegrams[addr];
|
||||
int qCount = (q == null) ? -1 : q.Count;
|
||||
|
||||
//LiveLogCache.Instance.AddLog(string.Format(
|
||||
// ">> RunDeviceBefore() >> component: {0} condition failed | DebugOk={1} | Addr={2} | QueueNull={3} | QueueCount={4} | silentCycles={5}",
|
||||
// Name, debugOk, addr, (q == null), qCount, silentCycles));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//LiveLogCache.Instance.AddLog(string.Format(
|
||||
// ">> RunDeviceBefore() >> component: {0} condition failed | DebugOk={1} | Addr={2} | silentCycles={3} | EXCEPTION={4}",
|
||||
// Name, debugOk, addr, silentCycles, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
if (gotValidResponse)
|
||||
{
|
||||
silentCycles = 0;
|
||||
IsOffline = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
silentCycles++;
|
||||
if (silentCycles >= OfflineAfterSilentCycles)
|
||||
{
|
||||
IsOffline = true;
|
||||
//LiveLogCache.Instance.AddLog(string.Format(">> RunDeviceBefore() >> component: {0} IsOffline", Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RunDeviceAfter()
|
||||
|
||||
@ -57,8 +57,11 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
PressureMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
PressureMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
@ -93,7 +96,7 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
|
||||
"Modbus address", /// 0
|
||||
"Default pressure", /// 1
|
||||
"Display format", /// 2
|
||||
"Unit of pressure", /// 3
|
||||
"Units of pressure", /// 3
|
||||
"Pressure limit Lo", /// 4
|
||||
"Pressure limit Hi", /// 5
|
||||
};
|
||||
|
||||
@ -59,8 +59,11 @@ namespace TBF.Rig.Modbus.TempControl.Easytherm
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
[XmlIgnore]
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
[XmlIgnore]
|
||||
public ProcParams ProcParams;
|
||||
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
|
||||
public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); }
|
||||
|
||||
@ -68,9 +68,12 @@ namespace TBF.Rig.Modbus.TempControl.Novus
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
[XmlIgnore]
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
[XmlIgnore]
|
||||
public ProcParams ProcParams;
|
||||
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
|
||||
public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); }
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
///
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using log4net;
|
||||
///
|
||||
/// Copyright (c) 2021 Sensus Slovensko a.s.
|
||||
///
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using log4net;
|
||||
using Common;
|
||||
using Config.Entities;
|
||||
using TBF.Rig.Generic;
|
||||
using System.Xml.Serialization;
|
||||
using TBF.Boxes;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.Generic;
|
||||
|
||||
namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
{
|
||||
@ -46,6 +47,24 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
|
||||
int ticketNumber; /// 0 .. number of devices registered for regular polling - 1
|
||||
|
||||
//[XmlIgnore]
|
||||
public bool IsOffline
|
||||
{
|
||||
get { return tempMtrCfg.IsOffline; }
|
||||
set { tempMtrCfg.IsOffline = value; }
|
||||
}
|
||||
|
||||
// Shared device/channel health tracking (updated by Channel 0)
|
||||
static int grochSilentCycles = 0;
|
||||
static bool grochOffline = true;
|
||||
|
||||
// Per-channel consecutive invalid-value counters
|
||||
static int[] invalidStreak = new int[TempMeterCfg.ChannelsCount];
|
||||
|
||||
// Thresholds
|
||||
const int GrochOfflineAfterCycles = 30; // No valid telegram N cycles => Groch offline
|
||||
const int ChannelOfflineAfterInvalid = 30; // Invalid value N times => channel offline
|
||||
|
||||
|
||||
public TempMeter() { }
|
||||
|
||||
@ -57,9 +76,17 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
// Ensure shared static health tracking is initialized only once
|
||||
EnsureSharedInitialized();
|
||||
|
||||
if (DebugLevel == DebugMode.Normal)
|
||||
{
|
||||
/// Only TempMeter with Channel == 0 communicates via the parent Modbus component
|
||||
// Each instance starts from shared states (will become online automatically after valid data)
|
||||
bool channelOffline = invalidStreak[Channel] >= ChannelOfflineAfterInvalid;
|
||||
IsOffline = grochOffline || channelOffline;
|
||||
|
||||
// Only TempMeter with Channel == 0 communicates via the parent Modbus component
|
||||
if (Channel == 0)
|
||||
{
|
||||
modbus = TbfComponents.FindComponent(tempMtrCfg.ParentName) as Common.Modbus;
|
||||
@ -68,10 +95,13 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
modbus.ComponentNames[0] = Name;
|
||||
ticketNumber = modbus.RegisterForPolling();
|
||||
}
|
||||
|
||||
log.FatalFormat("{0} initialized: {1}", Name, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In simulation you can decide whether to keep IsOffline false
|
||||
IsOffline = false;
|
||||
log.FatalFormat("{0} simulated: {1}", Name, this);
|
||||
}
|
||||
}
|
||||
@ -110,10 +140,11 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
|
||||
public void RunDeviceBefore()
|
||||
{
|
||||
if (DebugLevel == DebugMode.Normal && Channel == 0)
|
||||
/*if (DebugLevel == DebugMode.Normal && Channel == 0)
|
||||
{
|
||||
/// Only TempMeter with Channel == 0 communicates via the parent Modbus component
|
||||
///
|
||||
|
||||
if (modbus.ReceivedTelegrams[0].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[0].Dequeue();
|
||||
@ -147,7 +178,20 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if (DebugLevel != DebugMode.Normal)
|
||||
return;
|
||||
|
||||
if (Channel == 0)
|
||||
{
|
||||
// update shared state first (process telegrams)
|
||||
UpdateSharedStateFromGroch();
|
||||
}
|
||||
|
||||
// now every instance (including ch0) computes its offline state from fresh shared values
|
||||
bool channelOffline = invalidStreak[Channel] >= ChannelOfflineAfterInvalid;
|
||||
IsOffline = grochOffline || channelOffline;
|
||||
}
|
||||
|
||||
public void RunDeviceAfter()
|
||||
@ -182,12 +226,21 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events: TempDone, Error
|
||||
/// </summary>
|
||||
/// <param name="temp">Reference to a variable for the temperature in Celsius</param>
|
||||
/// <returns>ReadTempOp instance reference casted to IOperaton</returns>
|
||||
public IOperation ReadTempOp(ref DoubleBox temp)
|
||||
// Decide whether the raw value looks valid.
|
||||
// Adjust this to match real device behavior.
|
||||
static bool IsValidRaw(int raw)
|
||||
{
|
||||
if (raw == 0) return false;
|
||||
if (raw == 0xFFFF) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events: TempDone, Error
|
||||
/// </summary>
|
||||
/// <param name="temp">Reference to a variable for the temperature in Celsius</param>
|
||||
/// <returns>ReadTempOp instance reference casted to IOperaton</returns>
|
||||
public IOperation ReadTempOp(ref DoubleBox temp)
|
||||
{
|
||||
return new ReadTempOp(this, ref temp);
|
||||
}
|
||||
@ -202,5 +255,137 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
{
|
||||
return new ReadTempOp(this, ref temp, tempDone);
|
||||
}
|
||||
}
|
||||
|
||||
// Add to class TempMeter
|
||||
static readonly object initLock = new object();
|
||||
static bool sharedInitialized = false;
|
||||
|
||||
static void EnsureSharedInitialized()
|
||||
{
|
||||
if (sharedInitialized) return;
|
||||
|
||||
lock (initLock)
|
||||
{
|
||||
if (sharedInitialized) return;
|
||||
|
||||
// Start as offline until we receive the first valid telegram
|
||||
grochSilentCycles = GrochOfflineAfterCycles;
|
||||
grochOffline = true;
|
||||
|
||||
for (int i = 0; i < TempMeterCfg.ChannelsCount; i++)
|
||||
{
|
||||
invalidStreak[i] = ChannelOfflineAfterInvalid; // start channels as offline until first valid value
|
||||
tempRaw[i] = 0; // optional: reset raw buffer
|
||||
}
|
||||
|
||||
sharedInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSharedStateFromGroch()
|
||||
{
|
||||
bool gotValidTelegram = false;
|
||||
|
||||
// Process all queued telegrams to avoid backlog accumulation
|
||||
while (modbus.ReceivedTelegrams[0].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[0].Dequeue();
|
||||
|
||||
if (telegram.Length != 28)
|
||||
continue;
|
||||
|
||||
byte checksum = 0;
|
||||
for (int i = 0; i < 27; i++)
|
||||
checksum += telegram[i];
|
||||
|
||||
bool isValidGroch =
|
||||
telegram[0] == '\r' &&
|
||||
telegram[1] == '\n' &&
|
||||
telegram[2] == GrochAddress &&
|
||||
telegram[3] == 28 &&
|
||||
telegram[5] == 2 &&
|
||||
telegram[26] == 3 &&
|
||||
telegram[27] == checksum;
|
||||
|
||||
if (!isValidGroch)
|
||||
continue;
|
||||
|
||||
// At least one valid telegram received this cycle
|
||||
gotValidTelegram = true;
|
||||
|
||||
// Extract raw channel values
|
||||
if (TempMeterCfg.ChannelsCount > 0)
|
||||
{
|
||||
int raw = telegram[10] + 256 * telegram[11];
|
||||
tempRaw[0] = raw;
|
||||
invalidStreak[0] = IsValidRaw(raw) ? 0 : invalidStreak[0] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 1)
|
||||
{
|
||||
int raw = telegram[12] + 256 * telegram[13];
|
||||
tempRaw[1] = raw;
|
||||
invalidStreak[1] = IsValidRaw(raw) ? 0 : invalidStreak[1] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 2)
|
||||
{
|
||||
int raw = telegram[14] + 256 * telegram[15];
|
||||
tempRaw[2] = raw;
|
||||
invalidStreak[2] = IsValidRaw(raw) ? 0 : invalidStreak[2] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 3)
|
||||
{
|
||||
int raw = telegram[16] + 256 * telegram[17];
|
||||
tempRaw[3] = raw;
|
||||
invalidStreak[3] = IsValidRaw(raw) ? 0 : invalidStreak[3] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 4)
|
||||
{
|
||||
int raw = telegram[18] + 256 * telegram[19];
|
||||
tempRaw[4] = raw;
|
||||
invalidStreak[4] = IsValidRaw(raw) ? 0 : invalidStreak[4] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 5)
|
||||
{
|
||||
int raw = telegram[20] + 256 * telegram[21];
|
||||
tempRaw[5] = raw;
|
||||
invalidStreak[5] = IsValidRaw(raw) ? 0 : invalidStreak[5] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 6)
|
||||
{
|
||||
int raw = telegram[22] + 256 * telegram[23];
|
||||
tempRaw[6] = raw;
|
||||
invalidStreak[6] = IsValidRaw(raw) ? 0 : invalidStreak[6] + 1;
|
||||
}
|
||||
|
||||
if (TempMeterCfg.ChannelsCount > 7)
|
||||
{
|
||||
int raw = telegram[24] + 256 * telegram[25];
|
||||
tempRaw[7] = raw;
|
||||
invalidStreak[7] = IsValidRaw(raw) ? 0 : invalidStreak[7] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Device-level offline detection
|
||||
if (gotValidTelegram)
|
||||
{
|
||||
grochSilentCycles = 0;
|
||||
grochOffline = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
grochSilentCycles++;
|
||||
|
||||
if (grochSilentCycles >= GrochOfflineAfterCycles)
|
||||
{
|
||||
grochOffline = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,9 +14,9 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
{
|
||||
public class TempMeterCfg : ComponentCfgBase, IChildComponentCfg, GenericDevices.ICalibInfoCfg, IParamsProvider,
|
||||
IDrawingItemWithMeasuredVal
|
||||
{
|
||||
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(TempMeterCfg) })[0];
|
||||
public override XmlSerializer GetSerializer() { return Serializer; }
|
||||
{
|
||||
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(TempMeterCfg) })[0];
|
||||
public override XmlSerializer GetSerializer() { return Serializer; }
|
||||
|
||||
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities)
|
||||
{
|
||||
@ -26,9 +26,9 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
|
||||
public const int ChannelsCount = 8;
|
||||
|
||||
///
|
||||
/// Serialized parameters
|
||||
///
|
||||
///
|
||||
/// Serialized parameters
|
||||
///
|
||||
public int Address; /// 0 0 .. 255
|
||||
public int Channel; /// 1 0 .. 7 (7 = TempMeterCfg.ChannelsCount-1)
|
||||
public double A1; /// 2
|
||||
@ -65,8 +65,11 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
@ -113,7 +116,7 @@ namespace TBF.Rig.Modbus.TempMeter.Groch
|
||||
"A5", /// 6
|
||||
"Default temperature", /// 7
|
||||
"Display format", /// 8
|
||||
"Unit of temperature", /// 9
|
||||
"Units of temperature", /// 9
|
||||
"Temperature limit Lo", /// 10
|
||||
"Temperature limit Hi", /// 11
|
||||
};
|
||||
|
||||
@ -33,9 +33,17 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
public string AltString { get { return string.Empty; } }
|
||||
|
||||
int ticketNumber; /// 0 .. number of devices registered for regular polling - 1
|
||||
public bool IsOffline
|
||||
{
|
||||
get { return tempMtrCfg.IsOffline; }
|
||||
set { tempMtrCfg.IsOffline = value; }
|
||||
}
|
||||
|
||||
int silentCycles = 0;
|
||||
const int OfflineAfterSilentCycles = 30; // e.g. 5 cycles without valid response
|
||||
|
||||
|
||||
public TempMeter() { }
|
||||
public TempMeter() { }
|
||||
|
||||
public TempMeter(Generic.IComponentCfg cfg, IList<Generic.IComponent> components)
|
||||
: base(cfg)
|
||||
@ -50,6 +58,11 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
|
||||
modbus.ComponentNames[tempMtrCfg.ModbusAddress] = Name;
|
||||
ticketNumber = modbus.RegisterForPolling();
|
||||
|
||||
// Start offline until first valid response arrives (optional but consistent)
|
||||
IsOffline = true;
|
||||
silentCycles = OfflineAfterSilentCycles;
|
||||
|
||||
log.FatalFormat("{0} initialized: {1}", Name, this);
|
||||
}
|
||||
|
||||
@ -59,25 +72,45 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
/// <summary>Run this device</summary>
|
||||
public void RunDeviceBefore()
|
||||
{
|
||||
if (modbus.ReceivedTelegrams[tempMtrCfg.ModbusAddress].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[tempMtrCfg.ModbusAddress].Dequeue();
|
||||
if (telegram.Length == 9 && telegram[1] == 4 && telegram[2] == 4)
|
||||
{
|
||||
/// Swap byte order
|
||||
byte t1 = telegram[3];
|
||||
byte t2 = telegram[4];
|
||||
byte t3 = telegram[5];
|
||||
byte t4 = telegram[6];
|
||||
telegram[3] = t4;
|
||||
telegram[4] = t3;
|
||||
telegram[5] = t2;
|
||||
telegram[6] = t1;
|
||||
if (Cfg.DebugLevel != DebugMode.Normal)
|
||||
return;
|
||||
|
||||
receivedTemp = System.BitConverter.ToSingle(telegram, 3);
|
||||
log.WarnFormat("Temperature: {0}={1}°C", Name, receivedTemp.ToString("F1"));
|
||||
}
|
||||
}
|
||||
bool gotValidResponse = false;
|
||||
|
||||
while (modbus.ReceivedTelegrams[tempMtrCfg.ModbusAddress].Count > 0)
|
||||
{
|
||||
byte[] telegram = modbus.ReceivedTelegrams[tempMtrCfg.ModbusAddress].Dequeue();
|
||||
|
||||
// Presence = valid response frame
|
||||
if (telegram.Length == 9 && telegram[1] == 4 && telegram[2] == 4)
|
||||
{
|
||||
gotValidResponse = true;
|
||||
|
||||
// Optional: still parse temperature for display/use (does not affect presence)
|
||||
byte t1 = telegram[3];
|
||||
byte t2 = telegram[4];
|
||||
byte t3 = telegram[5];
|
||||
byte t4 = telegram[6];
|
||||
telegram[3] = t4;
|
||||
telegram[4] = t3;
|
||||
telegram[5] = t2;
|
||||
telegram[6] = t1;
|
||||
|
||||
receivedTemp = BitConverter.ToSingle(telegram, 3);
|
||||
}
|
||||
}
|
||||
|
||||
if (gotValidResponse)
|
||||
{
|
||||
silentCycles = 0;
|
||||
IsOffline = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
silentCycles++;
|
||||
if (silentCycles >= OfflineAfterSilentCycles)
|
||||
IsOffline = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RunDeviceAfter()
|
||||
@ -116,6 +149,17 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsValidTemperature(float t)
|
||||
{
|
||||
// Reject NaN/Infinity
|
||||
if (float.IsNaN(t) || float.IsInfinity(t)) return false;
|
||||
|
||||
// Put a reasonable physical range here (adjust to your real use case)
|
||||
if (t < -50.0f || t > 200.0f) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events: PressureDone, Error
|
||||
/// </summary>
|
||||
|
||||
@ -58,8 +58,11 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TempMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
@ -94,7 +97,7 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
|
||||
"Modbus address", /// 0
|
||||
"Default temperature", /// 1
|
||||
"Display format", /// 2
|
||||
"Unit of temperature", /// 3
|
||||
"Units of temperature", /// 3
|
||||
"Temperature limit Lo", /// 4
|
||||
"Temperature limit Hi", /// 5
|
||||
};
|
||||
|
||||
@ -49,8 +49,11 @@ namespace TBF.Rig.Modbus.UltrasoundLevelMeter
|
||||
[XmlIgnore]
|
||||
public IList<GNode> GNodes { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
LevelMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
LevelMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -91,9 +91,12 @@ namespace TBF.Rig.Scales.MettlerToledo
|
||||
[XmlIgnore]
|
||||
public double MsrdValLimHi { get { return Capacity; } set { } }
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
ScaleCfg()
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
ScaleCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -117,6 +117,9 @@ namespace TBF.Rig.Uni.FlowMeter
|
||||
double msrdValLimLo = 0; /// in [m3/h]
|
||||
double msrdValLimHi; /// in [m3/h]
|
||||
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
|
||||
public bool RangeEnabled(int rangeIx1)
|
||||
{
|
||||
|
||||
@ -58,8 +58,11 @@ namespace TBF.Rig.Uni.FlowMetersInParallel
|
||||
|
||||
private IEnumerable<Config.Entities.Component> flowMeters;
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
FlowMeterCfg()
|
||||
[XmlIgnore]
|
||||
public bool IsOffline { get; set; }
|
||||
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
FlowMeterCfg()
|
||||
{
|
||||
GNodes = new List<GNode>();
|
||||
}
|
||||
|
||||
@ -1456,7 +1456,9 @@
|
||||
<Compile Include="Rig\Scales\MettlerToledo\Factory.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\GetSerNumOp.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\ReadStableMassOp.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\SerialPortDevice.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\SerialPortDevice.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Scales\MettlerToledo\SerialPortDeviceFake.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\SetUnitsOp.cs" />
|
||||
<Compile Include="Rig\Scales\MettlerToledo\TaringOp.cs" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user