using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using NModbus.Data; using NModbus.Message; namespace NModbus.Device { /// /// Modbus master device. /// public abstract class ModbusMaster : ModbusDevice, IModbusMaster { protected ModbusMaster(IModbusTransport transport) : base(transport) { } /// /// Reads from 1 to 2000 contiguous coils status. /// /// Address of device to read values from. /// Address to begin reading. /// Number of coils to read. /// Coils status. public bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000); var request = new ReadCoilsInputsRequest( ModbusFunctionCodes.ReadCoils, slaveAddress, startAddress, numberOfPoints); return PerformReadDiscretes(request); } /// /// Asynchronously reads from 1 to 2000 contiguous coils status. /// /// Address of device to read values from. /// Address to begin reading. /// Number of coils to read. /// A task that represents the asynchronous read operation. public Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000); var request = new ReadCoilsInputsRequest( ModbusFunctionCodes.ReadCoils, slaveAddress, startAddress, numberOfPoints); return PerformReadDiscretesAsync(request); } /// /// Reads from 1 to 2000 contiguous discrete input status. /// /// Address of device to read values from. /// Address to begin reading. /// Number of discrete inputs to read. /// Discrete inputs status. public bool[] ReadInputs(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000); var request = new ReadCoilsInputsRequest( ModbusFunctionCodes.ReadInputs, slaveAddress, startAddress, numberOfPoints); return PerformReadDiscretes(request); } /// /// Asynchronously reads from 1 to 2000 contiguous discrete input status. /// /// Address of device to read values from. /// Address to begin reading. /// Number of discrete inputs to read. /// A task that represents the asynchronous read operation. public Task ReadInputsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 2000); var request = new ReadCoilsInputsRequest( ModbusFunctionCodes.ReadInputs, slaveAddress, startAddress, numberOfPoints); return PerformReadDiscretesAsync(request); } /// /// Reads contiguous block of holding registers. /// /// Address of device to read values from. /// Address to begin reading. /// Number of holding registers to read. /// Holding registers status. public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125); var request = new ReadHoldingInputRegistersRequest( ModbusFunctionCodes.ReadHoldingRegisters, slaveAddress, startAddress, numberOfPoints); return PerformReadRegisters(request); } /// /// Asynchronously reads contiguous block of holding registers. /// /// Address of device to read values from. /// Address to begin reading. /// Number of holding registers to read. /// A task that represents the asynchronous read operation. public Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125); var request = new ReadHoldingInputRegistersRequest( ModbusFunctionCodes.ReadHoldingRegisters, slaveAddress, startAddress, numberOfPoints); return PerformReadRegistersAsync(request); } /// /// Reads contiguous block of input registers. /// /// Address of device to read values from. /// Address to begin reading. /// Number of holding registers to read. /// Input registers status. public ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125); var request = new ReadHoldingInputRegistersRequest( ModbusFunctionCodes.ReadInputRegisters, slaveAddress, startAddress, numberOfPoints); return PerformReadRegisters(request); } /// /// Asynchronously reads contiguous block of input registers. /// /// Address of device to read values from. /// Address to begin reading. /// Number of holding registers to read. /// A task that represents the asynchronous read operation. public Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints) { ValidateNumberOfPoints("numberOfPoints", numberOfPoints, 125); var request = new ReadHoldingInputRegistersRequest( ModbusFunctionCodes.ReadInputRegisters, slaveAddress, startAddress, numberOfPoints); return PerformReadRegistersAsync(request); } /// /// Writes a single coil value. /// /// Address of the device to write to. /// Address to write value to. /// Value to write. public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value) { var request = new WriteSingleCoilRequestResponse(slaveAddress, coilAddress, value); Transport.UnicastMessage(request); } /// /// Asynchronously writes a single coil value. /// /// Address of the device to write to. /// Address to write value to. /// Value to write. /// A task that represents the asynchronous write operation. public Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value) { var request = new WriteSingleCoilRequestResponse(slaveAddress, coilAddress, value); return PerformWriteRequestAsync(request); } /// /// Writes a single holding register. /// /// Address of the device to write to. /// Address to write. /// Value to write. public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value) { var request = new WriteSingleRegisterRequestResponse( slaveAddress, registerAddress, value); Transport.UnicastMessage(request); } /// /// Asynchronously writes a single holding register. /// /// Address of the device to write to. /// Address to write. /// Value to write. /// A task that represents the asynchronous write operation. public Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value) { var request = new WriteSingleRegisterRequestResponse( slaveAddress, registerAddress, value); return PerformWriteRequestAsync(request); } /// /// Write a block of 1 to 123 contiguous 16 bit holding registers. /// /// Address of the device to write to. /// Address to begin writing values. /// Values to write. public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] data) { ValidateData("data", data, 123); var request = new WriteMultipleRegistersRequest( slaveAddress, startAddress, new RegisterCollection(data)); Transport.UnicastMessage(request); } /// /// Asynchronously writes a block of 1 to 123 contiguous registers. /// /// Address of the device to write to. /// Address to begin writing values. /// Values to write. /// A task that represents the asynchronous write operation. public Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] data) { ValidateData("data", data, 123); var request = new WriteMultipleRegistersRequest( slaveAddress, startAddress, new RegisterCollection(data)); return PerformWriteRequestAsync(request); } /// /// Writes a sequence of coils. /// /// Address of the device to write to. /// Address to begin writing values. /// Values to write. public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] data) { ValidateData("data", data, 1968); var request = new WriteMultipleCoilsRequest( slaveAddress, startAddress, new DiscreteCollection(data)); Transport.UnicastMessage(request); } /// /// Asynchronously writes a sequence of coils. /// /// Address of the device to write to. /// Address to begin writing values. /// Values to write. /// A task that represents the asynchronous write operation. public Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] data) { ValidateData("data", data, 1968); var request = new WriteMultipleCoilsRequest( slaveAddress, startAddress, new DiscreteCollection(data)); return PerformWriteRequestAsync(request); } /// /// Performs a combination of one read operation and one write operation in a single Modbus transaction. /// The write operation is performed before the read. /// /// Address of device to read values from. /// Address to begin reading (Holding registers are addressed starting at 0). /// Number of registers to read. /// Address to begin writing (Holding registers are addressed starting at 0). /// Register values to write. public ushort[] ReadWriteMultipleRegisters( byte slaveAddress, ushort startReadAddress, ushort numberOfPointsToRead, ushort startWriteAddress, ushort[] writeData) { ValidateNumberOfPoints("numberOfPointsToRead", numberOfPointsToRead, 125); ValidateData("writeData", writeData, 121); var request = new ReadWriteMultipleRegistersRequest( slaveAddress, startReadAddress, numberOfPointsToRead, startWriteAddress, new RegisterCollection(writeData)); return PerformReadRegisters(request); } /// /// Asynchronously performs a combination of one read operation and one write operation in a single Modbus transaction. /// The write operation is performed before the read. /// /// Address of device to read values from. /// Address to begin reading (Holding registers are addressed starting at 0). /// Number of registers to read. /// Address to begin writing (Holding registers are addressed starting at 0). /// Register values to write. /// A task that represents the asynchronous operation. public Task ReadWriteMultipleRegistersAsync( byte slaveAddress, ushort startReadAddress, ushort numberOfPointsToRead, ushort startWriteAddress, ushort[] writeData) { ValidateNumberOfPoints("numberOfPointsToRead", numberOfPointsToRead, 125); ValidateData("writeData", writeData, 121); var request = new ReadWriteMultipleRegistersRequest( slaveAddress, startReadAddress, numberOfPointsToRead, startWriteAddress, new RegisterCollection(writeData)); return PerformReadRegistersAsync(request); } /// /// Write a file record to the device. /// /// Address of device to write values to /// The Extended Memory file number /// The starting register address within the file /// The data to be written public void WriteFileRecord(byte slaveAdress, ushort fileNumber, ushort startingAddress, byte[] data) { ValidateMaxData("data", data, 244); var request = new WriteFileRecordRequest(slaveAdress, new FileRecordCollection( fileNumber, startingAddress, data)); Transport.UnicastMessage(request); } /// /// Executes the custom message. /// /// The type of the response. /// The request. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] [SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType")] public TResponse ExecuteCustomMessage(IModbusMessage request) where TResponse : IModbusMessage, new() { return Transport.UnicastMessage(request); } private static void ValidateData(string argumentName, T[] data, int maxDataLength) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (data.Length == 0 || data.Length > maxDataLength) { string msg = $"The length of argument {argumentName} must be between 1 and {maxDataLength} inclusive."; throw new ArgumentException(msg); } } private static void ValidateMaxData(string argumentName, T[] data, int maxDataLength) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (data.Length > maxDataLength) { string msg = $"The length of argument {argumentName} must not be greater than {maxDataLength}."; throw new ArgumentException(msg); } } private static void ValidateNumberOfPoints(string argumentName, ushort numberOfPoints, ushort maxNumberOfPoints) { if (numberOfPoints < 1 || numberOfPoints > maxNumberOfPoints) { string msg = $"Argument {argumentName} must be between 1 and {maxNumberOfPoints} inclusive."; throw new ArgumentException(msg); } } private bool[] PerformReadDiscretes(ReadCoilsInputsRequest request) { ReadCoilsInputsResponse response = Transport.UnicastMessage(request); return response.Data.Take(request.NumberOfPoints).ToArray(); } private Task PerformReadDiscretesAsync(ReadCoilsInputsRequest request) { return Task.Factory.StartNew(() => PerformReadDiscretes(request)); } private ushort[] PerformReadRegisters(ReadHoldingInputRegistersRequest request) { ReadHoldingInputRegistersResponse response = Transport.UnicastMessage(request); return response.Data.Take(request.NumberOfPoints).ToArray(); } private Task PerformReadRegistersAsync(ReadHoldingInputRegistersRequest request) { return Task.Factory.StartNew(() => PerformReadRegisters(request)); } private ushort[] PerformReadRegisters(ReadWriteMultipleRegistersRequest request) { ReadHoldingInputRegistersResponse response = Transport.UnicastMessage(request); return response.Data.Take(request.ReadRequest.NumberOfPoints).ToArray(); } private Task PerformReadRegistersAsync(ReadWriteMultipleRegistersRequest request) { return Task.Factory.StartNew(() => PerformReadRegisters(request)); } private Task PerformWriteRequestAsync(IModbusMessage request) where T : IModbusMessage, new() { return Task.Factory.StartNew(() => Transport.UnicastMessage(request)); } } }