tbf/TBF/Rig/Modbus/Common/Modbus.cs

242 lines
8.1 KiB
C#

///
/// 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.Text;
using System.Threading;
using Common;
using log4net;
using TBF.Rig.Generic;
using TBF.Boxes;
namespace TBF.Rig.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("{0}({1})", ClassName, Cfg.ToString(-1)); }
/// <summary>
/// Enumeration of modbus components via static fields and methods
/// </summary>
static int nextIdx = 0;
static int modbusComponentsCount { get { return nextIdx; } }
static Modbus[] modbusComponents;
///
int modbusComponentIx; /// 0-based modbus component index assigned in Initialize()
private readonly ModbusCfg modbusCommonCfg;
/// Private fields
SerialPort serialPort;
DateTime lastSerialPortWrite;
bool initialRunDeviceCommComplete;
Queue<byte[]> telegramsToSend;
public Queue<byte[]>[] ReceivedTelegrams { get { return receivedTelegrams; } }
Queue<byte[]>[] receivedTelegrams;
public string[] ComponentNames { get { return componentNames; } }
string[] componentNames;
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)
{
modbusCommonCfg = cfg as ModbusCfg;
}
public override void Initialize()
{
modbusComponentIx = nextIdx++;
///
if (modbusComponents == null || modbusComponents.Length < nextIdx)
{
Modbus[] componentsSoFar = modbusComponents;
modbusComponents = new Modbus[modbusComponentsCount];
if (componentsSoFar != null)
{
for (int i = 0; i < componentsSoFar.Length; i++) modbusComponents[i] = componentsSoFar[i];
}
modbusComponents[modbusComponentsCount - 1] = this;
}
telegramsToSend = new Queue<byte[]>();
receivedTelegrams = new Queue<byte[]>[256];
for (int i = 0; i < receivedTelegrams.Length; i++) receivedTelegrams[i] = new Queue<byte[]>();
componentNames = new string[256];
for (int i = 0; i < componentNames.Length; i++) componentNames[i] = "?";
if (modbusCommonCfg.DebugLevel == DebugMode.Normal)
{
string comPortName = "COM" + modbusCommonCfg.ComPortNr.ToString();
serialPort = new SerialPort(comPortName, modbusCommonCfg.BaudRate, modbusCommonCfg.Parity, modbusCommonCfg.DataBits, modbusCommonCfg.StopBits);
serialPort.Handshake = modbusCommonCfg.Handshake;
serialPort.Open();
initialRunDeviceCommComplete = false; /// Causes search for Quido RS modules in the first RunDeviceAfter() call
log.FatalFormat("{0} initialized: {1}", Name, this);
}
else
{
serialPort = null;
log.FatalFormat("{0} simulated: {1}", Name, this);
}
}
/// <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)
{
byte[] message = new byte[8]
{
modbusAddress,
function,
(byte)(dataAddress / 256),
(byte)(dataAddress % 256),
(byte)(dataCount / 256),
(byte)(dataCount % 256),
0,
0,
};
SendMessage(message);
}
/// <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)
{
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>Run this device</summary>
public void RunDeviceBefore()
{
if (serialPort == null) return;
int nrBytes = serialPort.BytesToRead;
if (nrBytes > 0)
{
byte[] buffer = new byte[nrBytes];
serialPort.Read(buffer, 0, nrBytes);
int deviceAddress = (nrBytes >= 1) ? buffer[0] : 0;
int function = (nrBytes >= 2) ? buffer[1] : 0;
if ((nrBytes >= 4) && Telegram.VerifyTelegramCRC(buffer))
{
receivedTelegrams[deviceAddress].Enqueue(buffer);
string s = string.Format("{0} - {1} address={2} count={3}", Name, Telegram.LogTelegram("Telegram received: ", buffer), deviceAddress, receivedTelegrams[deviceAddress].Count);
Debug.WriteLine(s);
log.Debug(s);
}
else
{
string s = Telegram.LogTelegram(string.Format("{0} - Invalid data received ", Name), buffer);
Debug.WriteLine(s);
log.Debug(s);
}
}
}
/// <summary>Run this device</summary>
public void RunDeviceAfter()
{
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()
{
/// 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());
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
}
public void StopDevice2() { }
public void SendMessageNow(byte[] message)
{
if (serialPort != null && serialPort.IsOpen)
{
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);
}
}
}