218 lines
7.0 KiB
C#
218 lines
7.0 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.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("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
|
|
|
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)
|
|
{
|
|
modbusCommonCfg = cfg as ModbusCfg;
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
telegramsToSend = new Queue<byte[]>();
|
|
|
|
receivedTelegrams = new Queue<byte[]>[256];
|
|
for (int i = 0; i < receivedTelegrams.Length; i++)
|
|
{
|
|
receivedTelegrams[i] = new Queue<byte[]>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|