/// /// 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 Config.Entities; using log4net; using TBF.BenchControl.Generic; using TBF.Boxes; using TBF.BenchControl.GenericDevices; namespace TBF.BenchControl.RegisterReaders.KPackE.Radio { /// /// Root component for Modbus communication via serial port (RS485) /// 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 }; // 00 OKAY @ (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 data; /// Received and not yet processed data /// /// Returns true when the last telegram is not older then 60 seconds /// public bool IsRadioOn() { return (DateTime.Now - lastTelegramReceived) < new TimeSpan(0, 0, 60); } public IList RegReaders; public IReceivesDataFromRadio EntryForm; public Radio() { } /// /// 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. /// public Radio(Generic.IComponentCfg cfg) : base(cfg) { radioCfg = cfg as RadioCfg; } public override void Initialize() { RegReaders = new List(); data = new List(); 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); } } /// /// Register(rr) is called from Initialize() function of RegisterReader-s /// /// RegisterReader reference public void Register(RegisterReader.RegisterReader regReader) { RegReaders.Add(regReader); } /// /// Unregister(rr) is called from StopDevice() function of RegisterReader-s /// /// RegisterReader reference 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); } /// Run this device public void RunDeviceBefore() { if (serialPort != null) ReadDataFromSerialPort(); } /// Run this device 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.ClaculateTelegramChecksum(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); } /// /// Compares received data bytes with a telegram frame. /// Zero bytes in the telegram frame are ignored, nonzero bytes must fit. /// /// Data bytes /// Offset of the fitted frame within data /// Telegram frame /// true when data fit the frame bool DataFitFrame(IList 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 } /// /// Get a sub-array /// public static byte[] SubArray(IList 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; } /// Stop this device public void StopDevice() { if (serialPort != null && serialPort.IsOpen) { serialPort.Close(); } } public void StopDevice2() { } } }