/// /// 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 Config.Entities; using log4net; using TBF.Rig.Generic; using TBF.Boxes; namespace TBF.Rig.Hart.Common { /// /// Root component for HART communication via a modem connected to a serial port (RS232) /// public class Hart : ComponentBase, IDevice, GenericDevices.IHart { private static readonly ILog log = LogManager.GetLogger(typeof(Hart)); public override string ToString() { return string.Format("Modbus({0})", Cfg.ToString(1)); } private readonly HartCfg hartCfg; /// Private fields SerialPort serialPort; DateTime lastSerialPortWrite; /// /// An array of Queue-s of received telegrams fetched by HART components (as Nivotrack). /// There is one queue for each device (each slave address). /// public Queue[] ReceivedTelegrams { get { return receivedTelegrams; } } Queue[] receivedTelegrams; /// /// A Queue of telegrams to send. System prevents sending more then one telegram per 500ms. /// Function SendMessage( ) either send the telegram immediately using SendMessageNow( ) or /// inserts the telegram into telegramsToSend queue. The queue is emptied in RunDeviceAfter( ). /// Queue telegramsToSend; public Hart() { } /// /// HART modem connected via serial interface (RS232). /// Connection settings: 1200 Bd 8-bits odd-parity 1-stop-bit Flow control: none or hardware. /// public Hart(Generic.IComponentCfg cfg) : base(cfg) { receivedTelegrams = new Queue[16]; /// Max. 16 slaves with adresses 0..15 for (int i = 0; i < receivedTelegrams.Length; i++) { receivedTelegrams[i] = new Queue(); } telegramsToSend = new Queue(); serialPort = null; hartCfg = cfg as HartCfg; log.Warn(this.ToString()); } ~Hart() { } public void Initialize() { if (hartCfg.DebugLevel == DebugMode.Simulate) { serialPort = null; log.FatalFormat("{0} - Device simulated", Name); return; } string comPortName = "COM" + hartCfg.ComPortNr.ToString(); serialPort = new SerialPort(comPortName, hartCfg.BaudRate, hartCfg.Parity, hartCfg.DataBits, hartCfg.StopBits); serialPort.Handshake = hartCfg.Handshake; serialPort.Open(); log.FatalFormat("{0} - Device successfully initialized", Name); } public void RunDeviceBefore() { if (serialPort != null) { ParseReceivedData(); } } public void RunDeviceAfter() { if ((serialPort != null) && (telegramsToSend.Count > 0) && (DateTime.Now - lastSerialPortWrite) > new TimeSpan(0, 0, 0, 0, 500)) { SendMessageNow(telegramsToSend.Dequeue()); } } public void StopDevice() { if (serialPort != null) { serialPort.Close(); serialPort = null; } } public void StopDevice2() { } public void SendMessageNow(byte[] message) { serialPort.Write(message, 0, message.Length); lastSerialPortWrite = DateTime.Now; string s = Telegram.LogTelegram(string.Format("{0} - Sending message ", Name), message); Debug.WriteLine(s); log.Debug(s); } /// /// Send an arbitrary modbus message. /// When the message length is N, however only bytes 1..N-2 have to be set. /// The last two message bytes (CRC) may be uninitialized or zero. /// They are calculated inside this function as required by Modbus specification. /// /// Message incl. a reserved checksum byte, checksum does not have to be set public void SendMessage(int preambLen, byte[] message) { if (serialPort == null) return; Telegram.UpdateTelegramChecksum(message); byte[] preambAndMessage = new byte[message.Length + preambLen]; for (int i = 0; i < preambLen; i++) preambAndMessage[i] = (byte)0xFF; for (int i = preambLen; i < preambLen + message.Length; i++) preambAndMessage[i] = message[i - preambLen]; if ((DateTime.Now - lastSerialPortWrite) > new TimeSpan(0, 0, 0, 0, 500)) { /// More then 100 ms since last 'send' --> do not enqueue the message SendMessageNow(preambAndMessage); } else { telegramsToSend.Enqueue(preambAndMessage); string s = Telegram.LogTelegram(string.Format("{0} - Enqueueing message ", Name), message); Debug.WriteLine(s); log.Debug(s); } } void ParseReceivedData() { int nrBytes = serialPort.BytesToRead; if (nrBytes > 0) { /// Read available data byte[] buf = new byte[nrBytes]; serialPort.Read(buf, 0, nrBytes); /// Determine count of leading FF-s int preambLen = 0; for (int i = 0; i < nrBytes; i++) { if (buf[i] != (byte)0xFF) break; preambLen++; } int telegramLen = nrBytes - preambLen; /// Check a telegram length, a checksum, a start byte (S -> M) and a master address (primary) if (preambLen < 3 || telegramLen < 4 || !Telegram.VerifyTelegramChecksum(preambLen, buf) || ((buf[preambLen] != (byte)Mark.ShortFrameSlave2Master) && (buf[preambLen] != (byte)Mark.LongFrameSlave2Master)) || ((buf[preambLen + 1] & 0x80) != (byte)Master.Primary)) { string s = Telegram.LogTelegram(string.Format("{0} - Invalid data received ", Name), buf); Debug.WriteLine(s); log.Debug(s); return; } /// Strip the preamble byte[] data = new byte[telegramLen]; for (int i = preambLen; i < nrBytes; i++) data[i - preambLen] = buf[i]; bool isShortFrame = (data[0] == 0x06); if (isShortFrame) { int address = data[1] & 0x0F; receivedTelegrams[address].Enqueue(data); string s = string.Format("{0} - {1} address={2} preambLen={3} count={4}", Name, Telegram.LogTelegram("Valid telegram received: ", data), address, preambLen, receivedTelegrams[address].Count); Debug.WriteLine(s); log.Debug(s); } else { /// TODO string s = Telegram.LogTelegram(string.Format("{0} - Telegram with long address received ", Name), buf); Debug.WriteLine(s); log.Debug(s); } } } } }