/// <summary> /// Reads the specified number of values of type <typeparamref name="TRead"/> from and writes the provided array of type <typeparamref name="TWrite"/> to the holding registers. The write operation is performed before the read. /// </summary> /// <typeparam name="TRead">Determines the type of the returned data.</typeparam> /// <typeparam name="TWrite">Determines the type of the provided data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="readStartingAddress">The holding register start address for the read operation.</param> /// <param name="readCount">The number of elements of type <typeparamref name="TRead"/> to read.</param> /// <param name="writeStartingAddress">The holding register start address for the write operation.</param> /// <param name="dataset">The data of type <typeparamref name="TWrite"/> to write to the server.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> public async Task <Memory <TRead> > ReadWriteMultipleRegistersAsync <TRead, TWrite>(int unitIdentifier, int readStartingAddress, int readCount, int writeStartingAddress, TWrite[] dataset, CancellationToken cancellationToken = default) where TRead : unmanaged where TWrite : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var readStartingAddress_converted = this.ConvertUshort(readStartingAddress); var readCount_converted = this.ConvertUshort(readCount); var writeStartingAddress_converted = this.ConvertUshort(writeStartingAddress); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset.AsSpan()); } var readQuantity = this.ConvertSize <TRead>(readCount_converted); var byteData = MemoryMarshal.Cast <TWrite, byte>(dataset).ToArray(); var dataset2 = SpanExtensions.Cast <byte, TRead>(await this.ReadWriteMultipleRegistersAsync(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData).ConfigureAwait(false)); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset2); } return(dataset2); }
/// <summary> /// Reads the specified number of values of type <typeparamref name="TRead"/> from and writes the provided array of type <typeparamref name="TWrite"/> to the holding registers. The write operation is performed before the read. /// </summary> /// <typeparam name="TRead">Determines the type of the returned data.</typeparam> /// <typeparam name="TWrite">Determines the type of the provided data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="readStartingAddress">The holding register start address for the read operation.</param> /// <param name="readCount">The number of elements of type <typeparamref name="TRead"/> to read.</param> /// <param name="writeStartingAddress">The holding register start address for the write operation.</param> /// <param name="dataset">The data of type <typeparamref name="TWrite"/> to write to the server.</param> public Span <TRead> ReadWriteMultipleRegisters <TRead, TWrite>(int unitIdentifier, int readStartingAddress, int readCount, int writeStartingAddress, TWrite[] dataset) where TRead : unmanaged where TWrite : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var readStartingAddress_converted = this.ConvertUshort(readStartingAddress); var readCount_converted = this.ConvertUshort(readCount); var writeStartingAddress_converted = this.ConvertUshort(writeStartingAddress); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset.AsSpan()); } var readQuantity = this.ConvertSize <TRead>(readCount_converted); var byteData = MemoryMarshal.Cast <TWrite, byte>(dataset).ToArray(); var dataset2 = MemoryMarshal.Cast <byte, TRead>(this.ReadWriteMultipleRegisters(unitIdentifier_converted, readStartingAddress_converted, readQuantity, writeStartingAddress_converted, byteData)); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset2); } return(dataset2); }
public static T SwitchEndianness <T>(T value) where T : unmanaged { var data = new T[] { value }; ModbusUtils.SwitchEndianness(data.AsSpan()); return(data[0]); }
public static T SwitchEndianness <T>(T value) where T : unmanaged { Span <T> data = stackalloc T[] { value }; ModbusUtils.SwitchEndianness(data); return(data[0]); }
/// <summary> /// Writes the provided array of type <typeparamref name="T"/> to the holding registers. /// </summary> /// <typeparam name="T">Determines the type of the provided data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The holding register start address for the write operation.</param> /// <param name="dataset">The data of type <typeparamref name="T"/> to write to the server.</param> public void WriteMultipleRegisters <T>(byte unitIdentifier, ushort startingAddress, T[] dataset) where T : unmanaged { if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset.AsSpan()); } this.WriteMultipleRegisters(unitIdentifier, startingAddress, MemoryMarshal.Cast <T, byte>(dataset).ToArray()); }
/// <summary> /// Connect to the specified <paramref name="remoteEndpoint"/>. /// </summary> /// <param name="remoteEndpoint">The IP address and optional port of the end unit. Examples: "192.168.0.1", "192.168.0.1:502", "::1", "[::1]:502". The default port is 502.</param> /// <param name="endianness">Specifies the endianness of the data exchanged with the Modbus server.</param> public void Connect(string remoteEndpoint, ModbusEndianness endianness) { if (!ModbusUtils.TryParseEndpoint(remoteEndpoint.AsSpan(), out var parsedRemoteEndpoint)) { throw new FormatException("An invalid IPEndPoint was specified."); } this.Connect(parsedRemoteEndpoint, endianness); }
/// <summary> /// Writes the provided <paramref name="value"/> to the holding registers. /// </summary> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="registerAddress">The holding register address for the write operation.</param> /// <param name="value">The value to write to the server.</param> public void WriteSingleRegister(byte unitIdentifier, ushort registerAddress, ushort value) { if (this.SwapBytes) { value = ModbusUtils.SwitchEndianness(value); } this.WriteSingleRegister(unitIdentifier, registerAddress, MemoryMarshal.Cast <ushort, byte>(new[] { value }).ToArray()); }
/// <summary> /// Reads the specified number of values of type <typeparamref name="T"/> from the input registers. /// </summary> /// <typeparam name="T">Determines the type of the returned data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The input register start address for the read operation.</param> /// <param name="count">The number of elements of type <typeparamref name="T"/> to read.</param> public Span <T> ReadInputRegisters <T>(byte unitIdentifier, ushort startingAddress, ushort count) where T : unmanaged { var dataset = MemoryMarshal.Cast <byte, T>(this.ReadInputRegisters(unitIdentifier, startingAddress, this.ConvertSize <T>(count))); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset); } return(dataset); }
/// <summary> /// Writes the provided array of type <typeparamref name="T"/> to the holding registers. /// </summary> /// <typeparam name="T">Determines the type of the provided data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The holding register start address for the write operation.</param> /// <param name="dataset">The data of type <typeparamref name="T"/> to write to the server.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> public async Task WriteMultipleRegistersAsync <T>(int unitIdentifier, int startingAddress, T[] dataset, CancellationToken cancellationToken = default) where T : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var startingAddress_converted = this.ConvertUshort(startingAddress); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset.AsSpan()); } await this.WriteMultipleRegistersAsync(unitIdentifier_converted, startingAddress_converted, MemoryMarshal.Cast <T, byte>(dataset).ToArray()).ConfigureAwait(false); }
/// <summary> /// Writes the provided <paramref name="value"/> to the holding registers. /// </summary> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="registerAddress">The holding register address for the write operation.</param> /// <param name="value">The value to write to the server.</param> public void WriteSingleRegister(int unitIdentifier, int registerAddress, ushort value) { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var registerAddress_converted = this.ConvertUshort(registerAddress); if (this.SwapBytes) { value = ModbusUtils.SwitchEndianness(value); } this.WriteSingleRegister(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast <ushort, byte>(new[] { value }).ToArray()); }
/// <summary> /// Writes the provided array of type <typeparamref name="T"/> to the holding registers. /// </summary> /// <typeparam name="T">Determines the type of the provided data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The holding register start address for the write operation.</param> /// <param name="dataset">The data of type <typeparamref name="T"/> to write to the server.</param> public void WriteMultipleRegisters <T>(int unitIdentifier, int startingAddress, T[] dataset) where T : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var startingAddress_converted = this.ConvertUshort(startingAddress); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset.AsSpan()); } this.WriteMultipleRegisters(unitIdentifier_converted, startingAddress_converted, MemoryMarshal.Cast <T, byte>(dataset).ToArray()); }
/// <summary> /// Writes the provided <paramref name="value"/> to the holding registers. /// </summary> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="registerAddress">The holding register address for the write operation.</param> /// <param name="value">The value to write to the server.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> public async Task WriteSingleRegisterAsync(int unitIdentifier, int registerAddress, ushort value, CancellationToken cancellationToken = default) { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var registerAddress_converted = this.ConvertUshort(registerAddress); if (this.SwapBytes) { value = ModbusUtils.SwitchEndianness(value); } await this.WriteSingleRegisterAsync(unitIdentifier_converted, registerAddress_converted, MemoryMarshal.Cast <ushort, byte>(new[] { value }).ToArray()).ConfigureAwait(false); }
public static void SetBigEndian <T>(this Span <short> buffer, ushort address, T value) where T : unmanaged { var byteBuffer = MemoryMarshal .AsBytes(buffer) .Slice(address * 2); if (BitConverter.IsLittleEndian) { value = ModbusUtils.SwitchEndianness(value); } Unsafe.WriteUnaligned(ref byteBuffer.GetPinnableReference(), value); }
public static bool DetectFrame(byte unitIdentifier, Memory <byte> frame) { byte newUnitIdentifier; /* Correct response frame (min. 6 bytes) * 00 Unit Identifier * 01 Function Code * 02 Byte count * 03 Minimum of 1 byte * 04 CRC Byte 1 * 05 CRC Byte 2 */ /* Error response frame (5 bytes) * 00 Unit Identifier * 01 Function Code + 0x80 * 02 Exception Code * 03 CRC Byte 1 * 04 CRC Byte 2 */ var span = frame.Span; if (span.Length < 5) { return(false); } if (unitIdentifier != 255) // 255 means "skip unit identifier check" { newUnitIdentifier = span[0]; if (newUnitIdentifier != unitIdentifier) { return(false); } } // CRC check var crcBytes = span.Slice(span.Length - 2, 2); var actualCRC = unchecked ((ushort)((crcBytes[1] << 8) + crcBytes[0])); var expectedCRC = ModbusUtils.CalculateCRC(frame.Slice(0, frame.Length - 2)); if (actualCRC != expectedCRC) { return(false); } return(true); }
/// <summary> /// Reads the specified number of values of type <typeparamref name="T"/> from the holding registers. /// </summary> /// <typeparam name="T">Determines the type of the returned data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The holding register start address for the read operation.</param> /// <param name="count">The number of elements of type <typeparamref name="T"/> to read.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> public async Task <Memory <T> > ReadHoldingRegistersAsync <T>(int unitIdentifier, int startingAddress, int count, CancellationToken cancellationToken = default) where T : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var startingAddress_converted = this.ConvertUshort(startingAddress); var count_converted = this.ConvertUshort(count); var dataset = SpanExtensions.Cast <byte, T>(await this.ReadHoldingRegistersAsync(unitIdentifier_converted, startingAddress_converted, this.ConvertSize <T>(count_converted)).ConfigureAwait(false)); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset); } return(dataset); }
/// <summary> /// Reads the specified number of values of type <typeparamref name="T"/> from the input registers. /// </summary> /// <typeparam name="T">Determines the type of the returned data.</typeparam> /// <param name="unitIdentifier">The unit identifier is used to communicate via devices such as bridges, routers and gateways that use a single IP address to support multiple independent Modbus end units. Thus, the unit identifier is the address of a remote slave connected on a serial line or on other buses. Use the default values 0x00 or 0xFF when communicating to a Modbus server that is directly connected to a TCP/IP network.</param> /// <param name="startingAddress">The input register start address for the read operation.</param> /// <param name="count">The number of elements of type <typeparamref name="T"/> to read.</param> public Span <T> ReadInputRegisters <T>(int unitIdentifier, int startingAddress, int count) where T : unmanaged { var unitIdentifier_converted = this.ConvertUnitIdentifier(unitIdentifier); var startingAddress_converted = this.ConvertUshort(startingAddress); var count_converted = this.ConvertUshort(count); var dataset = MemoryMarshal.Cast <byte, T>( this.ReadInputRegisters(unitIdentifier_converted, startingAddress_converted, this.ConvertSize <T>(count_converted))); if (this.SwapBytes) { ModbusUtils.SwitchEndianness(dataset); } return(dataset); }
public static T GetBigEndian <T>(this Span <short> buffer, ushort address) where T : unmanaged { var byteBuffer = MemoryMarshal .AsBytes(buffer) .Slice(address * 2); var value = Unsafe.ReadUnaligned <T>(ref byteBuffer.GetPinnableReference()); if (BitConverter.IsLittleEndian) { value = ModbusUtils.SwitchEndianness(value); } return(value); }
private async Task <bool> InternalReceiveRequestAsync() { this.Length = 0; try { while (true) { this.Length += await _serialPort.ReadAsync(this.FrameBuffer.Buffer, this.Length, this.FrameBuffer.Buffer.Length - this.Length, this.CTS.Token); // full frame received if (ModbusUtils.DetectFrame(255, this.FrameBuffer.Buffer.AsMemory(0, this.Length))) { this.FrameBuffer.Reader.BaseStream.Seek(0, SeekOrigin.Begin); // read unit identifier this.UnitIdentifier = this.FrameBuffer.Reader.ReadByte(); break; } else { // reset length because one or more chunks of data were received and written to // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (this.Length == this.FrameBuffer.Buffer.Length) { this.Length = 0; } } } } catch (TimeoutException) { this.Length = 0; } // make sure that the incoming frame is actually adressed to this server if (this.UnitIdentifier == this.ModbusRtuServer.UnitIdentifier) { this.LastRequest.Restart(); return(true); } else { return(false); } }
public static void SetBigEndian <T>(this Span <short> buffer, int address, T value) where T : unmanaged { if (!(0 <= address && address <= ushort.MaxValue)) { throw new Exception(ErrorMessage.Modbus_InvalidValueUShort); } var byteBuffer = MemoryMarshal .AsBytes(buffer) .Slice(address * 2); if (BitConverter.IsLittleEndian) { value = ModbusUtils.SwitchEndianness(value); } Unsafe.WriteUnaligned(ref byteBuffer.GetPinnableReference(), value); }
protected override int WriteFrame(Action extendFrame) { int frameLength; ushort crc; this.FrameBuffer.Writer.Seek(0, SeekOrigin.Begin); // add unit identifier this.FrameBuffer.Writer.Write(this.UnitIdentifier); // add PDU extendFrame(); // add CRC frameLength = unchecked ((int)this.FrameBuffer.Writer.BaseStream.Position); crc = ModbusUtils.CalculateCRC(this.FrameBuffer.Buffer.AsMemory(0, frameLength)); this.FrameBuffer.Writer.Write(crc); return(frameLength + 2); }
public static T GetMidLittleEndian <T>(this Span <short> buffer, int address) where T : unmanaged { if (!(0 <= address && address <= ushort.MaxValue)) { throw new Exception(ErrorMessage.Modbus_InvalidValueUShort); } var byteBuffer = MemoryMarshal .AsBytes(buffer) .Slice(address * 2); var value = Unsafe.ReadUnaligned <T>(ref byteBuffer.GetPinnableReference()); value = ModbusUtils.ConvertBetweenLittleEndianAndMidLittleEndian(value); if (!BitConverter.IsLittleEndian) { value = ModbusUtils.SwitchEndianness(value); } return(value); }
private protected override Span <byte> TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action <ExtendedBinaryWriter> extendFrame) { int messageLength; int frameLength; byte rawFunctionCode; ushort crc; int mbapHeaderLength = 4; // build request if (!(0 <= unitIdentifier && unitIdentifier <= 247)) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidUnitIdentifier); } // special case: broadcast (only for write commands) if (unitIdentifier == 0) { switch (functionCode) { case ModbusFunctionCode.WriteMultipleRegisters: case ModbusFunctionCode.WriteSingleCoil: case ModbusFunctionCode.WriteSingleRegister: case ModbusFunctionCode.WriteMultipleCoils: case ModbusFunctionCode.WriteFileRecord: case ModbusFunctionCode.MaskWriteRegister: break; default: throw new ModbusException(ErrorMessage.Modbus_InvalidUseOfBroadcast); } } _frameBuffer.Writer.Seek(0, SeekOrigin.Begin); _frameBuffer.Writer.Write(unitIdentifier); // 00 Unit Identifier extendFrame(_frameBuffer.Writer); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; // add CRC crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory().Slice(0, frameLength)); _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; // build message _messageBuffer.Writer.Seek(0, SeekOrigin.Begin); if (!SwapBytes) { _messageBuffer.Writer.WriteReverse(ProtocolIdentifier); //2b _messageBuffer.Writer.WriteReverse((ushort)frameLength); //2b } else { _messageBuffer.Writer.Write(ProtocolIdentifier); //2b _messageBuffer.Writer.Write((ushort)frameLength); //2b } _messageBuffer.Writer.Write(_frameBuffer.Buffer, 0, frameLength); //framelength messageLength = frameLength + mbapHeaderLength; // send request _serialPort.Write(_messageBuffer.Buffer, 0, messageLength); // special case: broadcast (only for write commands) if (unitIdentifier == 0) { return(_messageBuffer.Buffer.AsSpan(0, 0)); } // wait for and process response messageLength = 0; _messageBuffer.Reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { messageLength += _serialPort.Read(_messageBuffer.Buffer, messageLength, _messageBuffer.Buffer.Length - messageLength); if (ModbusUtils.DetectFrame(unitIdentifier, _messageBuffer.Buffer.AsMemory().Slice(mbapHeaderLength, messageLength - mbapHeaderLength))) { break; } else { // reset length because one or more chunks of data were received and written to // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (messageLength == _messageBuffer.Buffer.Length) { messageLength = 0; } } } //write message content to framebuffer _frameBuffer.Writer.BaseStream.Seek(0, SeekOrigin.Begin); _frameBuffer.Writer.Write(_messageBuffer.Buffer.AsSpan().Slice(mbapHeaderLength).ToArray(), 0, messageLength - mbapHeaderLength); frameLength = messageLength - mbapHeaderLength; unitIdentifier = _frameBuffer.Reader.ReadByte(); rawFunctionCode = _frameBuffer.Reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) { this.ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer[2]); } else if (rawFunctionCode != (byte)functionCode) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); } return(_frameBuffer.Buffer.AsSpan(1, frameLength - 3)); }
public static void SwitchEndianness <T>(Memory <T> dataset) where T : unmanaged { ModbusUtils.SwitchEndianness(dataset.Span); }
private protected override async Task <Memory <byte> > TransceiveFrameAsync(byte unitIdentifier, ModbusFunctionCode functionCode, Action <ExtendedBinaryWriter> extendFrame, CancellationToken cancellationToken = default) { int frameLength; byte rawFunctionCode; ushort crc; // build request if (!(0 <= unitIdentifier && unitIdentifier <= 247)) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidUnitIdentifier); } // special case: broadcast (only for write commands) if (unitIdentifier == 0) { switch (functionCode) { case ModbusFunctionCode.WriteMultipleRegisters: case ModbusFunctionCode.WriteSingleCoil: case ModbusFunctionCode.WriteSingleRegister: case ModbusFunctionCode.WriteMultipleCoils: case ModbusFunctionCode.WriteFileRecord: case ModbusFunctionCode.MaskWriteRegister: break; default: throw new ModbusException(ErrorMessage.Modbus_InvalidUseOfBroadcast); } } _frameBuffer.Writer.Seek(0, SeekOrigin.Begin); _frameBuffer.Writer.Write(unitIdentifier); // 00 Unit Identifier extendFrame(_frameBuffer.Writer); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; // add CRC crc = ModbusUtils.CalculateCRC(_frameBuffer.Buffer.AsMemory().Slice(0, frameLength)); _frameBuffer.Writer.Write(crc); frameLength = (int)_frameBuffer.Writer.BaseStream.Position; // send request await _serialPort.WriteAsync(_frameBuffer.Buffer, 0, frameLength, cancellationToken).ConfigureAwait(false); // special case: broadcast (only for write commands) if (unitIdentifier == 0) { return(_frameBuffer.Buffer.AsMemory(0, 0)); } // wait for and process response frameLength = 0; _frameBuffer.Reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { frameLength += await _serialPort.ReadAsync(_frameBuffer.Buffer, frameLength, _frameBuffer.Buffer.Length - frameLength, cancellationToken).ConfigureAwait(false); if (ModbusUtils.DetectFrame(unitIdentifier, _frameBuffer.Buffer.AsMemory().Slice(0, frameLength))) { break; } else { // reset length because one or more chunks of data were received and written to // the buffer, but no valid Modbus frame could be detected and now the buffer is full if (frameLength == _frameBuffer.Buffer.Length) { frameLength = 0; } } } unitIdentifier = _frameBuffer.Reader.ReadByte(); rawFunctionCode = _frameBuffer.Reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) { this.ProcessError(functionCode, (ModbusExceptionCode)_frameBuffer.Buffer[2]); } else if (rawFunctionCode != (byte)functionCode) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); } return(_frameBuffer.Buffer.AsMemory(1, frameLength - 3)); }