tbf/TestBenchFramework/BenchControl/WaterMeters/iPerl/WaterMeter.cs

364 lines
9.9 KiB
C#
Raw Normal View History

///
/// Copyright (c) 2015 Sensus Metering Systems
/// Author: Milan Hanajík
///
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
using log4net;
using TBF.BenchControl;
using TBF.BenchControl.Generic;
namespace TBF.BenchControl.WaterMeters.iPerl
{
/// <summary>
/// This component = instance of this class is a placeholder for a combined main watermeter
/// </summary>
public class WaterMeter : ComponentBase, IDevice, GenericDevices.IWaterMeter, GenericDevices.IRegisterReader, IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(WaterMeter));
protected static readonly ILog optoDataLogger = LogManager.GetLogger("OptoData");
public override string ToString() { return string.Format("iPerl({0})", Cfg.ToString(1)); }
readonly WaterMeterCfg iPerlCfg;
public int RfidComPortNr { get { return iPerlCfg.RfidComPortNr; } }
public int Group { get { return iPerlCfg.Group; } }
public float PulsesPerLtr { get { return iPerlCfg.ProcParams.PulsesPerLtr; } }
/// <summary>WaterMeter producer</summary>
public string Producer { get { return iPerlCfg.ProcParams.Producer; } }
/// <summary>Nominal water flow in [m3/h]</summary>
public float Qn { get { return iPerlCfg.ProcParams.Qn; } }
/// <summary>WaterMeter approval information or signature</summary>
public string ApprovalInfo { get { return iPerlCfg.ProcParams.ApprovalInfo; } }
/// <summary>Metrological class</summary>
public string MetrologicalClass { get { return iPerlCfg.ProcParams.MetrologicalClass; } }
/// Properties set by the Begin and the End form
public string SerialNr
{
get { return serialNr; }
set { serialNr = value; }
}
string serialNr;
public string EndState
{
get { return endState; }
set { endState = value; }
}
string endState;
public string BeginState
{
get { return beginState; }
set { beginState = value; }
}
string beginState;
public bool Disabled
{
get { return disabled; }
set { disabled = value; }
}
bool disabled;
/// <summary> ConfigStruct of the water meter obtained or updated by iPerlCommunication </summary>
public ConfigStruct ConfigStruct
{
get { return configStruct; }
set { configStruct = value; }
}
ConfigStruct configStruct;
/// <summary> CalibrationStruct of the water meter obtained or updated by iPerlCommunication </summary>
public CalibrationStruct CalibrationStruct
{
get { return calibrationStruct; }
set { calibrationStruct = value; }
}
CalibrationStruct calibrationStruct;
2015-08-14 12:33:36 +00:00
public ushort CalibrationFactor { get { return CalibrationStruct.Calibration; } }
public ushort CalculatedCalibrationFactor
{
get
{
return (UInt16)((double)CalibrationStruct.Calibration + 0.5); /// TODO: real calculation
2015-08-14 12:33:36 +00:00
}
}
/// <summary>
/// Volume of water in liter set by OptoTelegramRreceived(...)
/// </summary>
private double volumeLtr;
private double volumeLtrStart;
private Boxes.IntBox pulses;
private Boxes.IntBox refPulses;
///
/// Opto serial port and worker thread related private variables
///
private SerialPort optoSerialPort;
#if USE_SEPARATE_WORKER_THREAD
private Thread workerThread;
private bool stopWorkerThread;
#endif
public WaterMeter()
{
synchronized = false;
synchronized2 = false;
partOfTelegram = string.Empty;
}
public WaterMeter(WaterMeterCfg cfg)
: base(cfg)
{
iPerlCfg = cfg;
log.Debug(this.ToString());
}
static bool initOnceDone;
public void Initialize()
{
if (DebugLevel == Entities.DebugMode.Normal)
{
if (!initOnceDone)
{
initOnceDone = true;
optoDataLogger.Fatal("------------------------------------------------------------------------");
optoDataLogger.Fatal("Program restarted");
}
optoSerialPort = new SerialPort(string.Format("COM{0}", iPerlCfg.OptoComPortNr),
9600, Parity.None, 8, StopBits.One);
optoSerialPort.Handshake = Handshake.None;
optoSerialPort.Open();
#if USE_SEPARATE_WORKER_THREAD
stopWorkerThread = false;
workerThread = new Thread(Worker);
workerThread.Start();
#endif
}
}
public void RunDeviceBefore()
{
#if !USE_SEPARATE_WORKER_THREAD
ReadOptoSerialPort();
#endif
}
public void RunDeviceAfter()
{
}
public void StopDevice()
{
if (DebugLevel == Entities.DebugMode.Normal)
{
#if USE_SEPARATE_WORKER_THREAD
stopWorkerThread = true;
workerThread.Join(2000);
#endif
optoSerialPort.Close();
}
}
/// <summary>
/// Events: Event.ReadRegisterDone, Event.Error
/// </summary>
/// <param name="pulses">Reference to a variable for the water meter pulses</param>
/// <returns>ReadWaterMeter instance reference casted to IOperaton</returns>
public IOperation ReadRegisterOp(ref Boxes.IntBox pulses)
{
this.pulses = pulses;
return this;
}
/// <summary>
/// Events: Event.ReadRegisterDone, Event.Error
/// </summary>
/// <param name="pulses">Reference to a variable for the water meter pulses</param>
/// <param name="pulses">Reference to a variable for the related reference flowmeter pulses</param>
/// <returns>Reference to operation object instance</returns>
public IOperation ReadRegisterOp(ref Boxes.IntBox pulses, ref Boxes.IntBox refPulses)
{
this.pulses = pulses;
this.refPulses = refPulses;
return this;
}
/// <summary>
/// Events: ReadReferenceDone, Error
/// </summary>
/// <param name="pulses">Reference to a variable for the water meter pulses</param>
/// <returns>Reference to operation object instance</returns>
public IOperation ReadReferenceForRegisterOp(ref Boxes.IntBox pulses, ref Boxes.IntBox refPulses)
{
this.pulses = pulses;
this.refPulses = refPulses;
return this;
}
/// <summary>Start this operation</summary>
public void Start()
{
ReadPulses();
}
/// <summary>Run this operation</summary>
/// <returns>eventDone</returns>
public Event Run()
{
ReadPulses();
return Event.ReadRegisterDone;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
}
void ReadPulses()
{
pulses.Val = (int)((volumeLtr - volumeLtrStart) / (double)PulsesPerLtr + 0.5);
refPulses.Val = StateMachine.ControlBoard.EtPulses(0);
}
public void Reset()
{
volumeLtrStart = volumeLtr;
}
#if USE_SEPARATE_WORKER_THREAD
void Worker()
{
while (!stopWorkerThread)
{
Thread.Sleep(250);
if (stopWorkerThread) break;
ReadOptoSerialPort();
}
}
#endif
bool synchronized;
bool synchronized2;
string partOfTelegram;
/// <summary>
/// 9600 Bd, 8 data bits, 1 stop bit, no parity
///
/// Telegram description:
///
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
///
/// Example:
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
/// ...
/// </summary>
void ReadOptoSerialPort()
{
if (DebugLevel == Entities.DebugMode.Normal || DebugLevel == Entities.DebugMode.Record)
{
int nrBytes = optoSerialPort.BytesToRead;
if (nrBytes > 0)
{
char[] buffer = new char[nrBytes];
optoSerialPort.Read(buffer, 0, nrBytes);
string received = new string(buffer);
string allRcvd = partOfTelegram + received;
OptoTelegram opto;
while (true)
{
int pos = allRcvd.IndexOf("\r\n");
if (pos < 0)
{
/// No CR+LF found, wait for more characters in the next invocation
partOfTelegram = allRcvd;
return;
}
// CR+LF found
else if (pos < OptoTelegram.Length - 2)
{
/// CR+LF found too early, truncate the beginning incl CR+LF and keep scanning in this loop
allRcvd = allRcvd.Substring(pos + 2);
if (synchronized)
{
optoDataLogger.ErrorFormat("{0}: Opto data syncronisation error", Name);
}
synchronized = true;
}
// CR+LF found and (pos >= OptoTelegram.Length - 2)
else if ((null != (opto = OptoTelegram.FromString(allRcvd.Substring(pos - OptoTelegram.Length + 2), iPerlCfg.MeterType))))
{
OptoTelegramRreceived(opto, synchronized2);
synchronized2 = synchronized;
allRcvd = allRcvd.Substring(pos + 2);
}
else
{
optoDataLogger.ErrorFormat("{0}: Wrong format of opto data", Name);
allRcvd = allRcvd.Substring(pos + 2);
}
}
//OnOptoReceived(this, new OptoReceivedEventArgs(s));
}
else
{
//OnOptoReceived(this, new OptoReceivedEventArgs("."));
}
}
else if (DebugLevel == Entities.DebugMode.Simulate)
{
}
else if (DebugLevel == Entities.DebugMode.Reply)
{
}
}
void OptoTelegramRreceived(OptoTelegram optoTelegram, bool async)
{
volumeLtr = optoTelegram.Volume;
optoDataLogger.InfoFormat("{0}: {1}", Name, optoTelegram);
}
/// <summary>
/// Called from the state machine when a test is selected and UI needs to be updated.
/// </summary>
public void OnOptoReceived(object sender, OptoReceivedEventArgs args)
{
if (OptoReceivedHandler == null) return;
try { OptoReceivedHandler(sender, args); }
catch (Exception) { }
}
public event EventHandler<OptoReceivedEventArgs> OptoReceivedHandler;
}
}