Commit d7e444ce authored by Andreas Müller's avatar Andreas Müller

Implemented the Modbus TCP Server and some changes.

parent db96b5b0
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Modbus.Common
namespace Modbus.Common
{
/// <summary>
/// Contains all constants used in Modbus.
......@@ -10,26 +7,6 @@ namespace Modbus.Common
{
#region Error/Exception
private static Dictionary<byte, string> exceptions = new Dictionary<byte, string>
{
{ 0, "No Error" },
{ 1, "Illegal Function" },
{ 2, "Illegal Data Address" },
{ 3, "Illegal Data Value" },
{ 4, "Slave Device Failure" },
{ 5, "Acknowledge" },
{ 6, "Slave Device Busy" },
{ 7, "Negative Acknowledge" },
{ 8, "Memory Parity Error" },
{ 10, "Gateway Path Unavailable" },
{ 11, "Gateway Target Device Failed to Respond" }
};
/// <summary>
/// Known Modbus exception codes returned by the server.
/// </summary>
public static ReadOnlyDictionary<byte, string> ErrorMessages => new ReadOnlyDictionary<byte, string>(exceptions);
/// <summary>
/// The Bit-Mask to filter the error-state of a Modbus response.
/// </summary>
......
namespace Modbus.Common
using System.ComponentModel;
namespace Modbus.Common
{
/// <summary>
/// Lists the Modbus request types.
......@@ -31,34 +33,104 @@
/// <summary>
/// Read coils (Fn 1).
/// </summary>
[Description("Read Coils")]
ReadCoils = 0x01,
/// <summary>
/// Read discrete inputs (Fn 2).
/// </summary>
[Description("Read Discrete Inputs")]
ReadDiscreteInputs = 0x02,
/// <summary>
/// Reads holding registers (Fn 3).
/// </summary>
[Description("Read Holding Registers")]
ReadHoldingRegisters = 0x03,
/// <summary>
/// Reads input registers (Fn 4).
/// </summary>
[Description("Read Input Registers")]
ReadInputRegisters = 0x04,
/// <summary>
/// Writes a single coil (Fn 5).
/// </summary>
[Description("Write Single Coil")]
WriteSingleCoil = 0x05,
/// <summary>
/// Writes a single register (Fn 6).
/// </summary>
[Description("Write Single Register")]
WriteSingleRegister = 0x06,
/// <summary>
/// Writes multiple coils (Fn 15).
/// </summary>
[Description("Write Multiple Coils")]
WriteMultipleCoils = 0x0F,
/// <summary>
/// Writes multiple registers (Fn 16).
/// </summary>
[Description("Write Multiple Registers")]
WriteMultipleRegisters = 0x10
}
}
/// <summary>
/// Lists the Modbus exception codes.
/// </summary>
public enum ErrorCode : byte
{
/// <summary>
/// No error.
/// </summary>
[Description("No error")]
NoError = 0,
/// <summary>
/// Function code not valid/supported.
/// </summary>
[Description("Illegal function")]
IllegalFunction = 1,
/// <summary>
/// Data address not in range.
/// </summary>
[Description("Illegal data address")]
IllegalDataAddress = 2,
/// <summary>
/// The data value to set not valid.
/// </summary>
[Description("Illegal data value")]
IllegalDataValue = 3,
/// <summary>
/// Slave device produced a failure.
/// </summary>
[Description("Slave device failure")]
SlaveDeviceFailure = 4,
/// <summary>
/// Ack
/// </summary>
[Description("Acknowledge")]
Acknowledge = 5,
/// <summary>
/// Slave device is working on another task.
/// </summary>
[Description("Slave device busy")]
SlaveDeviceBusy = 6,
/// <summary>
/// nAck
/// </summary>
[Description("Negative acknowledge")]
NegativeAcknowledge = 7,
/// <summary>
/// Momory Parity Error.
/// </summary>
[Description("Memory parity error")]
MemoryParityError = 8,
/// <summary>
/// Gateway of the device could not be reached.
/// </summary>
[Description("Gateway path unavailable")]
GatewayPath = 10,
/// <summary>
/// Gateway device did no resopond.
/// </summary>
[Description("Gateway target device failed to respond")]
GatewayTargetDevice = 11
}
}
\ No newline at end of file
using Modbus.Common.Structures;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
......@@ -14,8 +15,6 @@ namespace Modbus.Common
{
#region Register handling
#region From Register
#region To unsigned data types
/// <summary>
......@@ -255,8 +254,22 @@ namespace Modbus.Common
#endregion To string
#endregion From Register
#endregion Register handling
#region Enums
/// <summary>
/// Tries to read the description of an enum-value.
/// </summary>
/// <param name="value">The enum value.</param>
/// <returns>The description or the <see cref="Enum.ToString()"/></returns>
public static string GetDescription(this Enum value)
{
var fi = value.GetType().GetField(value.ToString());
var attrs = (DescriptionAttribute[])fi?.GetCustomAttributes(typeof(DescriptionAttribute), inherit: false);
return attrs?.FirstOrDefault()?.Description ?? value.ToString();
}
#endregion
}
}
......@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Modbus.Common
namespace Modbus.Common.Interfaces
{
/// <summary>
/// Represents the interface for a Modbus client.
......
using Modbus.Common.Structures;
using System;
using System.Collections.Generic;
namespace Modbus.Common.Interfaces
{
/// <summary>
/// Represents the interface of a Modbus server.
/// </summary>
public interface IModbusServer : IDisposable
{
#region Events
/// <summary>
/// Raised when a coil was written.
/// </summary>
event EventHandler<WriteEventArgs> CoilWritten;
/// <summary>
/// Raised when a register was written.
/// </summary>
event EventHandler<WriteEventArgs> RegisterWritten;
#endregion Events
#region Properties
/// <summary>
/// Gets the UTC timestamp of the server start.
/// </summary>
DateTime StartTime { get; }
/// <summary>
/// Gets a value indicating whether the server is running.
/// </summary>
bool IsRunning { get; }
/// <summary>
/// Gets a list of device ids the server handles.
/// </summary>
List<byte> DeviceIds { get; }
#endregion Properties
#region Public methods
#region Coils
/// <summary>
/// Returns a coil of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="coilNumber">The address of the coil.</param>
/// <returns>The coil.</returns>
Coil GetCoil(byte deviceId, ushort coilNumber);
/// <summary>
/// Sets the status of a coild to a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="coilNumber">The address of the coil.</param>
/// <param name="value">The status of the coil.</param>
void SetCoil(byte deviceId, ushort coilNumber, bool value);
/// <summary>
/// Sets the status of a coild to a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="coil">The coil.</param>
void SetCoil(byte deviceId, Coil coil);
#endregion Coils
#region Discrete Inputs
/// <summary>
/// Returns a discrete input of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="inputNumber">The discrete input address.</param>
/// <returns>The discrete input.</returns>
DiscreteInput GetDiscreteInput(byte deviceId, ushort inputNumber);
/// <summary>
/// Sets a discrete input of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="inputNumber">The discrete input address.</param>
/// <param name="value">A value inidcating whether the input is set.</param>
void SetDiscreteInput(byte deviceId, ushort inputNumber, bool value);
/// <summary>
/// Sets a discrete input of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="discreteInput">The discrete input to set.</param>
void SetDiscreteInput(byte deviceId, DiscreteInput discreteInput);
#endregion Discrete Inputs
#region Input Registers
/// <summary>
/// Returns an input register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The input register address.</param>
/// <returns>The input register.</returns>
Register GetInputRegister(byte deviceId, ushort registerNumber);
/// <summary>
/// Sets an input register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The input register address.</param>
/// <param name="value">The register value.</param>
void SetInputRegister(byte deviceId, ushort registerNumber, ushort value);
/// <summary>
/// Sets an input register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The input register address.</param>
/// <param name="highByte">The High-Byte value.</param>
/// <param name="lowByte">The Low-Byte value.</param>
void SetInputRegister(byte deviceId, ushort registerNumber, byte highByte, byte lowByte);
/// <summary>
/// Sets an input register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="register">The input register.</param>
void SetInputRegister(byte deviceId, Register register);
#endregion Input Registers
#region Holding Registers
/// <summary>
/// Returns a holding register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The holding register address.</param>
/// <returns>The holding register.</returns>
Register GetHoldingRegister(byte deviceId, ushort registerNumber);
/// <summary>
/// Sets a holding register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The holding register address.</param>
/// <param name="value">The register value.</param>
void SetHoldingRegister(byte deviceId, ushort registerNumber, ushort value);
/// <summary>
/// Sets a holding register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registerNumber">The holding register address.</param>
/// <param name="highByte">The high byte value.</param>
/// <param name="lowByte">The low byte value.</param>
void SetHoldingRegister(byte deviceId, ushort registerNumber, byte highByte, byte lowByte);
/// <summary>
/// Sets a holding register of a device.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="register">The register.</param>
void SetHoldingRegister(byte deviceId, Register register);
#endregion Holding Registers
#region Devices
/// <summary>
/// Adds a new device to the server.
/// </summary>
/// <param name="deviceId">The id of the new device.</param>
/// <returns>true on success, otherwise false.</returns>
bool AddDevice(byte deviceId);
/// <summary>
/// Removes a device from the server.
/// </summary>
/// <param name="deviceId">The device id to remove.</param>
/// <returns>true on success, otherwise false.</returns>
bool RemoveDevice(byte deviceId);
#endregion Devices
#endregion Public methods
}
/// <summary>
/// Provides information of the write action.
/// </summary>
public class WriteEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="WriteEventArgs"/> class using a single coil.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="coil">The coil.</param>
public WriteEventArgs(byte deviceId, Coil coil)
{
Coils = new List<Coil> { coil };
}
/// <summary>
/// Initializes a new instance of the <see cref="WriteEventArgs"/> class using a list of coils.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="coils">A list of coils.</param>
public WriteEventArgs(byte deviceId, List<Coil> coils)
{
Coils = coils;
}
/// <summary>
/// Initializes a new instance of the <see cref="WriteEventArgs"/> class using a single register.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="register">The register.</param>
public WriteEventArgs(byte deviceId, Register register)
{
Registers = new List<Register> { register };
}
/// <summary>
/// Initializes a new instance of the <see cref="WriteEventArgs"/> class using a list of registers.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="registers">A list of registers.</param>
public WriteEventArgs(byte deviceId, List<Register> registers)
{
Registers = registers;
}
/// <summary>
/// Gets a list of written coils.
/// </summary>
public List<Coil> Coils { get; private set; }
/// <summary>
/// Gets a list of written registers.
/// </summary>
public List<Register> Registers { get; private set; }
}
}
using Modbus.Common;
using Modbus.Common.Interfaces;
using Modbus.Common.Structures;
using Modbus.Tcp.Protocol;
using Modbus.Tcp.Utils;
......@@ -119,6 +120,10 @@ namespace Modbus.Tcp.Client
Count = count
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -192,6 +197,10 @@ namespace Modbus.Tcp.Client
Count = count
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -265,6 +274,10 @@ namespace Modbus.Tcp.Client
Count = count
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -334,6 +347,10 @@ namespace Modbus.Tcp.Client
Count = count
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -407,6 +424,10 @@ namespace Modbus.Tcp.Client
var value = (ushort)(coil.Value ? 0xFF00 : 0x0000);
request.Data.SetUInt16(0, value);
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -466,6 +487,10 @@ namespace Modbus.Tcp.Client
Data = new DataBuffer(new[] { register.HiByte, register.LoByte })
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -531,14 +556,16 @@ namespace Modbus.Tcp.Client
var numBytes = (int)Math.Ceiling(orderedList.Count / 8.0);
var coilBytes = new byte[numBytes];
for (int i = 0; i < orderedList.Count; i++)
{
var posByte = i / 8;
var posBit = i % 8;
if (orderedList[i].Value)
{
var posByte = i / 8;
var posBit = i % 8;
var mask = (byte)Math.Pow(2, posBit);
coilBytes[posByte] = (byte)(coilBytes[posByte] | mask);
var mask = (byte)Math.Pow(2, posBit);
coilBytes[posByte] = (byte)(coilBytes[posByte] | mask);
}
}
try
......@@ -552,6 +579,10 @@ namespace Modbus.Tcp.Client
Data = new DataBuffer(coilBytes)
};
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......@@ -630,6 +661,10 @@ namespace Modbus.Tcp.Client
request.Data.SetUInt16(i * 2 + 1, orderedList[i].Value);
}
var response = await SendRequest(request);
if (response.IsTimeout)
{
throw new ModbusException("Response timed out. Device id invalid?");
}
if (response.IsError)
{
throw new ModbusException(response.ErrorMessage);
......
......@@ -9,7 +9,7 @@ namespace Modbus.Tcp.Protocol
/// <summary>
/// Represents the request from a client to the server.
/// </summary>
public class Request
internal class Request
{
#region Fields
......@@ -71,7 +71,7 @@ namespace Modbus.Tcp.Protocol
public ushort Address { get; set; }
/// <summary>
/// Gets or sets the number of registers.
/// Gets or sets the number of elements.
/// </summary>
public ushort Count { get; set; }
......
......@@ -8,7 +8,7 @@ namespace Modbus.Tcp.Protocol
/// <summary>
/// Represents the response from the server to a client.
/// </summary>
public class Response
internal class Response
{
#region Constructors
......@@ -61,12 +61,12 @@ namespace Modbus.Tcp.Protocol
/// <summary>
/// Gets or sets the error/exception code.
/// </summary>
public byte ErrorCode { get; set; }
public ErrorCode ErrorCode { get; set; }
/// <summary>
/// Gets the error message.
/// </summary>
public string ErrorMessage => Consts.ErrorMessages[ErrorCode];
public string ErrorMessage => ErrorCode.GetDescription();
/// <summary>
/// Gets or sets the register address.
......@@ -77,20 +77,13 @@ namespace Modbus.Tcp.Protocol
/// Gets or sets the number of registers.
/// </summary>
public ushort Count { get; set; }
/// <summary>
/// Gets or sets the data bytes.
/// </summary>
public byte[] Bytes
{
get { return Data.Buffer; }
set { Data = new DataBuffer(value); }
}
/// <summary>
/// Gets or sets the data.
/// </summary>
internal DataBuffer Data { get; set; }
public DataBuffer Data { get; set; }
public bool IsTimeout { get; private set; }
#endregion Properties
......@@ -108,7 +101,7 @@ namespace Modbus.Tcp.Protocol
if (IsError)
{
fn = (byte)(fn & Consts.ErrorMask);
buffer.AddByte(ErrorCode);
buffer.AddByte((byte)ErrorCode);
}
else
{
......@@ -146,6 +139,13 @@ namespace Modbus.Tcp.Protocol
private void Deserialize(byte[] bytes)
{
// Response timed out => device not available
if (bytes.All(b => b == 0))
{
IsTimeout = true;
return;
}
var buffer = new DataBuffer(bytes);
var ident = buffer.GetUInt16(2);
if (ident != 0)
......@@ -165,7 +165,7 @@ namespace Modbus.Tcp.Protocol
if ((fn & Consts.ErrorMask) > 0)
{
Function = (FunctionCode)(fn ^ Consts.ErrorMask);
ErrorCode = buffer.GetByte(8);
ErrorCode = (ErrorCode)buffer.GetByte(8);
}
else
{
......@@ -219,7 +219,7 @@ namespace Modbus.Tcp.Protocol
Function.GetHashCode() ^
Address.GetHashCode() ^
Count.GetHashCode() ^
Bytes.GetHashCode();
Data.GetHashCode();
}
/// <inheritdoc/>
......
This diff is collapsed.
using Modbus.Common.Structures;
using System;
using System.Collections.Generic;
namespace Modbus.Tcp.Utils
{
internal class ModbusDevice
{
private List<ushort> coils = new List<ushort>();
private List<ushort> discreteInputs = new List<ushort>();
private Dictionary<ushort, ushort> inputRegisters = new Dictionary<ushort, ushort>();
private Dictionary<ushort, ushort> holdingRegisters = new Dictionary<ushort, ushort>();
public ModbusDevice(byte id)
{
DeviceId = id;
}
public byte DeviceId { get; private set; }
#region Coils
public Coil GetCoil(ushort address)
{
lock (coils)
{
return new Coil { Address = address, Value = coils.Contains(address) };
}
}
public void SetCoil(ushort address, bool value)
{
lock (coils)
{
if (value && !coils.Contains(address))
{
coils.Add(address);
}
if (!value && coils.Contains(address))
{
coils.Remove(address);
}
}
}
#endregion Coils
#region Discrete Input
public DiscreteInput GetInput(ushort address)
{
lock (discreteInputs)
{
return new DiscreteInput { Address = address, Value = discreteInputs.Contains(address) };
}
}
public void SetInput(ushort address, bool value)
{
lock (discreteInputs)
{
if (value && !discreteInputs.Contains(address))
{
discreteInputs.Add(address);
}
if (!value && discreteInputs.Contains(address))
{
discreteInputs.Remove(address);
}
}
}
#endregion</