diff --git a/SchematicDrawing/IDrawingItemWithMeasuredVal.cs b/SchematicDrawing/IDrawingItemWithMeasuredVal.cs
index c9725b24d..5ad98258b 100644
--- a/SchematicDrawing/IDrawingItemWithMeasuredVal.cs
+++ b/SchematicDrawing/IDrawingItemWithMeasuredVal.cs
@@ -13,5 +13,6 @@ namespace SchematicDrawing
Common.Unit MsrdUnit { get; }
double MsrdValLimLo { get; }
double MsrdValLimHi { get; }
+ bool IsOffline { get; set; }
}
}
diff --git a/SchematicDrawing/SchematicDrawingCtrl.cs b/SchematicDrawing/SchematicDrawingCtrl.cs
index 398b43056..4a1a03062 100644
--- a/SchematicDrawing/SchematicDrawingCtrl.cs
+++ b/SchematicDrawing/SchematicDrawingCtrl.cs
@@ -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);
}
diff --git a/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig
new file mode 100644
index 000000000..a53abc4d8
--- /dev/null
+++ b/SharedComponents/obj/Debug/net472/SharedComponents.GeneratedMSBuildEditorConfig.editorconfig
@@ -0,0 +1,6 @@
+is_global = true
+build_property.RootNamespace = SharedComponents
+build_property.ProjectDir = C:\Users\micha\git\tbf\SharedComponents\
+build_property.EnableComHosting =
+build_property.EnableGeneratedComInterfaceComImportInterop =
+build_property.CsWinRTUseWindowsUIXamlProjections = false
\ No newline at end of file
diff --git a/SharedComponents/obj/SharedComponents.csproj.nuget.g.props b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props
new file mode 100644
index 000000000..568c2629e
--- /dev/null
+++ b/SharedComponents/obj/SharedComponents.csproj.nuget.g.props
@@ -0,0 +1,15 @@
+
+
+
+ True
+ NuGet
+ $(MSBuildThisFileDirectory)project.assets.json
+ $(UserProfile)\.nuget\packages\
+ C:\Users\micha\.nuget\packages\
+ PackageReference
+ 7.0.0
+
+
+
+
+
\ No newline at end of file
diff --git a/TBF/Rig/Ambient/Comet/AmbientCfg.cs b/TBF/Rig/Ambient/Comet/AmbientCfg.cs
index 0bb339f1e..119f632c6 100644
--- a/TBF/Rig/Ambient/Comet/AmbientCfg.cs
+++ b/TBF/Rig/Ambient/Comet/AmbientCfg.cs
@@ -73,8 +73,11 @@ namespace TBF.Rig.Ambient.Comet
[XmlIgnore]
public IList 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();
Format = string.Empty;
diff --git a/TBF/Rig/Ambient/Greco/AmbientCfg.cs b/TBF/Rig/Ambient/Greco/AmbientCfg.cs
index cbc8e3eb6..37391827c 100644
--- a/TBF/Rig/Ambient/Greco/AmbientCfg.cs
+++ b/TBF/Rig/Ambient/Greco/AmbientCfg.cs
@@ -72,6 +72,9 @@ namespace TBF.Rig.Ambient.Greco
[XmlIgnore]
public IList GNodes { get; set; }
+ [XmlIgnore]
+ public bool IsOffline { get; set; }
+
/// Private parameterless constructor invoked by all other (public) constructors
AmbientCfg()
{
diff --git a/TBF/Rig/ControlBoard/Uni/UniCBCfg.cs b/TBF/Rig/ControlBoard/Uni/UniCBCfg.cs
index 1dbfb12d8..066bf810e 100644
--- a/TBF/Rig/ControlBoard/Uni/UniCBCfg.cs
+++ b/TBF/Rig/ControlBoard/Uni/UniCBCfg.cs
@@ -66,6 +66,9 @@ namespace TBF.Rig.ControlBoard.Uni
[XmlIgnore]
public IList EdgesToSet { get; set; }
+ [XmlIgnore]
+ public bool IsOffline { get; set; }
+
/// Private parameterless constructor invoked by all other (public) constructors
UniCBCfg()
{
diff --git a/TBF/Rig/Dummy/FlowMeter/FlowMeterCfg.cs b/TBF/Rig/Dummy/FlowMeter/FlowMeterCfg.cs
index 28f498567..629d72527 100644
--- a/TBF/Rig/Dummy/FlowMeter/FlowMeterCfg.cs
+++ b/TBF/Rig/Dummy/FlowMeter/FlowMeterCfg.cs
@@ -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();
}
diff --git a/TBF/Rig/Keithley/TempMeter/TempMeter.cs b/TBF/Rig/Keithley/TempMeter/TempMeter.cs
index f2186e7bd..16160b9b0 100644
--- a/TBF/Rig/Keithley/TempMeter/TempMeter.cs
+++ b/TBF/Rig/Keithley/TempMeter/TempMeter.cs
@@ -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 operations;
Dictionary operations2;
+ public bool IsOffline
+ {
+ get { return tempMtrCfg.IsOffline; }
+ set { tempMtrCfg.IsOffline = value; }
+ }
+
+ int invalidStreak = 0;
+ const int OfflineAfterInvalid = 5;
- public TempMeter() { }
+ public TempMeter() { }
public TempMeter(Generic.IComponentCfg cfg, IList 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;
}
- ///
- /// Events: TempDone, Error
- ///
- /// Reference to a variable for the temperature in Celsius
- /// ReadTempOp instance reference casted to IOperaton
- public IOperation ReadTempOp(ref DoubleBox temp)
+ ///
+ /// Events: TempDone, Error
+ ///
+ /// Reference to a variable for the temperature in Celsius
+ /// ReadTempOp instance reference casted to IOperaton
+ public IOperation ReadTempOp(ref DoubleBox temp)
{
IOperation operation;
if (operations.TryGetValue(temp, out operation))
diff --git a/TBF/Rig/Keithley/TempMeter/TempMeterCfg.cs b/TBF/Rig/Keithley/TempMeter/TempMeterCfg.cs
index 012327ce0..04539c05f 100644
--- a/TBF/Rig/Keithley/TempMeter/TempMeterCfg.cs
+++ b/TBF/Rig/Keithley/TempMeter/TempMeterCfg.cs
@@ -62,8 +62,11 @@ namespace TBF.Rig.Keithley.TempMeter
[XmlIgnore]
public IList 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();
}
diff --git a/TBF/Rig/MettlerToledo/Standard/BalanceCfg.cs b/TBF/Rig/MettlerToledo/Standard/BalanceCfg.cs
index 07d5eec2b..11fda0d79 100644
--- a/TBF/Rig/MettlerToledo/Standard/BalanceCfg.cs
+++ b/TBF/Rig/MettlerToledo/Standard/BalanceCfg.cs
@@ -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();
}
diff --git a/TBF/Rig/Modbus/Ambient/Comet/AmbientCfg.cs b/TBF/Rig/Modbus/Ambient/Comet/AmbientCfg.cs
index 6bdbfa09d..49b9cbc41 100644
--- a/TBF/Rig/Modbus/Ambient/Comet/AmbientCfg.cs
+++ b/TBF/Rig/Modbus/Ambient/Comet/AmbientCfg.cs
@@ -68,8 +68,11 @@ namespace TBF.Rig.Modbus.Ambient.Comet
[XmlIgnore]
public IList 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();
Format = string.Empty;
diff --git a/TBF/Rig/Modbus/Meret/AdjustableScale/AdjustableMeterCfg.cs b/TBF/Rig/Modbus/Meret/AdjustableScale/AdjustableMeterCfg.cs
index 16f8a8504..3cd119224 100644
--- a/TBF/Rig/Modbus/Meret/AdjustableScale/AdjustableMeterCfg.cs
+++ b/TBF/Rig/Modbus/Meret/AdjustableScale/AdjustableMeterCfg.cs
@@ -61,8 +61,11 @@ namespace TBF.Rig.Modbus.Meret.AdjustableScale
[XmlIgnore]
public IList 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();
}
diff --git a/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeter.cs b/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeter.cs
index 045cb5c47..3e6e06f98 100644
--- a/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeter.cs
+++ b/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeter.cs
@@ -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 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 ? "" : 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()
diff --git a/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeterCfg.cs b/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeterCfg.cs
index c7905cde9..04abe150c 100644
--- a/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeterCfg.cs
+++ b/TBF/Rig/Modbus/PressureMeter/Meret/PressureMeterCfg.cs
@@ -57,8 +57,11 @@ namespace TBF.Rig.Modbus.PressureMeter.Meret
[XmlIgnore]
public IList 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();
}
diff --git a/TBF/Rig/Modbus/TempControl/Easytherm/TControlCfg.cs b/TBF/Rig/Modbus/TempControl/Easytherm/TControlCfg.cs
index b86060110..3ad8443f1 100644
--- a/TBF/Rig/Modbus/TempControl/Easytherm/TControlCfg.cs
+++ b/TBF/Rig/Modbus/TempControl/Easytherm/TControlCfg.cs
@@ -65,9 +65,12 @@ namespace TBF.Rig.Modbus.TempControl.Easytherm
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); }
+ [XmlIgnore]
+ public bool IsOffline { get; set; }
- /// Private parameterless constructor invoked by all other (public) constructors
- TControlCfg()
+
+ /// Private parameterless constructor invoked by all other (public) constructors
+ TControlCfg()
{
ProcParams = new ProcParams(true);
GNodes = new List();
diff --git a/TBF/Rig/Modbus/TempControl/Novus/TControlCfg.cs b/TBF/Rig/Modbus/TempControl/Novus/TControlCfg.cs
index a4a317310..3ca7fc087 100644
--- a/TBF/Rig/Modbus/TempControl/Novus/TControlCfg.cs
+++ b/TBF/Rig/Modbus/TempControl/Novus/TControlCfg.cs
@@ -75,9 +75,12 @@ namespace TBF.Rig.Modbus.TempControl.Novus
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); }
+ [XmlIgnore]
+ public bool IsOffline { get; set; }
- /// Private parameterless constructor invoked by all other (public) constructors
- TControlCfg()
+
+ /// Private parameterless constructor invoked by all other (public) constructors
+ TControlCfg()
{
ProcParams = new ProcParams(true);
GNodes = new List();
diff --git a/TBF/Rig/Modbus/TempMeter/Groch/TempMeter.cs b/TBF/Rig/Modbus/TempMeter/Groch/TempMeter.cs
index ec28b1e4c..7a082bebd 100644
--- a/TBF/Rig/Modbus/TempMeter/Groch/TempMeter.cs
+++ b/TBF/Rig/Modbus/TempMeter/Groch/TempMeter.cs
@@ -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
}
}
- ///
- /// Events: TempDone, Error
- ///
- /// Reference to a variable for the temperature in Celsius
- /// ReadTempOp instance reference casted to IOperaton
- 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;
+ }
+
+ ///
+ /// Events: TempDone, Error
+ ///
+ /// Reference to a variable for the temperature in Celsius
+ /// ReadTempOp instance reference casted to IOperaton
+ 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;
+ }
+ }
+ }
+ }
}
diff --git a/TBF/Rig/Modbus/TempMeter/Groch/TempMeterCfg.cs b/TBF/Rig/Modbus/TempMeter/Groch/TempMeterCfg.cs
index 8a120ab7e..a51a7aa7f 100644
--- a/TBF/Rig/Modbus/TempMeter/Groch/TempMeterCfg.cs
+++ b/TBF/Rig/Modbus/TempMeter/Groch/TempMeterCfg.cs
@@ -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 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 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();
}
diff --git a/TBF/Rig/Modbus/TempMeter/Meret/TempMeter.cs b/TBF/Rig/Modbus/TempMeter/Meret/TempMeter.cs
index b27b2983e..cf833e8b4 100644
--- a/TBF/Rig/Modbus/TempMeter/Meret/TempMeter.cs
+++ b/TBF/Rig/Modbus/TempMeter/Meret/TempMeter.cs
@@ -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 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
/// Run this device
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()
diff --git a/TBF/Rig/Modbus/TempMeter/Meret/TempMeterCfg.cs b/TBF/Rig/Modbus/TempMeter/Meret/TempMeterCfg.cs
index bd0ad91e3..e69fe3f67 100644
--- a/TBF/Rig/Modbus/TempMeter/Meret/TempMeterCfg.cs
+++ b/TBF/Rig/Modbus/TempMeter/Meret/TempMeterCfg.cs
@@ -58,8 +58,11 @@ namespace TBF.Rig.Modbus.TempMeter.Meret
[XmlIgnore]
public IList 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();
}
diff --git a/TBF/Rig/Modbus/UltrasoundLevelMeter/LevelMeterCfg.cs b/TBF/Rig/Modbus/UltrasoundLevelMeter/LevelMeterCfg.cs
index 182c4132c..0ecb0c937 100644
--- a/TBF/Rig/Modbus/UltrasoundLevelMeter/LevelMeterCfg.cs
+++ b/TBF/Rig/Modbus/UltrasoundLevelMeter/LevelMeterCfg.cs
@@ -49,8 +49,11 @@ namespace TBF.Rig.Modbus.UltrasoundLevelMeter
[XmlIgnore]
public IList 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();
}
diff --git a/TBF/Rig/Scales/MettlerToledo/ScaleCfg.cs b/TBF/Rig/Scales/MettlerToledo/ScaleCfg.cs
index 58c99a436..c9f57e994 100644
--- a/TBF/Rig/Scales/MettlerToledo/ScaleCfg.cs
+++ b/TBF/Rig/Scales/MettlerToledo/ScaleCfg.cs
@@ -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();
}
diff --git a/TBF/Rig/Uni/FlowMeter/FlowMeterCfg.cs b/TBF/Rig/Uni/FlowMeter/FlowMeterCfg.cs
index 90bcacae6..5d2a25e63 100644
--- a/TBF/Rig/Uni/FlowMeter/FlowMeterCfg.cs
+++ b/TBF/Rig/Uni/FlowMeter/FlowMeterCfg.cs
@@ -116,6 +116,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)
{
diff --git a/TBF/Rig/Uni/FlowMetersInParallel/FlowMeterCfg.cs b/TBF/Rig/Uni/FlowMetersInParallel/FlowMeterCfg.cs
index 3294b9850..b944e8aba 100644
--- a/TBF/Rig/Uni/FlowMetersInParallel/FlowMeterCfg.cs
+++ b/TBF/Rig/Uni/FlowMetersInParallel/FlowMeterCfg.cs
@@ -58,8 +58,11 @@ namespace TBF.Rig.Uni.FlowMetersInParallel
private IEnumerable 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();
}
diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj
index 795381d59..c69b8dfd0 100644
--- a/TBF/TBF.csproj
+++ b/TBF/TBF.csproj
@@ -17,6 +17,7 @@
false
+ 8
D:\_Sensus\Temp\Bin\
true
Disk
@@ -39,7 +40,7 @@
full
false
bin\Debug\
- TRACE;DEBUG;CAMERA;LANG_PL;IPERL
+ TRACE;DEBUG;CAMERA;LANG_PL;IPERL;
prompt
4
true
@@ -50,7 +51,7 @@
pdbonly
true
bin\Release\
- TRACE;CAMERA;LANG_PL;IPERL
+ TRACE;CAMERA;LANG_PL;IPERL;
prompt
4
AnyCPU
@@ -60,7 +61,7 @@
true
bin\Debug\
- TRACE;DEBUG;CAMERA; HEAT_METERS; DIAG_LOG_MASS1;LANG_PL;IPERL
+ TRACE;DEBUG;CAMERA;LANG_PL;IPERL;
full
AnyCPU
prompt
@@ -70,7 +71,7 @@
bin\x86\Release\
- TRACE;CAMERA; HEAT_METERS;DIAG_LOG_PUMP2;LANG_PL;IPERL;
+ TRACE;CAMERA;LANG_PL;IPERL;
true
pdbonly
AnyCPU
@@ -630,6 +631,7 @@
+
@@ -1231,6 +1233,10 @@
+
+ Component
+
+
diff --git a/TBFTests/obj/Debug/TBFTests.csproj.AssemblyReference.cache b/TBFTests/obj/Debug/TBFTests.csproj.AssemblyReference.cache
index 475082876..5be513db5 100644
Binary files a/TBFTests/obj/Debug/TBFTests.csproj.AssemblyReference.cache and b/TBFTests/obj/Debug/TBFTests.csproj.AssemblyReference.cache differ