359 lines
11 KiB
C#
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();
|
|
}
|
|
}
|
|
} |