tbf/TBF/Rig/Modbus/WaterAnalyzerUni/Analyzer.cs

359 lines
11 KiB
C#

using Common;
using log4net;
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using TBF.Boxes;
using TBF.Rig.Generic;
using TBF.Rig.Modbus.WaterAnalyzerUni;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public class Analyzer : ComponentBase, IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(Analyzer));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
readonly AnalyzerCfg myCfg;
Common.Modbus modbus;
float conductivity; // [uS/cm]
double temperature; // [°C]
int msrmntTimeStamp;
int ticketNumber;
ushort[] rawRegs;
DateTime lastUpdate;
byte[] lastTelegram;
public float Conductivity { get { return conductivity; } }
public AnalyzerDiagnostics Diagnostics { get; private set; }
public Analyzer()
{
Diagnostics = new AnalyzerDiagnostics();
}
public Analyzer(IComponentCfg cfg, IList<IComponent> components)
: base(cfg)
{
myCfg = cfg as AnalyzerCfg;
Diagnostics = new AnalyzerDiagnostics();
}
public override void Initialize()
{
modbus = TbfComponents.FindComponent(myCfg.ParentName) as Common.Modbus;
if (modbus == null) throw new Exception("Cannot find " + Name + " parent");
conductivity = 0.0F;
temperature = 0.0;
msrmntTimeStamp = 0;
ticketNumber = -1;
rawRegs = new ushort[Math.Max(1, (int)myCfg.ConverterRegisterCount)];
modbus.ComponentNames[myCfg.ModbusAddress] = Name;
ticketNumber = modbus.RegisterForPolling();
log.FatalFormat("{0} initialized: {1}", Name, this);
}
public float ReadConductivity()
{
return conductivity;
}
public double ReadTemperature()
{
return temperature;
}
public ReadConductivityOp ReadConductivityOp(ref FloatBox conduct)
{
return new ReadConductivityOp(this, ref conduct);
}
public IOperation ReadTempOp(ref DoubleBox temperature)
{
return new ReadTempOp(this, ref temperature);
}
public IOperation ReadTempOp(ref DoubleBox temperature, Event eventDone)
{
return new ReadTempOp(this, ref temperature, eventDone);
}
public void RunDeviceBefore()
{
if (myCfg.DebugLevel == DebugMode.Simulate ||
myCfg.DebugLevel == DebugMode.FailureDuringOperation)
{
msrmntTimeStamp = StateMachine.Time;
return;
}
var queue = modbus.ReceivedTelegrams[myCfg.ModbusAddress];
if (queue.Count == 0) return;
lastTelegram = queue.Dequeue();
if (myCfg.ConnectionType == AnalyzerConnectionType.DirectConductivityMeter)
{
ParseDirectAnalyzerTelegram(lastTelegram);
}
else
{
ParseConverterTelegram(lastTelegram);
}
}
public void RunDeviceAfter()
{
if (myCfg.DebugLevel == DebugMode.Simulate ||
myCfg.DebugLevel == DebugMode.FailureDuringOperation ||
!modbus.IsMyTurn(ticketNumber))
{
return;
}
if (myCfg.ConnectionType == AnalyzerConnectionType.DirectConductivityMeter)
{
SendDirectAnalyzerRequest();
}
else
{
SendConverterRequest();
}
}
public void StopDevice() { }
public void StopDevice2() { }
void SendDirectAnalyzerRequest()
{
ushort regAddr = myCfg.DirectRegisterAddress;
ushort count = 2;
byte[] msg = new byte[8];
msg[0] = myCfg.ModbusAddress;
msg[1] = 3;
msg[2] = (byte)(regAddr >> 8);
msg[3] = (byte)(regAddr & 0xFF);
msg[4] = (byte)(count >> 8);
msg[5] = (byte)(count & 0xFF);
Diagnostics.SetRequest(myCfg.ModbusAddress, 3, regAddr, count);
modbus.SendMessage(msg, Name);
}
void SendConverterRequest()
{
Diagnostics.SetRequest(
myCfg.ModbusAddress,
myCfg.ConverterReadFunction,
myCfg.ConverterFirstRegister,
myCfg.ConverterRegisterCount);
modbus.SendMessage(
myCfg.ModbusAddress,
myCfg.ConverterReadFunction,
myCfg.ConverterFirstRegister,
myCfg.ConverterRegisterCount,
Name);
}
void ParseDirectAnalyzerTelegram(byte[] telegram)
{
if (telegram == null) return;
if (telegram.Length == 9 && telegram[1] == 3 && telegram[2] == 4)
{
byte[] conductBytes = CreateFloatBytes(
telegram[3],
telegram[4],
telegram[5],
telegram[6],
myCfg.DirectFloatByteOrder);
conductivity = BitConverter.ToSingle(conductBytes, 0);
lastUpdate = DateTime.Now;
UpdateProcessData(conductivity);
Diagnostics.SetResponse(
telegram,
null,
conductivity,
0,
conductivity,
myCfg.Unit);
string line = string.Format("conductivity = {0} {1}", conductivity, myCfg.Unit);
log.Debug(line);
Debug.WriteLine(line);
}
}
void ParseConverterTelegram(byte[] telegram)
{
try
{
if (telegram == null || telegram.Length < 5) return;
if (telegram[1] != myCfg.ConverterReadFunction) return;
int byteCount = telegram[2];
int expectedBytes = myCfg.ConverterRegisterCount * 2;
if (byteCount < expectedBytes) return;
if (telegram.Length < 3 + expectedBytes) return;
if (rawRegs == null || rawRegs.Length != myCfg.ConverterRegisterCount)
rawRegs = new ushort[Math.Max(1, (int)myCfg.ConverterRegisterCount)];
for (int i = 0; i < myCfg.ConverterRegisterCount; i++)
{
int ix = 3 + i * 2;
rawRegs[i] = (ushort)((telegram[ix] << 8) | telegram[ix + 1]);
}
double raw = ReadConverterRawValue();
double current = ConvertRawToMilliAmps(raw);
conductivity = (float)ConvertRawToConductivity(raw);
lastUpdate = DateTime.Now;
UpdateProcessData(conductivity);
Diagnostics.SetResponse(
telegram,
rawRegs,
raw,
current,
conductivity,
myCfg.Unit);
string line = string.Format(
"converter raw = {0:0.###}, conductivity = {1:0.###} {2}",
raw,
conductivity,
myCfg.Unit);
log.Debug(line);
Debug.WriteLine(line);
}
catch (Exception ex)
{
Diagnostics.SetError(
ex.Message + Environment.NewLine +
"Telegram: " + BitConverter.ToString(telegram));
log.WarnFormat("{0}: Failed to parse converter telegram. {1}", Name, ex.Message);
Debug.WriteLine(ex);
}
}
double ReadConverterRawValue()
{
if (myCfg.ConverterValueSource == AnalyzerValueSource.IntegerRegister)
{
int ch = myCfg.ConverterChannel;
if (rawRegs == null || ch < 0 || ch >= rawRegs.Length)
return 0.0;
return rawRegs[ch];
}
if (rawRegs == null || rawRegs.Length < 4)
return 0.0;
return ModbusFloat(
rawRegs[2],
rawRegs[3],
myCfg.ConverterFloatByteOrder);
}
double ConvertRawToConductivity(double raw)
{
double denom = myCfg.RawValueAt20mA - myCfg.RawValueAt4mA;
if (Math.Abs(denom) < 1e-12) return 0;
return myCfg.ConductivityAt4mA +
(raw - myCfg.RawValueAt4mA) / denom *
(myCfg.ConductivityAt20mA - myCfg.ConductivityAt4mA);
}
double ConvertRawToMilliAmps(double raw)
{
double denom = myCfg.RawValueAt20mA - myCfg.RawValueAt4mA;
if (Math.Abs(denom) < 1e-12) return 0;
return 4.0 + (raw - myCfg.RawValueAt4mA) * (16.0 / denom);
}
static byte[] CreateFloatBytes(
byte b0,
byte b1,
byte b2,
byte b3,
AnalyzerFloatByteOrder order)
{
switch (order)
{
case AnalyzerFloatByteOrder.ABCD:
return BitConverter.IsLittleEndian
? new byte[] { b3, b2, b1, b0 }
: new byte[] { b0, b1, b2, b3 };
case AnalyzerFloatByteOrder.BADC:
return BitConverter.IsLittleEndian
? new byte[] { b2, b3, b0, b1 }
: new byte[] { b1, b0, b3, b2 };
case AnalyzerFloatByteOrder.CDAB:
return BitConverter.IsLittleEndian
? new byte[] { b1, b0, b3, b2 }
: new byte[] { b2, b3, b0, b1 };
case AnalyzerFloatByteOrder.DCBA:
return BitConverter.IsLittleEndian
? new byte[] { b0, b1, b2, b3 }
: new byte[] { b3, b2, b1, b0 };
default:
return BitConverter.IsLittleEndian
? new byte[] { b3, b2, b1, b0 }
: new byte[] { b0, b1, b2, b3 };
}
}
static float ModbusFloat(
ushort hi,
ushort lo,
AnalyzerFloatByteOrder order)
{
byte a = (byte)(hi >> 8);
byte b = (byte)(hi & 0xFF);
byte c = (byte)(lo >> 8);
byte d = (byte)(lo & 0xFF);
byte[] bytes = CreateFloatBytes(a, b, c, d, order);
return BitConverter.ToSingle(bytes, 0);
}
void UpdateProcessData(float conduct)
{
TBF.Rig.Sequences.ProcessData.Conductivity.Val = conduct;
}
public void ShowDiagnostics()
{
new AnalyzerDiagnosticsForm(this).Show();
}
}
}