tbf/TBF/Rig/RegisterReaders/KPackE/Radio/Radio.cs

289 lines
10 KiB
C#

///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO.Ports;
using System.Text;
using System.Threading;
using Common;
using log4net;
using TBF.Rig.Generic;
using TBF.Boxes;
using TBF.Rig.GenericDevices;
namespace TBF.Rig.RegisterReaders.KPackE.Radio
{
/// <summary>
/// Root component for Modbus communication via serial port (RS485)
/// </summary>
public class Radio : ComponentBase, IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(Radio));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
enum KPackE_Type
{
Reading = 0,
RSSI = 1,
Delta = 2,
SmartMeterEvent = 5,
Event = 7,
}
enum KPackE_Original
{
Repeated = 0,
Original = 1,
}
///
/// Definition of telegrams and telegram frames
///
static readonly byte[] Ack = new byte[] { 0x06 };
static readonly byte[] HartBeatTelegram = new byte[] { 0x0A, 0x30, 0x30, 0x20, 0x4F, 0x4B, 0x41, 0x59, 0x20, 0x40, 0x0D }; // <LF>00 OKAY @<CR> (11 bytes)
static readonly byte[] KPackETelegramFrame = new byte[] { 0x02, 0x0F, 0x4D, 0x45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03, 0, 0x0A, 0x0D };
static readonly int MinRcvdTlgrmLen = Math.Min(HartBeatTelegram.Length, KPackETelegramFrame.Length);
static readonly int MaxRcvdTlgrmLen = Math.Max(HartBeatTelegram.Length, KPackETelegramFrame.Length);
///
/// Private fields
///
private readonly RadioCfg radioCfg;
SerialPort serialPort;
DateTime lastSerialPortWrite;
DateTime lastTelegramReceived;
IList<byte> data; /// Received and not yet processed data
/// <summary>
/// Returns true when the last telegram is not older then 60 seconds
/// </summary>
public bool IsRadioOn()
{
return (DateTime.Now - lastTelegramReceived) < new TimeSpan(0, 0, 60);
}
public IList<RegisterReader.RegisterReader> RegReaders;
public IReceivesDataFromRadio EntryForm;
public Radio() { }
/// <summary>
/// Ambient temperature / humidity / pressure meter 'Greco' connected via serial interface (RS232)
/// Connection settings: 19200 Bd 8-bits No-parity 1-stop-bit Flow control: none or hardware.
/// </summary>
public Radio(Generic.IComponentCfg cfg)
: base(cfg)
{
radioCfg = cfg as RadioCfg;
}
public override void Initialize()
{
RegReaders = new List<RegisterReader.RegisterReader>();
data = new List<byte>();
lastTelegramReceived = DateTime.MinValue;
if (radioCfg.DebugLevel == DebugMode.Normal)
{
string portName = "COM" + radioCfg.ComPortNr.ToString();
serialPort = new SerialPort(portName, radioCfg.BaudRate, radioCfg.Parity, radioCfg.DataBits, radioCfg.StopBits);
serialPort.Handshake = radioCfg.Handshake;
serialPort.Open();
log.FatalFormat("{0} initialized: {1}", Name, this);
}
else
{
serialPort = null;
log.FatalFormat("{0} simulated: {1}", Name, this);
}
}
/// <summary>
/// Register(rr) is called from Initialize() function of RegisterReader-s
/// </summary>
/// <param name="regReader">RegisterReader reference</param>
public void Register(RegisterReader.RegisterReader regReader)
{
RegReaders.Add(regReader);
}
/// <summary>
/// Unregister(rr) is called from StopDevice() function of RegisterReader-s
/// </summary>
/// <param name="regReader">RegisterReader reference</param>
public void Unregister(RegisterReader.RegisterReader regReader)
{
RegReaders.Remove(regReader);
}
public void SendAcknowledgeNow()
{
serialPort.Write(Ack, 0, Ack.Length);
lastSerialPortWrite = DateTime.Now;
string s = Telegram.LogTelegram(string.Format("{0} - Sending message ", Name), Ack);
Debug.WriteLine(s);
log.Debug(s);
}
/// <summary>Run this device</summary>
public void RunDeviceBefore()
{
if (serialPort != null) ReadDataFromSerialPort();
}
/// <summary>Run this device</summary>
public void RunDeviceAfter()
{
if (serialPort != null) ReadDataFromSerialPort();
}
void ReadDataFromSerialPort()
{
/// Append any new data to the data buffer
int receivedBytesCount = serialPort.BytesToRead;
if (receivedBytesCount > 0)
{
byte[] receivedData = new byte[receivedBytesCount];
serialPort.Read(receivedData, 0, receivedBytesCount);
{
string s = Telegram.LogTelegram(string.Format("{0} received ", Name), receivedData);
Debug.WriteLine(s);
log.Debug(s);
}
foreach (var b in receivedData) data.Add(b);
}
if (data.Count >= MinRcvdTlgrmLen)
{
for (int offset = 0; offset <= (data.Count - MinRcvdTlgrmLen); offset++)
{
if (DataFitFrame(data, offset, KPackETelegramFrame) &&
(data[offset + 14] == Telegram.CalculateTelegramChecksum(SubArray(data, offset + 2, 12))))
{
/// Read the telegram
KPackE_Type type = (KPackE_Type)((data[offset + 4] >> 5) & 0x07);
KPackE_Original original = ((data[offset + 4] & 0x10) != 0) ? KPackE_Original.Original : KPackE_Original.Repeated;
int slotNumber = (data[offset + 4] & 0x0F);
long reading = (((data[offset + 5] * 256 + data[offset + 6]) * 256 + data[offset + 7]) * 256 + data[offset + 8]) * 16 + ((data[offset + 9] >> 4) & 0x0F);
int account = ((data[offset + 9] & 0x0F) * 256 + data[offset + 10]) * 256 + data[offset + 11];
int rssi = data[offset + 12];
if (type == KPackE_Type.Reading)
{
string serialNr = account.ToString();
foreach (var rr in RegReaders)
{
if (rr.SerialNr == serialNr)
{
rr.WMStateReceived((int)reading);
EntryForm.WMStateReceived(serialNr, reading * rr.LtrsPerPulse);
break;
}
}
}
lastTelegramReceived = DateTime.Now;
SendAcknowledgeNow();
/// Remove processed data from the buffer
for (int i = 0; i < offset + KPackETelegramFrame.Length; i++) data.RemoveAt(0);
string s;
if (type == KPackE_Type.Reading)
{
s = string.Format("{0} processed KPackE telegram: s/n = {1} reading = {2}", Name, account, reading);
}
else
{
s = string.Format("{0} processed KPackE telegram: s/n = {1} {2}", Name, account, type);
}
Debug.WriteLine(s);
log.Info(s);
break;
}
else if (DataFitFrame(data, offset, HartBeatTelegram))
{
/// acknowledge hart beat telegram
lastTelegramReceived = DateTime.Now;
SendAcknowledgeNow();
/// Remove processed data from the buffer
for (int i = 0; i < offset + HartBeatTelegram.Length; i++) data.RemoveAt(0);
string s = string.Format("{0} processed HartBeat telegram", Name);
Debug.WriteLine(s);
log.Debug(s);
break;
}
}
}
string ss = string.Format("{0}: {1} unprocessed bytes in the data buffer", Name, data.Count);
Debug.WriteLine(ss);
log.Debug(ss);
}
/// <summary>
/// Compares received data bytes with a telegram frame.
/// Zero bytes in the telegram frame are ignored, nonzero bytes must fit.
/// </summary>
/// <param name="data">Data bytes</param>
/// <param name="offset">Offset of the fitted frame within data</param>
/// <param name="telegramFrame">Telegram frame</param>
/// <returns>true when data fit the frame</returns>
bool DataFitFrame(IList<byte> data, int offset, byte[] telegramFrame)
{
if ((data.Count - offset) < telegramFrame.Length)
{
return false; /// Not enough data
}
for (int i = 0; i < telegramFrame.Length; i++)
{
if ((telegramFrame[i] != 0) && (telegramFrame[i] != data[i + offset]))
{
return false; /// Data do not fit nonzero frame bytes (zero frame bytes are ignored)
}
}
return true; /// Data fit the telegram frame
}
/// <summary>
/// Get a sub-array
/// </summary>
public static byte[] SubArray(IList<byte> data, int index, int length)
{
if (data.Count < index + length) return new byte[1] { 0 };
byte[] result = new byte[length];
for (int i = 0; i < length; i++) result[i] = data[index + i];
return result;
}
/// <summary>Stop this device</summary>
public void StopDevice()
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
}
public void StopDevice2() { }
}
}