tbf/TBF/BenchControl/Modbus/Common/Modbus.cs

251 lines
8.2 KiB
C#
Raw Normal View History

///
/// Copyright (c) 2015-2017 Sensus Metering Systems
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using Config.Entities;
using log4net;
using TBF.BenchControl.Generic;
using TBF.Boxes;
namespace TBF.BenchControl.Modbus.Common
{
/// <summary>
/// Root component for Modbus communication via serial port (RS485)
/// </summary>
public class Modbus : ComponentBase, IDevice, GenericDevices.IModbus
{
private static readonly ILog log = LogManager.GetLogger(typeof(Modbus));
public override string ToString() { return string.Format("Modbus({0})", Cfg.ToString(1)); }
const int MinTelegramLen = 4;
private readonly ModbusCfg modbusCommonCfg;
/// Private fields
SerialPort serialPort;
DateTime lastSerialPortWrite;
bool initialRunDeviceCommComplete;
public Queue<byte[]>[] ReceivedTelegrams
{
get { return receivedTelegrams; }
}
Queue<byte[]>[] receivedTelegrams;
Queue<byte[]> telegramsToSend;
public Modbus()
{
}
/// <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 Modbus(Generic.IComponentCfg cfg)
: base(cfg)
{
receivedTelegrams = new Queue<byte[]>[256];
for (int i = 0; i < receivedTelegrams.Length; i++)
{
receivedTelegrams[i] = new Queue<byte[]>();
}
telegramsToSend = new Queue<byte[]>();
serialPort = null;
modbusCommonCfg = cfg as ModbusCfg;
log.Debug(this.ToString());
}
~Modbus()
{
}
public void Initialize()
{
if (modbusCommonCfg.DebugLevel == DebugMode.Simulate)
{
serialPort = null;
log.FatalFormat("{0} - Device simulated", Name);
return;
}
string comPortName = "COM" + modbusCommonCfg.ComPortNr.ToString();
serialPort = new SerialPort(comPortName, modbusCommonCfg.BaudRate, modbusCommonCfg.Parity, modbusCommonCfg.DataBits, modbusCommonCfg.StopBits);
serialPort.Handshake = modbusCommonCfg.Handshake;
serialPort.Open();
//stopWorkerThread = false;
//workerThread = new Thread(Worker);
//workerThread.Start();
initialRunDeviceCommComplete = false; /// Causes search for Quido RS modules in the first RunDeviceAfter() call
log.FatalFormat("{0} - Device successfully initialized", Name);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="message">Message incl CRC fields, CRC bytes dont have to be set</param>
public void SendMessage(byte[] message)
{
if (serialPort == null) return;
Telegram.UpdateTelegramCRC(message);
if ((DateTime.Now - lastSerialPortWrite) > new TimeSpan(0, 0, 0, 0, 100) && initialRunDeviceCommComplete)
{
/// More then 100 ms since last 'send' --> do not enqueue the message
SendMessageNow(message);
}
else
{
telegramsToSend.Enqueue(message);
string s = Telegram.LogTelegram(string.Format("{0} - Enqueueing message ", Name), message);
Debug.WriteLine(s);
log.Debug(s);
}
}
/// <summary>
/// Send a structured modbus message.
/// </summary>
/// <param name="modbusAddress">Device address (1..255) or 0 = broadcast</param>
/// <param name="function">Function (0..127)</param>
/// <param name="dataAddress">Address of data to be transferred (0..65535)</param>
/// <param name="dataCount">Count of data bytes to be transferred (0..65535)</param>
public void SendMessage(byte modbusAddress, byte function, ushort dataAddress, ushort dataCount)
{
if (serialPort == null) return;
byte[] message = new byte[8]
{
modbusAddress,
function,
(byte)(dataAddress / 256),
(byte)(dataAddress % 256),
(byte)(dataCount / 256),
(byte)(dataCount % 256),
0,
0,
};
SendMessage(message);
}
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);
}
/// <summary>Run this device</summary>
public void RunDeviceBefore()
{
/// Create 'dataToProcess' buffer with all received bytes
int nrBytes;
if (serialPort == null || (nrBytes = serialPort.BytesToRead) < MinTelegramLen) return;
byte[] dataToProcess = new byte[nrBytes];
serialPort.Read(dataToProcess, 0, nrBytes);
while (dataToProcess.Length >= MinTelegramLen)
{
for (int candidateLen = MinTelegramLen; candidateLen <= dataToProcess.Length; candidateLen++)
{
if (Telegram.VerifyTelegramCRC(dataToProcess, candidateLen))
{
/// A valid telegram found in dataToProcess buffer
/// Split data in dataToProcess buffer into the verified telegram and newly created dataToProcess
byte[] receivedTelegram = dataToProcess.Take<byte>(candidateLen).ToArray<byte>();
dataToProcess = dataToProcess.Skip<byte>(candidateLen).ToArray<byte>();
/// Enqueue
int deviceAddress = receivedTelegram[0];
receivedTelegrams[deviceAddress].Enqueue(receivedTelegram);
string s = string.Format("{0} - {1} queue size={2}", Name, Telegram.LogTelegram("Received message ", receivedTelegram), receivedTelegrams[deviceAddress].Count);
Debug.WriteLine(s);
log.Debug(s);
break; /// Quit for loop
}
else if (candidateLen == dataToProcess.Length)
{
/// All bytes considered but no valid telegram found
string s = Telegram.LogTelegram(string.Format("{0} - Invalid data received ", Name), dataToProcess);
Debug.WriteLine(s);
log.Debug(s);
return;
}
}
}
}
/// <summary>Run this device</summary>
public void RunDeviceAfter()
{
if (serialPort == null) return;
if (!initialRunDeviceCommComplete)
{
initialRunDeviceCommComplete = true; /// Prevent 2nd invocation of the subsequent code
/// Search Quido RS modules
byte[] searchTelegram = new byte[] { 0xF8, 0x11, 0, 0 };
Telegram.UpdateTelegramCRC(searchTelegram);
SendMessageNow(searchTelegram);
}
if (telegramsToSend.Count > 0 && (DateTime.Now - lastSerialPortWrite) > new TimeSpan(0, 0, 0, 0, 500))
{
SendMessageNow(telegramsToSend.Dequeue());
}
}
/// <summary>Stop this device</summary>
public void StopDevice()
{
}
public void StopDevice2()
{
if (serialPort != null)
{
/// Send telegrams currently in the queue
while (telegramsToSend.Count > 0)
{
while ((DateTime.Now - lastSerialPortWrite) <= new TimeSpan(0, 0, 0, 0, 200))
{
Thread.Sleep(100);
}
SendMessageNow(telegramsToSend.Dequeue());
}
//stopWorkerThread = true;
//workerThread.Join(2000);
serialPort.Close();
serialPort = null;
}
}
}
}