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:
Marek Frniak 2026-02-25 09:47:41 +01:00
parent 8fffb9e366
commit 0c41c750e8
25 changed files with 530 additions and 133 deletions

View File

@ -13,5 +13,6 @@ namespace SchematicDrawing
Common.Unit MsrdUnit { get; }
double MsrdValLimLo { get; }
double MsrdValLimHi { get; }
bool IsOffline { get; set; }
}
}

View File

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

View File

@ -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>

View File

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

View File

@ -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()
{

View File

@ -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()
{

View File

@ -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>();
}

View File

@ -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))

View File

@ -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>();
}

View File

@ -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>();
}

View File

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

View File

@ -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>();
}

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>

View File

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

View File

@ -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>();
}

View File

@ -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>();
}

View File

@ -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)
{

View File

@ -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>();
}

View File

@ -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" />