Server
ModbusServer.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AMWD.Modbus.Common;
using AMWD.Modbus.Common.Interfaces;
using AMWD.Modbus.Common.Structures;
using AMWD.Modbus.Common.Util;
using AMWD.Modbus.Tcp.Protocol;
using AMWD.Modbus.Tcp.Util;
using Microsoft.Extensions.Logging;
namespace AMWD.Modbus.Tcp.Server
{
///
/// A handler to process the modbus requests.
///
/// The request to process.
/// The cancellation token fired on .
/// The response.
public delegate Response ModbusTcpRequestHandler(Request request, CancellationToken cancellationToken);
///
/// A server to communicate via Modbus TCP.
///
public clast ModbusServer : IModbusServer
{
#region Fields
private readonly ILogger logger;
private readonly CancellationTokenSource stopCts = new();
private TcpListener tcpListener;
private readonly ConcurrentDictionary modbusDevices = new();
private Task clientConnect;
private readonly ConcurrentDictionary tcpClients = new();
private readonly List clientTasks = new();
private readonly ModbusTcpRequestHandler requestHandler;
#endregion Fields
#region Constructors
///
/// Initializes a new instance of the clast.
///
/// The port to listen. (Default: 502)
/// The ip address to bind on. (Default: )
/// instance to write log entries. (Default: no logger)
/// Set this request handler to override the default implemented handling. (Default: serving the data provided by Set* methods)
public ModbusServer(int port = 502, IPAddress listenAddress = null, ILogger logger = null, ModbusTcpRequestHandler requestHandler = null)
{
ListenAddress = listenAddress;
if (ListenAddress == null)
ListenAddress = IPAddress.IPv6Any;
if (port < 0 || port > 65535)
throw new ArgumentOutOfRangeException(nameof(port));
try
{
var listener = new TcpListener(ListenAddress, port);
listener.Start(10);
Port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
}
catch (Exception ex)
{
throw new ArgumentException(nameof(port), ex);
}
this.logger = logger;
this.requestHandler = requestHandler ?? HandleRequest;
Initialization = Task.Run(() => Initialize());
}
#endregion Constructors
#region Events
///
/// Raised when a client has connected to the server.
///
public event EventHandler ClientConnected;
///
/// Raised when a client has disconnected from the server.
///
public event EventHandler ClientDisconnected;
///
/// Raised when a coil was written.
///
public event EventHandler InputWritten;
///
/// Raised when a register was written.
///
public event EventHandler RegisterWritten;
#endregion Events
#region Properties
///
/// Gets the result of the asynchronous initialization of this instance.
///
public Task Initialization { get; } = Task.CompletedTask;
///
/// Gets the UTC timestamp of the server start.
///
public DateTime StartTime { get; private set; } = DateTime.MinValue;
///
/// Gets a value indicating whether the server is running.
///
public bool IsRunning { get; private set; }
///
/// Gets the binding address.
///
public IPAddress ListenAddress { get; }
///
/// Gets the port listening on.
///
public int Port { get; }
///
/// Gets or sets read/write timeout. (Default: 1 second)
///
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(1);
///
/// Gets a list of device ids the server handles.
///
public List DeviceIds => modbusDevices.Keys.ToList();
#endregion Properties
#region Public methods
#region Coils
///
/// Returns a coil of a device.
///
/// The device id.
/// The address of the coil.
/// The coil.
public Coil GetCoil(byte deviceId, ushort coilNumber)
{
try
{
logger?.LogTrace("ModbusServer.GetCoil enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
return device.GetCoil(coilNumber);
}
finally
{
logger?.LogTrace("ModbusServer.GetCoil leave");
}
}
///
/// Sets the status of a coild to a device.
///
/// The device id.
/// The address of the coil.
/// The status of the coil.
public void SetCoil(byte deviceId, ushort coilNumber, bool value)
{
try
{
logger?.LogTrace("ModbusServer.SetCoil(byte, ushort, bool) enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
device.SetCoil(coilNumber, value);
}
finally
{
logger?.LogTrace("ModbusServer.SetCoil(byte, ushort, bool) leave");
}
}
///
/// Sets the status of a coild to a device.
///
/// The device id.
/// The coil.
public void SetCoil(byte deviceId, ModbusObject coil)
{
try
{
logger?.LogTrace("ModbusServer.SetCoil(byte, ModbusObject) enter");
CheckDisposed();
if (coil.Type != ModbusObjectType.Coil)
throw new ArgumentException("Invalid coil type set");
SetCoil(deviceId, coil.Address, coil.BoolValue);
}
finally
{
logger?.LogTrace("ModbusServer.SetCoil(byte, ModbusObject) leave");
}
}
#endregion Coils
#region Discrete Inputs
///
/// Returns a discrete input of a device.
///
/// The device id.
/// The discrete input address.
/// The discrete input.
public DiscreteInput GetDiscreteInput(byte deviceId, ushort inputNumber)
{
try
{
logger?.LogTrace("ModbusServer.GetDiscreteInput enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
return device.GetInput(inputNumber);
}
finally
{
logger?.LogTrace("ModbusServer.GetDiscreteInput leave");
}
}
///
/// Sets a discrete input of a device.
///
/// The device id.
/// The discrete input address.
/// A value inidcating whether the input is set.
public void SetDiscreteInput(byte deviceId, ushort inputNumber, bool value)
{
try
{
logger?.LogTrace("ModbusServer.SetDiscreteInput(byte, ushort, bool) enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
device.SetInput(inputNumber, value);
}
finally
{
logger?.LogTrace("ModbusServer.SetDiscreteInput(byte, ushort, bool) leave");
}
}
///
/// Sets a discrete input of a device.
///
/// The device id.
/// The discrete input to set.
public void SetDiscreteInput(byte deviceId, ModbusObject discreteInput)
{
try
{
logger?.LogTrace("ModbusServer.SetDiscreteInput(byte, ModbusObject) enter");
CheckDisposed();
if (discreteInput.Type != ModbusObjectType.DiscreteInput)
throw new ArgumentException("Invalid input type set");
SetDiscreteInput(deviceId, discreteInput.Address, discreteInput.BoolValue);
}
finally
{
logger?.LogTrace("ModbusServer.SetDiscreteInput(byte, ModbusObject) leave");
}
}
#endregion Discrete Inputs
#region Input Registers
///
/// Returns an input register of a device.
///
/// The device id.
/// The input register address.
/// The input register.
public Register GetInputRegister(byte deviceId, ushort registerNumber)
{
try
{
logger?.LogTrace("ModbusServer.GetInputRegister enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
return device.GetInputRegister(registerNumber);
}
finally
{
logger?.LogTrace("ModbusServer.GetInputRegister leave");
}
}
///
/// Sets an input register of a device.
///
/// The device id.
/// The input register address.
/// The register value.
public void SetInputRegister(byte deviceId, ushort registerNumber, ushort value)
{
try
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ushort, ushort) enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
device.SetInputRegister(registerNumber, value);
}
finally
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ushort, ushort) leave");
}
}
///
/// Sets an input register of a device.
///
/// The device id.
/// The input register address.
/// The High-Byte value.
/// The Low-Byte value.
public void SetInputRegister(byte deviceId, ushort registerNumber, byte highByte, byte lowByte)
{
try
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ushort, byte, byte) enter");
CheckDisposed();
SetInputRegister(deviceId, new Register { Address = registerNumber, HiByte = highByte, LoByte = lowByte, Type = ModbusObjectType.InputRegister });
}
finally
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ushort, byte, byte) leave");
}
}
///
/// Sets an input register of a device.
///
/// The device id.
/// The input register.
public void SetInputRegister(byte deviceId, ModbusObject register)
{
try
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ModbusObject) enter");
CheckDisposed();
if (register.Type != ModbusObjectType.InputRegister)
throw new ArgumentException("Invalid register type set");
SetInputRegister(deviceId, register.Address, register.RegisterValue);
}
finally
{
logger?.LogTrace("ModbusServer.SetInputRegister(byte, ModbusObject) leave");
}
}
#endregion Input Registers
#region Holding Registers
///
/// Returns a holding register of a device.
///
/// The device id.
/// The holding register address.
/// The holding register.
public Register GetHoldingRegister(byte deviceId, ushort registerNumber)
{
try
{
logger?.LogTrace("ModbusServer.GetHoldingRegister enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
return device.GetHoldingRegister(registerNumber);
}
finally
{
logger?.LogTrace("ModbusServer.GetHoldingRegister leave");
}
}
///
/// Sets a holding register of a device.
///
/// The device id.
/// The holding register address.
/// The register value.
public void SetHoldingRegister(byte deviceId, ushort registerNumber, ushort value)
{
try
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ushort, ushort) enter");
CheckDisposed();
if (!modbusDevices.TryGetValue(deviceId, out ModbusDevice device))
throw new ArgumentException($"Device #{deviceId} does not exist");
device.SetHoldingRegister(registerNumber, value);
}
finally
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ushort, ushort) leave");
}
}
///
/// Sets a holding register of a device.
///
/// The device id.
/// The holding register address.
/// The high byte value.
/// The low byte value.
public void SetHoldingRegister(byte deviceId, ushort registerNumber, byte highByte, byte lowByte)
{
try
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ushort, byte, byte) enter");
CheckDisposed();
SetHoldingRegister(deviceId, new Register { Address = registerNumber, HiByte = highByte, LoByte = lowByte, Type = ModbusObjectType.HoldingRegister });
}
finally
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ushort, byte, byte) leave");
}
}
///
/// Sets a holding register of a device.
///
/// The device id.
/// The register.
public void SetHoldingRegister(byte deviceId, ModbusObject register)
{
try
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ModbusObject) enter");
CheckDisposed();
if (register.Type != ModbusObjectType.HoldingRegister)
throw new ArgumentException("Invalid register type set");
SetHoldingRegister(deviceId, register.Address, register.RegisterValue);
}
finally
{
logger?.LogTrace("ModbusServer.SetHoldingRegister(byte, ModbusObject) leave");
}
}
#endregion Holding Registers
#region Devices
///
/// Adds a new device to the server.
///
/// The id of the new device.
/// true on success, otherwise false.
public bool AddDevice(byte deviceId)
{
try
{
logger?.LogTrace("ModbusServer.AddDevice enter");
CheckDisposed();
return modbusDevices.TryAdd(deviceId, new ModbusDevice(deviceId));
}
finally
{
logger?.LogTrace("ModbusServer.AddDevice leave");
}
}
///
/// Removes a device from the server.
///
/// The device id to remove.
/// true on success, otherwise false.
public bool RemoveDevice(byte deviceId)
{
try
{
logger?.LogTrace("ModbusServer.RemoveDevice enter");
CheckDisposed();
return modbusDevices.TryRemove(deviceId, out ModbusDevice _);
}
finally
{
logger?.LogTrace("ModbusServer.RemoveDevice leave");
}
}
#endregion Devices
#endregion Public methods
#region Private methods
#region Server
private Task Initialize()
{
try
{
logger?.LogTrace("ModbusServer.Initialize enter");
CheckDisposed();
tcpListener?.Stop();
tcpListener = null;
tcpListener = new TcpListener(ListenAddress, Port);
if (ListenAddress.AddressFamily == AddressFamily.InterNetworkV6)
tcpListener.Server.DualMode = true;
tcpListener.Start();
StartTime = DateTime.UtcNow;
IsRunning = true;
clientConnect = Task.Run(async () => await WaitForClient());
logger?.LogInformation($"Modbus server started. Listening on {ListenAddress}:{Port}/tcp.");
return Task.CompletedTask;
}
finally
{
logger?.LogTrace("ModbusServer.Initialize leave");
}
}
private async Task WaitForClient()
{
try
{
logger?.LogTrace("ModbusServer.WaitForClient enter");
while (!stopCts.IsCancellationRequested)
{
try
{
var client = await tcpListener.AcceptTcpClientAsync();
if (tcpClients.TryAdd(client, true))
{
var clientTask = Task.Run(async () => await HandleClient(client));
clientTasks.Add(clientTask);
}
}
catch
{
// keep things quiet
}
}
}
finally
{
logger?.LogTrace("ModbusServer.WaitForClient leave");
}
}
private async Task HandleClient(TcpClient client)
{
logger?.LogTrace("ModbusServer.HandleClient enter");
var endpoint = (IPEndPoint)client.Client.RemoteEndPoint;
try
{
ClientConnected?.Invoke(this, new ClientEventArgs(endpoint));
logger?.LogInformation($"Client connected: {endpoint.Address}.");
var stream = client.GetStream();
while (!stopCts.IsCancellationRequested)
{
using var requestStream = new MemoryStream();
using (var cts = new CancellationTokenSource(Timeout))
using (stopCts.Token.Register(() => cts.Cancel()))
{
try
{
byte[] header = await stream.ReadExpectedBytes(6, cts.Token);
await requestStream.WriteAsync(header, 0, header.Length, cts.Token);
byte[] bytes = header.Skip(4).Take(2).ToArray();
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
int following = BitConverter.ToUInt16(bytes, 0);
byte[] payload = await stream.ReadExpectedBytes(following, cts.Token);
await requestStream.WriteAsync(payload, 0, payload.Length, cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
continue;
}
}
try
{
var request = new Request(requestStream.GetBuffer());
var response = requestHandler?.Invoke(request, stopCts.Token);
if (response != null)
{
using var cts = new CancellationTokenSource(Timeout);
using var reg = stopCts.Token.Register(() => cts.Cancel());
try
{
byte[] bytes = response.Serialize();
await stream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
continue;
}
}
}
catch (ArgumentException ex)
{
logger?.LogWarning(ex, $"Invalid data received from {endpoint.Address}: {ex.Message}");
}
catch (NotImplementedException ex)
{
logger?.LogWarning(ex, $"Invalid data received from {endpoint.Address}: {ex.Message}");
}
}
}
catch (IOException)
{
// client connection closed
return;
}
catch (Exception ex)
{
logger?.LogError(ex, $"Unexpected error ({ex.GetType().Name}) occurred: {ex.GetMessage()}");
}
finally
{
ClientDisconnected?.Invoke(this, new ClientEventArgs(endpoint));
logger?.LogInformation($"Client disconnected: {endpoint.Address}");
client.Dispose();
tcpClients.TryRemove(client, out _);
logger?.LogTrace("ModbusServer.HandleClient leave");
}
}
private Response HandleRequest(Request request, CancellationToken cancellationToken)
{
// The device is not known => no response to send.
if (!modbusDevices.ContainsKey(request.DeviceId))
return null;
return request.Function switch
{
FunctionCode.ReadCoils => HandleReadCoils(request),
FunctionCode.ReadDiscreteInputs => HandleReadDiscreteInputs(request),
FunctionCode.ReadHoldingRegisters => HandleReadHoldingRegisters(request),
FunctionCode.ReadInputRegisters => HandleReadInputRegisters(request),
FunctionCode.WriteSingleCoil => HandleWriteSingleCoil(request),
FunctionCode.WriteSingleRegister => HandleWritSingleRegister(request),
FunctionCode.WriteMultipleCoils => HandleWriteMultipleCoils(request),
FunctionCode.WriteMultipleRegisters => HandleWriteMultipleRegisters(request),
FunctionCode.EncapsulatedInterface => HandleEncapsulatedInterface(request),
_ => new Response(request)
{
ErrorCode = ErrorCode.IllegalFunction
}
};
}
#endregion Server
#region Function implementation
#region Read requests
private Response HandleReadCoils(Request request)
{
var response = new Response(request);
try
{
if (request.Count < Consts.MinCount || request.Count > Consts.MaxCoilCountRead)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
int len = (int)Math.Ceiling(request.Count / 8.0);
response.Data = new DataBuffer(len);
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
if (GetCoil(request.DeviceId, addr).BoolValue)
{
int posByte = i / 8;
int posBit = i % 8;
byte mask = (byte)Math.Pow(2, posBit);
response.Data[posByte] = (byte)(response.Data[posByte] | mask);
}
}
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleReadDiscreteInputs(Request request)
{
var response = new Response(request);
try
{
if (request.Count < Consts.MinCount || request.Count > Consts.MaxCoilCountRead)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
int len = (int)Math.Ceiling(request.Count / 8.0);
response.Data = new DataBuffer(len);
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
if (GetDiscreteInput(request.DeviceId, addr).BoolValue)
{
int posByte = i / 8;
int posBit = i % 8;
byte mask = (byte)Math.Pow(2, posBit);
response.Data[posByte] = (byte)(response.Data[posByte] | mask);
}
}
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleReadHoldingRegisters(Request request)
{
var response = new Response(request);
try
{
if (request.Count < Consts.MinCount || request.Count > Consts.MaxRegisterCountRead)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
response.Data = new DataBuffer(request.Count * 2);
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
var reg = GetHoldingRegister(request.DeviceId, addr);
response.Data.SetUInt16(i * 2, reg.RegisterValue);
}
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleReadInputRegisters(Request request)
{
var response = new Response(request);
try
{
if (request.Count < Consts.MinCount || request.Count > Consts.MaxRegisterCountRead)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
response.Data = new DataBuffer(request.Count * 2);
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
var reg = GetInputRegister(request.DeviceId, addr);
response.Data.SetUInt16(i * 2, reg.RegisterValue);
}
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleEncapsulatedInterface(Request request)
{
var response = new Response(request);
if (request.MEIType != MEIType.ReadDeviceInformation)
{
response.ErrorCode = ErrorCode.IllegalFunction;
return response;
}
if ((byte)request.MEIObject < 0x00 ||
(byte)request.MEIObject > 0xFF ||
((byte)request.MEIObject > 0x06 && (byte)request.MEIObject < 0x80))
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
return response;
}
if (request.MEICategory < DeviceIDCategory.Basic || request.MEICategory > DeviceIDCategory.Individual)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
return response;
}
string version = astembly.Getastembly(typeof(ModbusServer))
.GetCustomAttribute()
.InformationalVersion;
response.MEIType = request.MEIType;
response.MEICategory = request.MEICategory;
var dict = new Dictionary();
switch (request.MEICategory)
{
case DeviceIDCategory.Basic:
response.ConformityLevel = 0x01;
dict.Add(DeviceIDObject.VendorName, "AM.WD");
dict.Add(DeviceIDObject.ProductCode, "AM.WD-MBS-TCP");
dict.Add(DeviceIDObject.MajorMinorRevision, version);
break;
case DeviceIDCategory.Regular:
response.ConformityLevel = 0x02;
dict.Add(DeviceIDObject.VendorName, "AM.WD");
dict.Add(DeviceIDObject.ProductCode, "AM.WD-MBS-TCP");
dict.Add(DeviceIDObject.MajorMinorRevision, version);
dict.Add(DeviceIDObject.VendorUrl, "https://github.com/AndreasAmMueller/Modbus");
dict.Add(DeviceIDObject.ProductName, "AM.WD Modbus");
dict.Add(DeviceIDObject.ModelName, "TCP Server");
dict.Add(DeviceIDObject.UserApplicationName, "Modbus TCP Server");
break;
case DeviceIDCategory.Extended:
response.ConformityLevel = 0x03;
dict.Add(DeviceIDObject.VendorName, "AM.WD");
dict.Add(DeviceIDObject.ProductCode, "AM.WD-MBS-TCP");
dict.Add(DeviceIDObject.MajorMinorRevision, version);
dict.Add(DeviceIDObject.VendorUrl, "https://github.com/AndreasAmMueller/Modbus");
dict.Add(DeviceIDObject.ProductName, "AM.WD Modbus");
dict.Add(DeviceIDObject.ModelName, "TCP Server");
dict.Add(DeviceIDObject.UserApplicationName, "Modbus TCP Server");
break;
case DeviceIDCategory.Individual:
switch (request.MEIObject)
{
case DeviceIDObject.VendorName:
response.ConformityLevel = 0x81;
dict.Add(DeviceIDObject.VendorName, "AM.WD");
break;
case DeviceIDObject.ProductCode:
response.ConformityLevel = 0x81;
dict.Add(DeviceIDObject.ProductCode, "AM.WD-MBS-TCP");
break;
case DeviceIDObject.MajorMinorRevision:
response.ConformityLevel = 0x81;
dict.Add(DeviceIDObject.MajorMinorRevision, version);
break;
case DeviceIDObject.VendorUrl:
response.ConformityLevel = 0x82;
dict.Add(DeviceIDObject.VendorUrl, "https://github.com/AndreasAmMueller/Modbus");
break;
case DeviceIDObject.ProductName:
response.ConformityLevel = 0x82;
dict.Add(DeviceIDObject.ProductName, "AM.WD Modbus");
break;
case DeviceIDObject.ModelName:
response.ConformityLevel = 0x82;
dict.Add(DeviceIDObject.ModelName, "TCP Server");
break;
case DeviceIDObject.UserApplicationName:
response.ConformityLevel = 0x82;
dict.Add(DeviceIDObject.UserApplicationName, "Modbus TCP Server");
break;
default:
response.ConformityLevel = 0x83;
dict.Add(request.MEIObject, "Custom Data for " + request.MEIObject);
break;
}
break;
}
response.MoreRequestsNeeded = false;
response.NextObjectId = 0x00;
response.ObjectCount = (byte)dict.Count;
response.Data = new DataBuffer();
foreach (var kvp in dict)
{
byte[] bytes = Encoding.ASCII.GetBytes(kvp.Value);
response.Data.AddByte((byte)kvp.Key);
response.Data.AddByte((byte)bytes.Length);
response.Data.AddBytes(bytes);
}
return response;
}
#endregion Read requests
#region Write requests
private Response HandleWriteSingleCoil(Request request)
{
var response = new Response(request);
try
{
ushort val = request.Data.GetUInt16(0);
if (val != 0x0000 && val != 0xFF00)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
var coil = new Coil { Address = request.Address, BoolValue = (val > 0) };
SetCoil(request.DeviceId, coil);
response.Data = request.Data;
InputWritten?.Invoke(this, new WriteEventArgs(request.DeviceId, coil));
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleWritSingleRegister(Request request)
{
var response = new Response(request);
try
{
ushort val = request.Data.GetUInt16(0);
if (request.Address < Consts.MinAddress || request.Address > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
var register = new Register { Address = request.Address, RegisterValue = val, Type = ModbusObjectType.HoldingRegister };
SetHoldingRegister(request.DeviceId, register);
response.Data = request.Data;
RegisterWritten?.Invoke(this, new WriteEventArgs(request.DeviceId, register));
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
}
catch
{
return null;
}
return response;
}
private Response HandleWriteMultipleCoils(Request request)
{
try
{
var response = new Response(request);
int numBytes = (int)Math.Ceiling(request.Count / 8.0);
if (request.Count < Consts.MinCount || request.Count > Consts.MaxCoilCountWrite || numBytes != request.Data.Length)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
var list = new List();
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
int posByte = i / 8;
int posBit = i % 8;
byte mask = (byte)Math.Pow(2, posBit);
int val = request.Data[posByte] & mask;
var coil = new Coil { Address = addr, BoolValue = (val > 0) };
SetCoil(request.DeviceId, coil);
list.Add(coil);
}
InputWritten?.Invoke(this, new WriteEventArgs(request.DeviceId, list));
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
return response;
}
catch
{
return null;
}
}
private Response HandleWriteMultipleRegisters(Request request)
{
try
{
var response = new Response(request);
//request.Data contains [byte count] [data]..[data]
if (request.Count < Consts.MinCount || request.Count > Consts.MaxRegisterCountWrite || request.Count * 2 != request.Data.Length - 1)
{
response.ErrorCode = ErrorCode.IllegalDataValue;
}
else if (request.Address < Consts.MinAddress || request.Address + request.Count > Consts.MaxAddress)
{
response.ErrorCode = ErrorCode.IllegalDataAddress;
}
else
{
try
{
var list = new List();
for (int i = 0; i < request.Count; i++)
{
ushort addr = (ushort)(request.Address + i);
ushort val = request.Data.GetUInt16(i * 2 + 1);
var register = new Register { Address = addr, RegisterValue = val, Type = ModbusObjectType.HoldingRegister };
SetHoldingRegister(request.DeviceId, register);
list.Add(register);
}
RegisterWritten?.Invoke(this, new WriteEventArgs(request.DeviceId, list));
}
catch
{
response.ErrorCode = ErrorCode.SlaveDeviceFailure;
}
}
return response;
}
catch
{
return null;
}
}
#endregion Write requests
#endregion Function implementation
#endregion Private methods
#region IDisposable implementation
private bool isDisposed;
///
public void Dispose()
{
if (isDisposed)
return;
isDisposed = true;
stopCts.Cancel();
tcpListener.Stop();
foreach (var client in tcpClients.Keys)
client.Dispose();
Task.WaitAll(clientTasks.ToArray());
Task.WaitAll(clientConnect);
tcpClients.Clear();
IsRunning = false;
}
///
/// Checks whether the object is already disposed.
///
protected void CheckDisposed()
{
if (isDisposed)
throw new ObjectDisposedException(GetType().FullName);
}
#endregion IDisposable implementation
}
///
/// Provides connection information of a client.
///
public clast ClientEventArgs : EventArgs
{
///
/// Initializes a new instance of the clast.
///
/// The client end point.
public ClientEventArgs(IPEndPoint endpoint)
{
EndPoint = endpoint;
}
///
/// Gets the endpoint information of the client.
///
public IPEndPoint EndPoint { get; }
}
}