private bool CheckRegisterBounds(ModbusFunctionCode functionCode, ushort address, ushort maxStartingAddress, ushort quantityOfRegisters, ushort maxQuantityOfRegisters) { if (this.ModbusServer.RequestValidator != null) { var result = this.ModbusServer.RequestValidator(functionCode, address, quantityOfRegisters); if (result > ModbusExceptionCode.OK) { this.WriteExceptionResponse(functionCode, result); return(false); } } if (address < 0 || address + quantityOfRegisters > maxStartingAddress) { this.WriteExceptionResponse(functionCode, ModbusExceptionCode.IllegalDataAddress); return(false); } if (quantityOfRegisters <= 0 || quantityOfRegisters > maxQuantityOfRegisters) { this.WriteExceptionResponse(functionCode, ModbusExceptionCode.IllegalDataValue); return(false); } return(true); }
public string GetParament() { ParaPack para = new ParaPack(); if (cbo_functioncode.SelectedItem != null) { ModbusFunctionCode funCode = (ModbusFunctionCode)cbo_functioncode.SelectedItem; para.AddItem("内存区", funCode.Code); } para.AddItem("偏置", this.ndOffset.Text.Trim()); if (cbo_StoreType.SelectedItem != null) { ModbusDataType datatype = (ModbusDataType)cbo_StoreType.SelectedItem; para.AddItem("数据类型", datatype.DataType.ToString()); } if (cbo_datatype.SelectedItem != null) { string datatype = (string)cbo_datatype.SelectedItem; para.AddItem("存储位置", datatype.ToString()); } para.AddItem("字节长度", this.ndCharSize.Text.Trim() == ""?"0": this.ndCharSize.Text.Trim()); para.AddItem("按位存取", this.cbPosition.Checked?"1":"0"); para.AddItem("数据位", this.ndPosition.Text.Trim() == ""?"0": this.ndPosition.Text.Trim()); return(para.ToString()); }
public WriteMultipleFunction(ModbusFunctionCode functionCode, ushort startAddress, int[] commandValues, CommandOriginType commandOrigin) : base(functionCode) { StartAddress = startAddress; CommandValues = commandValues; CommandOrigin = commandOrigin; }
public WriteSingleFunction(ModbusFunctionCode functionCode, ushort outputAddress, int commandValue, CommandOriginType commandOrigin) : base(functionCode) { OutputAddress = outputAddress; CommandValue = commandValue; CommandOrigin = commandOrigin; }
/// <summary> /// Sends a modbus telegram over the interface and waits for a response /// </summary> /// <param name="deviceAddress">Modbus device address.</param> /// <param name="fc">Function code.</param> /// <param name="timeout">Timeout in Milli seconds.</param> /// <param name="telegramLength">Total length of the telegram in bytes</param> /// <param name="desiredDataLength">Length of the desired telegram data (without fc, cs, ...) of the response in bytes. -1 for unknown.</param> /// <param name="telegramContext">Interface specific context of the telegram</param> /// <param name="dataPos">Index of the response data in the buffer.</param> /// <returns></returns> protected virtual short SendReceive(byte deviceAddress, ModbusFunctionCode fc, int timeout, short telegramLength, short desiredDataLength, object telegramContext, ref short dataPos) { lock (_SyncObject) { try { _Interface.SendTelegram(_Buffer, telegramLength); _Interface.PrepareRead(); if (deviceAddress == ModbusConst.BroadcastAddress) { return(0); } byte responseDeviceAddress; byte responseFc = 0; short dataLength = 0; while (timeout > 0) { var ts = DateTime.Now.Ticks; if (!_Interface.ReceiveTelegram(_Buffer, desiredDataLength, timeout, out telegramLength)) { throw new ModbusException(ModbusErrorCode.Timeout); } timeout -= (int)((DateTime.Now.Ticks - ts) / 10000); // if this is not the response we are waiting for wait again until time runs out if (_Interface.ParseTelegram(_Buffer, telegramLength, true, ref telegramContext, out responseDeviceAddress, out responseFc, out dataPos, out dataLength) && responseDeviceAddress == deviceAddress && (responseFc & 0x7f) == (byte)fc) { break; } if (timeout <= 0) { throw new ModbusException(ModbusErrorCode.Timeout); } } if ((responseFc & 0x80) != 0) { // error response throw new ModbusException((ModbusErrorCode)_Buffer[dataPos]); } return(dataLength); } finally { _Interface.PrepareWrite(); } } }
/// <summary> /// Handles a received message. /// </summary> /// <param name="intf">Interface by which te message was received.</param> /// <param name="deviceAddress">Address of the target device</param> /// <param name="isBroadcast">true if the message is a broadcast. For broadcast messages no reponse is sent.</param> /// <param name="telegramLength">Length of the message in bytes.</param> /// <param name="telegramContext">Interface specific message context.</param> /// <param name="fc">Function code.</param> /// <param name="dataPos">Index of function code specific data.</param> /// <param name="dataLength">Length of the function code specific data in bytes.</param> protected override void OnHandleTelegram(IModbusInterface intf, byte deviceAddress, bool isBroadcast, short telegramLength, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { var master = (GatewayMaster)_masterMap[deviceAddress]; if (master != null) { short masterTelegramLength; short masterDataPos; object masterTelegramContext = null; master.MasterInterface.CreateTelegram(master.TargetAddress, (byte)fc, dataLength, master.Buffer, out masterTelegramLength, out masterDataPos, false, ref masterTelegramContext); Array.Copy(Buffer, dataPos, master.Buffer, masterDataPos, dataLength); try { var masterDataLength = SendReceive(master.MasterInterface, master.Buffer, master.TargetAddress, fc, master.Timeout, masterTelegramLength, -1, masterTelegramContext, ref masterDataPos); intf.CreateTelegram(deviceAddress, (byte)fc, masterDataLength, Buffer, out telegramLength, out dataPos, true, ref telegramContext); Array.Copy(master.Buffer, masterDataPos, Buffer, dataPos, masterDataLength); intf.SendTelegram(Buffer, telegramLength); } catch (ModbusException ex) { try { if (ex.ErrorCode == ModbusErrorCode.Timeout) { SendErrorResult(intf, false, deviceAddress, telegramContext, fc, ModbusErrorCode.GatewayTargetDeviceFailedToRespond); } else if (((ushort)ex.ErrorCode & 0xff00) != 0) { SendErrorResult(intf, false, deviceAddress, telegramContext, fc, ModbusErrorCode.GatewayTargetDeviceFailedToRespond); } else { SendErrorResult(intf, false, deviceAddress, telegramContext, fc, ex.ErrorCode); } } // ReSharper disable once EmptyGeneralCatchClause catch { } } catch { try { SendErrorResult(intf, false, deviceAddress, telegramContext, fc, ModbusErrorCode.GatewayPathUnavailable); } // ReSharper disable once EmptyGeneralCatchClause catch { } } } else { base.OnHandleTelegram(intf, deviceAddress, isBroadcast, telegramLength, telegramContext, fc, dataPos, dataLength); } }
/// <summary> /// Sends a modbus telegram over the interface and waits for a response /// </summary> /// <param name="deviceAddress">Modbus device address.</param> /// <param name="fc">Function code.</param> /// <param name="timeout">Timeout in Milli seconds.</param> /// <param name="telegramLength">Total length of the telegram in bytes</param> /// <param name="desiredDataLength">Length of the desired telegram data (without fc, cs, ...) of the response in bytes. -1 for unknown.</param> /// <param name="telegramContext">Interface specific context of the telegram</param> /// <param name="dataPos">Index of the response data in the buffer.</param> /// <returns></returns> protected virtual short SendReceive(byte deviceAddress, ModbusFunctionCode fc, int timeout, short telegramLength, short desiredDataLength, object telegramContext, ref short dataPos) { lock (_SyncObject) { try { _Interface.SendTelegram(_Buffer, telegramLength); _Interface.PrepareRead(); if (deviceAddress == ModbusConst.BroadcastAddress) { return 0; } byte responseDeviceAddress; byte responseFc = 0; short dataLength = 0; while (timeout > 0) { var ts = DateTime.Now.Ticks; if (!_Interface.ReceiveTelegram(_Buffer, desiredDataLength, timeout, out telegramLength)) { throw new ModbusException(ModbusErrorCode.Timeout); } timeout -= (int) ((DateTime.Now.Ticks - ts)/10000); // if this is not the response we are waiting for wait again until time runs out if (_Interface.ParseTelegram(_Buffer, telegramLength, true, ref telegramContext, out responseDeviceAddress, out responseFc, out dataPos, out dataLength) && responseDeviceAddress == deviceAddress && (responseFc & 0x7f) == (byte) fc) { break; } if (timeout <= 0) { throw new ModbusException(ModbusErrorCode.Timeout); } } if ((responseFc & 0x80) != 0) { // error response throw new ModbusException((ModbusErrorCode) _Buffer[dataPos]); } return dataLength; } finally { _Interface.PrepareWrite(); } } }
private T Write <T>(ModbusDevice device, ModbusFunctionCode functionCode, int zeroBasedOffset, Action <IMessageBufferWriter> writeAction, Func <IMessageBufferReader, T> readValueFunc) { var requestContext = new ModbusTransportContext() { TransactionIdentifier = GetTransactionIdentifier() }; ModbusTransport.SendMessage(requestContext, (writer) => { writer.Push(device.Address); writer.Push((byte)functionCode); writer.Push((byte)((zeroBasedOffset >> 8) & 0xFF)); writer.Push((byte)((zeroBasedOffset >> 0) & 0xFF)); writeAction(writer); }); T returnedValue = default; var responseContext = ModbusTransport.ReceiveMessage( (reader) => { if (reader.PushByteFromStream() != device.Address) { throw new InvalidOperationException(); } byte receivedFunctionCode = reader.PushByteFromStream(); if (receivedFunctionCode == ((byte)functionCode | 0x80)) { var exceptionCode = (ModbusExceptionCode)reader.PushByteFromStream(); throw new ModbusException(exceptionCode); } if (receivedFunctionCode != (byte)functionCode) { throw new InvalidOperationException(); } if ((ushort)((reader.PushByteFromStream() << 8) + (reader.PushByteFromStream() << 0)) != zeroBasedOffset) { throw new InvalidOperationException(); } returnedValue = readValueFunc(reader); }); if (requestContext.TransactionIdentifier != responseContext.TransactionIdentifier) { throw new InvalidOperationException(); } return(returnedValue !); }
private async Task <T> WriteAsync <T>(ModbusDevice device, ModbusFunctionCode functionCode, int zeroBasedOffset, Action <IMessageBufferWriter> writeAction, Func <IMessageBufferReader, Task <T> > readValueFunc, CancellationToken cancellationToken) { var requestContext = new ModbusTransportContext() { TransactionIdentifier = GetTransactionIdentifier() }; await ModbusTransport.SendMessageAsync(requestContext, (writer) => { writer.Push(device.Address); writer.Push((byte)functionCode); writer.Push((byte)((zeroBasedOffset >> 8) & 0xFF)); writer.Push((byte)((zeroBasedOffset >> 0) & 0xFF)); writeAction(writer); }, cancellationToken); T returnedValue = default; var responseContext = await ModbusTransport.ReceiveMessageAsync( async (reader) => { if (await reader.PushByteFromStreamAsync(cancellationToken) != device.Address) { throw new InvalidOperationException(); } byte receivedFunctionCode = await reader.PushByteFromStreamAsync(cancellationToken); if (receivedFunctionCode == ((byte)functionCode | 0x80)) { var exceptionCode = (ModbusExceptionCode)await reader.PushByteFromStreamAsync(cancellationToken); throw new ModbusException(exceptionCode); } if (receivedFunctionCode != (byte)functionCode) { throw new InvalidOperationException(); } if (await reader.PushByteFromStreamAsync(cancellationToken) != zeroBasedOffset) { throw new InvalidOperationException(); } returnedValue = await readValueFunc(reader); }, cancellationToken); if (requestContext.TransactionIdentifier != responseContext.TransactionIdentifier) { throw new InvalidOperationException(); } return(returnedValue !); }
private async Task ReadAsync(ModbusDevice device, ModbusFunctionCode functionCode, int zeroBasedOffset, int count, Action <MessageBufferSpan> actionWithReturnedBuffer, CancellationToken cancellationToken) { var requestContext = new ModbusTransportContext() { TransactionIdentifier = GetTransactionIdentifier() }; await ModbusTransport.SendMessageAsync(requestContext, (writer) => { writer.Push(device.Address); writer.Push((byte)functionCode); writer.Push((byte)((zeroBasedOffset >> 8) & 0xFF)); writer.Push((byte)((zeroBasedOffset >> 0) & 0xFF)); writer.Push((byte)((count >> 8) & 0xFF)); writer.Push((byte)((count >> 0) & 0xFF)); }, cancellationToken); MessageBufferSpan?receivedBuffer = null; var responseContext = await ModbusTransport.ReceiveMessageAsync( async (reader) => { if (await reader.PushByteFromStreamAsync(cancellationToken) != device.Address) { throw new InvalidOperationException(); } byte receivedFunctionCode = await reader.PushByteFromStreamAsync(cancellationToken); if (receivedFunctionCode == ((byte)functionCode | 0x80)) { var exceptionCode = (ModbusExceptionCode)await reader.PushByteFromStreamAsync(cancellationToken); throw new ModbusException(exceptionCode); } if (receivedFunctionCode != (byte)functionCode) { throw new InvalidOperationException(); } var byteCount = await reader.PushByteFromStreamAsync(cancellationToken); await reader.PushFromStreamAsync(byteCount, cancellationToken); receivedBuffer = new MessageBufferSpan(reader.Buffer, (ushort)(reader.Buffer.Length - byteCount), byteCount); }, cancellationToken); if (responseContext.TransactionIdentifier != requestContext.TransactionIdentifier) { throw new InvalidOperationException(); } actionWithReturnedBuffer(receivedBuffer !); }
internal void ProcessError(ModbusFunctionCode functionCode, ModbusExceptionCode exceptionCode) { switch (exceptionCode) { case ModbusExceptionCode.IllegalFunction: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x01_IllegalFunction); case ModbusExceptionCode.IllegalDataAddress: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x02_IllegalDataAddress); case ModbusExceptionCode.IllegalDataValue: switch (functionCode) { case ModbusFunctionCode.WriteMultipleRegisters: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x03_IllegalDataValue_0x7B); case ModbusFunctionCode.ReadHoldingRegisters: case ModbusFunctionCode.ReadInputRegisters: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x03_IllegalDataValue_0x7D); case ModbusFunctionCode.ReadCoils: case ModbusFunctionCode.ReadDiscreteInputs: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x03_IllegalDataValue_0x7D0); default: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x03_IllegalDataValue); } case ModbusExceptionCode.ServerDeviceFailure: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x04_ServerDeviceFailure); case ModbusExceptionCode.Acknowledge: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x05_Acknowledge); case ModbusExceptionCode.ServerDeviceBusy: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x06_ServerDeviceBusy); case ModbusExceptionCode.MemoryParityError: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x08_MemoryParityError); case ModbusExceptionCode.GatewayPathUnavailable: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x0A_GatewayPathUnavailable); case ModbusExceptionCode.GatewayTargetDeviceFailedToRespond: throw new ModbusException(exceptionCode, ErrorMessage.ModbusClient_0x0B_GatewayTargetDeviceFailedToRespond); default: throw new ArgumentOutOfRangeException(ErrorMessage.ModbusClient_InvalidExceptionCode); } }
//不同内存区域显示不同的类型 private void Cbo_functioncode_SelectedIndexChanged(object sender, EventArgs e) { groupStored.Visible = false; groupposition.Visible = false; if (cbo_functioncode.SelectedItem != null) { ModbusFunctionCode func = (ModbusFunctionCode)cbo_functioncode.SelectedItem; switch (func.Code) { case "01": { groupStored.Visible = false; rb_rw.Checked = true; } break; case "02": { groupStored.Visible = false; rb_r.Checked = true; } break; case "03": { groupStored.Visible = true; rb_rw.Checked = true; } break; case "04": { groupStored.Visible = true; rb_r.Checked = true; } break; } } }
private async Task ExecuteReadCommand(IReadModbusFunction readCommand) { string verboseMessage = $"{baseLogString} entering ExecuteReadCommand method, FunctionCode: {readCommand.FunctionCode}, StartAddress: {readCommand.StartAddress}, Quantity: {readCommand.Quantity}."; Logger.LogVerbose(verboseMessage); ModbusFunctionCode functionCode = readCommand.FunctionCode; ushort startAddress = readCommand.StartAddress; ushort quantity = readCommand.Quantity; if (quantity <= 0) { string message = $"{baseLogString} ExecuteReadCommand => Reading Quantity: {quantity} does not make sense."; Logger.LogError(message); throw new ArgumentException(message); } if (startAddress + quantity >= ushort.MaxValue || startAddress + quantity == ushort.MinValue || startAddress == ushort.MinValue) { string message = $"{baseLogString} ExecuteReadCommand => Address is out of bound. Start address: {startAddress}, Quantity: {quantity}"; Logger.LogError(message); throw new ArgumentException(message); } if (functionCode == ModbusFunctionCode.READ_COILS || functionCode == ModbusFunctionCode.READ_DISCRETE_INPUTS) { verboseMessage = $"{baseLogString} ExecuteReadCommand => ExecuteDiscreteReadCommand about to be called."; Logger.LogVerbose(verboseMessage); //LOGIC await ExecuteDiscreteReadCommand(functionCode, startAddress, quantity); } else if (functionCode == ModbusFunctionCode.READ_HOLDING_REGISTERS || functionCode == ModbusFunctionCode.READ_INPUT_REGISTERS) { verboseMessage = $"{baseLogString} ExecuteReadCommand => ExecuteAnalogReadCommand about to be called."; Logger.LogVerbose(verboseMessage); //LOGIC await ExecuteAnalogReadCommand(functionCode, startAddress, quantity); } else { string errorMessage = $"{baseLogString} ExecuteWriteSingleCommand => function code hase value: {functionCode}, but one of these was required: {ModbusFunctionCode.READ_COILS}, {ModbusFunctionCode.READ_DISCRETE_INPUTS}, {ModbusFunctionCode.READ_HOLDING_REGISTERS}, {ModbusFunctionCode.READ_INPUT_REGISTERS}."; Logger.LogError(errorMessage); throw new ArgumentException(errorMessage); } }
private bool CheckRegisterBounds(ModbusFunctionCode functionCode, int startingAddress, int maxStartingAddress, int quantityOfRegisters, int maxQuantityOfRegisters) { if (startingAddress < 0 || startingAddress + quantityOfRegisters > maxStartingAddress) { this.WriteExceptionResponse(functionCode, ModbusExceptionCode.IllegalDataAddress); return(false); } if (quantityOfRegisters <= 0 || quantityOfRegisters > maxQuantityOfRegisters) { this.WriteExceptionResponse(functionCode, ModbusExceptionCode.IllegalDataValue); return(false); } return(true); }
public void SetParament(string parastr) { ParaPack para = new ParaPack(parastr); if (para.Count > 0) { ///内存区 for (int i = 0; i < cbo_functioncode.Items.Count; i++) { ModbusFunctionCode funCode = (ModbusFunctionCode)cbo_functioncode.Items[i]; if (funCode.Code == para.GetValue("内存区")) { cbo_functioncode.SelectedIndex = i; break; } } //数据类型 for (int i = 0; i < cbo_StoreType.Items.Count; i++) { ModbusDataType datatype = (ModbusDataType)cbo_StoreType.Items[i]; if (datatype.ToString() == para.GetValue("数据类型")) { cbo_StoreType.SelectedIndex = i; break; } } //偏置 this.ndOffset.Value = para.GetValue("偏置") == "" ? 0: Convert.ToDecimal(para.GetValue("偏置")); this.ndCharSize.Value = para.GetValue("字节长度") == "" ? 0 : Convert.ToDecimal(para.GetValue("字节长度")); this.ndPosition.Value = para.GetValue("数据位") == "" ? 0 : Convert.ToDecimal(para.GetValue("数据位")); //存储位置 for (int i = 0; i < cbo_datatype.Items.Count; i++) { string datatype = (string)cbo_datatype.Items[i]; if (datatype.ToString() == para.GetValue("存储位置")) { cbo_datatype.SelectedIndex = i; break; } } //按位存取 this.cbPosition.Checked = para.GetValue("按位存取") == "1" ? true : false; } }
private void WriteMultipleRegisters(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (dataLength < 5) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { ushort startAddress = ModbusUtils.ExtractUShort(_buffer, dataPos); ushort registerCount = ModbusUtils.ExtractUShort(_buffer, dataPos + 2); //byte byteCount = _Buffer[dataPos + 4]; ushort[] registers = new ushort[registerCount]; for (int i = 0; i < registerCount; i++) { registers[i] = ModbusUtils.ExtractUShort(_buffer, dataPos + 5 + 2 * i); } var err = OnWriteMultipleRegisters(isBroadcast, startAddress, registers); if (isBroadcast) { return; } if (err != ModbusErrorCode.NoError) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, err); } else { short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, 4, _buffer, out telegramLength, out dataPos, true, ref telegramContext); ModbusUtils.InsertUShort(_buffer, dataPos, startAddress); ModbusUtils.InsertUShort(_buffer, dataPos + 2, registerCount); intf.SendTelegram(_buffer, telegramLength); } } }
private void WriteMultipleCoils(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (dataLength < 5) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { ushort startAddress = ModbusUtils.ExtractUShort(_buffer, dataPos); ushort outputCount = ModbusUtils.ExtractUShort(_buffer, dataPos + 2); byte byteCount = _buffer[dataPos + 4]; byte[] values = new byte[byteCount]; Array.Copy(_buffer, dataPos + 5, values, 0, values.Length); var err = OnWriteMultipleCoils(isBroadcast, startAddress, outputCount, values); if (isBroadcast) { return; } if (err != ModbusErrorCode.NoError) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, err); } else { short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, 4, _buffer, out telegramLength, out dataPos, true, ref telegramContext); ModbusUtils.InsertUShort(_buffer, dataPos, startAddress); ModbusUtils.InsertUShort(_buffer, dataPos + 2, outputCount); intf.SendTelegram(_buffer, telegramLength); } } }
/// <summary> /// Sends a error result message. /// </summary> /// <param name="intf">Interface to send message to.</param> /// <param name="isBroadcast">true if the message is a broadcast. For broadcast messages no reponse is sent.</param> /// <param name="deviceAddress">Device address for response</param> /// <param name="telegramContext">Interface specific telegram context.</param> /// <param name="fc">Function code. The msg is automatically set.</param> /// <param name="modbusErrorCode">Modbus error code to send.</param> protected virtual void SendErrorResult(IModbusInterface intf, bool isBroadcast, byte deviceAddress, object telegramContext, ModbusFunctionCode fc, ModbusErrorCode modbusErrorCode) { if (isBroadcast) { return; } short telegramLength; short dataPos; intf.CreateTelegram(deviceAddress, (byte)((byte)fc | 0x80), 1, _buffer, out telegramLength, out dataPos, true, ref telegramContext); _buffer[dataPos] = (byte)modbusErrorCode; intf.SendTelegram(_buffer, telegramLength); }
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)); }
private protected abstract Span <byte> TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action <ExtendedBinaryWriter> extendFrame);
private void WriteSingleRegister(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (dataLength < 4) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { ushort address = ModbusUtils.ExtractUShort(_buffer, dataPos); ushort value = ModbusUtils.ExtractUShort(_buffer, dataPos + 2); var err = OnWriteSingleRegister(isBroadcast, address, value); if (isBroadcast) { return; } if (err != ModbusErrorCode.NoError) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, err); } else { short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, 4, _buffer, out telegramLength, out dataPos, true, ref telegramContext); ModbusUtils.InsertUShort(_buffer, dataPos, address); ModbusUtils.InsertUShort(_buffer, dataPos + 2, value); intf.SendTelegram(_buffer, telegramLength); } } }
/// <summary> /// OnCustomTelegram is called for any function code which is not explicitly handeled by a On"FunctionCode" methos. /// </summary> /// <param name="intf">Interface which sent the request.</param> /// <param name="isBroadcast">true if the request is a broadcast</param> /// <param name="buffer">Buffer containing the message.</param> /// <param name="telegramLength">Total length of message in bytes.</param> /// <param name="telegramContext">Interface specific message context.</param> /// <param name="fc">Function code.</param> /// <param name="dataPos">Index of the function code specific data.</param> /// <param name="dataLength">Length of the function code specific data in bytes.</param> /// <returns></returns> /// <remarks> /// Look at http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf for more details about function codes. /// </remarks> protected virtual bool OnCustomTelegram(IModbusInterface intf, bool isBroadcast, byte[] buffer, short telegramLength, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { return(false); }
/// <summary> /// Is called when ever a modbus message was received, no matter if it was for this device or not. /// </summary> /// <param name="modbusInterface">Interface by which the message was received</param> /// <param name="deviceAddress">Address to which device the message was sent</param> /// <param name="functionCode">Function code</param> protected virtual void OnMessageReeived(IModbusInterface modbusInterface, byte deviceAddress, ModbusFunctionCode functionCode) { }
private async Task ExecuteAnalogReadCommand(ModbusFunctionCode functionCode, ushort startAddress, ushort quantity) { string verboseMessage = $"{baseLogString} entering ExecuteAnalogReadCommand method, command's functionCode: {functionCode}, startAddress: {startAddress}, quantity:{quantity}."; Logger.LogVerbose(verboseMessage); int[] data; PointType pointType; if (functionCode == ModbusFunctionCode.READ_HOLDING_REGISTERS) { verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => about to call ModbusClient.ReadHoldingRegisters({startAddress - 1}, {quantity}) method."; Logger.LogVerbose(verboseMessage); //KEY LOGIC pointType = PointType.ANALOG_OUTPUT; data = modbusClient.ReadHoldingRegisters(startAddress - 1, quantity); verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => ModbusClient.ReadHoldingRegisters({startAddress - 1}, {quantity}) method SUCCESSFULLY executed. Resulting data count: {data.Length}."; Logger.LogVerbose(verboseMessage); } else if (functionCode == ModbusFunctionCode.READ_INPUT_REGISTERS) { verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => about to call ModbusClient.ReadInputRegisters({startAddress - 1}, {quantity}) method."; Logger.LogVerbose(verboseMessage); //KEY LOGIC pointType = PointType.ANALOG_INPUT; data = modbusClient.ReadInputRegisters(startAddress - 1, quantity); verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => ModbusClient.ReadInputRegisters({startAddress - 1}, {quantity}) method SUCCESSFULLY executed. Resulting data count: {data.Length}."; Logger.LogVerbose(verboseMessage); } else { string message = $"{baseLogString} ExecuteAnalogReadCommand => function code is neither ModbusFunctionCode.READ_HOLDING_REGISTERS nor ModbusFunctionCode.READ_INPUT_REGISTERS"; Logger.LogError(message); throw new ArgumentException(message); } //this.analogMeasurementCache = new Dictionary<long, AnalogModbusData>(data.Length); this.analogMeasurementCache.Clear(); var modelReadAccessClient = ScadaModelReadAccessClient.CreateClient(); var modelUpdateAccessClient = ScadaModelUpdateAccessClient.CreateClient(); var gidToPointItemMap = await modelReadAccessClient.GetGidToPointItemMap(); var addressToGidMap = await modelReadAccessClient.GetAddressToGidMap(); var commandDescriptionCache = await modelReadAccessClient.GetCommandDescriptionCache(); for (ushort i = 0; i < data.Length; i++) { ushort address = (ushort)(startAddress + i); int rawValue = data[i]; if (!addressToGidMap.ContainsKey((short)pointType)) { Logger.LogWarning($"{baseLogString} ExecuteAnalogReadCommand => Point type: {pointType} is not in the current addressToGidMap."); continue; } //for commands enqueued during model update, that are not valid if (!addressToGidMap[(short)pointType].ContainsKey(address)) { Logger.LogWarning($"{baseLogString} ExecuteAnalogReadCommand => trying to read value on address {address}, Point type: {pointType}, which is not in the current addressToGidMap."); continue; } long gid = addressToGidMap[(short)pointType][address]; //for commands enqueued during model update, that are not valid if (!gidToPointItemMap.ContainsKey(gid)) { Logger.LogWarning($"{baseLogString} ExecuteAnalogReadCommand => trying to read value for measurement with gid: 0x{gid:X16}, which is not in the current SCADA Model."); continue; } if (!(gidToPointItemMap[gid] is IAnalogPointItem pointItem)) { string message = $"{baseLogString} ExecuteAnalogReadCommand => PointItem [Gid: 0x{gid:X16}] does not implement {typeof(IAnalogPointItem)}."; Logger.LogError(message); throw new Exception(message); } //KEY LOGIC if (pointItem.CurrentRawValue != rawValue) { pointItem = (IAnalogPointItem)(await modelUpdateAccessClient.UpdatePointItemRawValue(pointItem.Gid, rawValue)); Logger.LogInformation($"{baseLogString} ExecuteAnalogReadCommand => Alarm for Point [Gid: 0x{pointItem.Gid:X16}, Address: {pointItem.Address}] set to {pointItem.Alarm}."); } //LOGIC CommandOriginType commandOrigin = CommandOriginType.UNKNOWN_ORIGIN; if (commandDescriptionCache.ContainsKey(gid) && commandDescriptionCache[gid].Value == pointItem.CurrentRawValue) { commandOrigin = commandDescriptionCache[gid].CommandOrigin; await modelUpdateAccessClient.RemoveCommandDescription(gid); Logger.LogDebug($"{baseLogString} ExecuteAnalogReadCommand => Command origin of command address: {pointItem.Address} is set to {commandOrigin}."); //LOGIC AnalogModbusData analogData = new AnalogModbusData(pointItem.CurrentEguValue, pointItem.Alarm, gid, commandOrigin); this.analogMeasurementCache.Add(gid, analogData); verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => AnalogModbusData added to measurementCache. MeasurementGid: {analogData.MeasurementGid:X16}, Value: {analogData.Value}, Alarm: {analogData.Alarm}, CommandOrigin: {analogData.CommandOrigin} ."; Logger.LogVerbose(verboseMessage); } } //LOGIC await modelUpdateAccessClient.MakeAnalogEntryToMeasurementCache(this.analogMeasurementCache, true); verboseMessage = $"{baseLogString} ExecuteAnalogReadCommand => MakeAnalogEntryToMeasurementCache method called. measurementCache count: {this.analogMeasurementCache.Count}."; Logger.LogVerbose(verboseMessage); }
private void ReadWriteMultipleRegisters(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (dataLength < 9) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { ushort readStartAddress = ModbusUtils.ExtractUShort(_buffer, dataPos); ushort readCount = ModbusUtils.ExtractUShort(_buffer, dataPos + 2); ushort writeStartAddress = ModbusUtils.ExtractUShort(_buffer, dataPos + 4); ushort writeCount = ModbusUtils.ExtractUShort(_buffer, dataPos + 6); //byte byteCount = _Buffer[dataPos + 8]; ushort[] writeRegisters = new ushort[writeCount]; for (int i = 0; i < writeCount; i++) { writeRegisters[i] = ModbusUtils.ExtractUShort(_buffer, dataPos + 5 + 2 * i); } ushort[] readRegisters = new ushort[readCount]; var err = OnReadWriteMultipleRegisters(isBroadcast, writeStartAddress, writeRegisters, readStartAddress, readRegisters); if (isBroadcast) { return; } if (err != ModbusErrorCode.NoError) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, err); } else { short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, (short)(1 + 2 * readRegisters.Length), _buffer, out telegramLength, out dataPos, true, ref telegramContext); _buffer[dataPos] = (byte)(2 * readRegisters.Length); for (int i = 0; i < readCount; i++) { ModbusUtils.InsertUShort(_buffer, dataPos + 1 + 2 * i, readRegisters[i]); } intf.SendTelegram(_buffer, telegramLength); } } }
/// <summary> /// Is called when the device identification of this devuice is requested. /// </summary> /// <param name="intf">Interface from wich the requst was received</param> /// <param name="isBroadcast">true if request is a broadcast</param> /// <param name="telegramContext">Conext of the telegram</param> /// <param name="fc">Function code</param> /// <param name="dataPos">Posittion (offset) of the data in the buffer</param> /// <param name="dataLength">Length of the data in the buffer</param> protected virtual void ReadDeviceIdentification(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (isBroadcast) { return; } if (dataLength < 3) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { byte deviceIdCode = _buffer[dataPos + 1]; if (deviceIdCode < 1 || deviceIdCode > 4) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); return; } byte objectId = _buffer[dataPos + 2]; byte lastObjectId; switch (deviceIdCode) { case 0x00: lastObjectId = 0x02; // basic break; case 0x01: lastObjectId = 0x7f; // regular break; case 0x02: lastObjectId = 0xff; // extended break; default: lastObjectId = objectId; // specific break; } byte[] values = new byte[intf.MaxTelegramLength - 6]; byte objectCount = 0; short valuePos = 0; bool moreFolows = false; byte nextId = 0; for (short id = objectId; id <= lastObjectId; ++id) { var value = OnGetDeviceIdentification((ModbusObjectId)id); if (value == null) { // no more values break; } if (values.Length - (valuePos + 2) >= value.Length) { ++objectCount; values[valuePos++] = (byte)id; values[valuePos++] = (byte)value.Length; for (int c = 0; c < value.Length; c++) { values[valuePos++] = (byte)value[c]; } } else { // more to come moreFolows = true; nextId = (byte)(id + 1); break; } } short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, (short)(6 + valuePos), _buffer, out telegramLength, out dataPos, true, ref telegramContext); _buffer[dataPos] = 0x0e; _buffer[dataPos + 1] = deviceIdCode; _buffer[dataPos + 2] = (byte)((byte)GetConformityLevel() & 0x80); _buffer[dataPos + 3] = (byte)(moreFolows ? 0xff : 0x00); _buffer[dataPos + 4] = nextId; _buffer[dataPos + 5] = objectCount; Array.Copy(values, 0, _buffer, dataPos + 6, valuePos); intf.SendTelegram(_buffer, telegramLength); } }
/// <summary> /// Handles a received message. /// </summary> /// <param name="intf">Interface by which the message was received.</param> /// <param name="deviceAddress">Address of the target device</param> /// <param name="isBroadcast">true if the message is a broadcast. For broadcast messages no response is sent.</param> /// <param name="telegramLength">Length of the message in bytes.</param> /// <param name="telegramContext">Interface specific message context.</param> /// <param name="fc">Function code.</param> /// <param name="dataPos">Index of function code specific data.</param> /// <param name="dataLength">Length of the function code specific data in bytes.</param> protected virtual void OnHandleTelegram(IModbusInterface intf, byte deviceAddress, bool isBroadcast, short telegramLength, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { try { switch (fc) { case ModbusFunctionCode.ReadCoils: ReadCoils(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.ReadDiscreteInputs: ReadDiscreteInputs(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.ReadHoldingRegisters: ReadHoldingRegisters(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.ReadInputRegisters: ReadInputRegisters(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.WriteSingleCoil: WriteSingleCoil(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.WriteSingleRegister: WriteSingleRegister(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.WriteMultipleCoils: WriteMultipleCoils(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.WriteMultipleRegisters: WriteMultipleRegisters(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.ReadWriteMultipleRegisters: ReadWriteMultipleRegisters(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; case ModbusFunctionCode.ReadDeviceIdentification: case ModbusFunctionCode.ReadDeviceIdentification2: ReadDeviceIdentification(intf, isBroadcast, telegramContext, fc, dataPos, dataLength); break; default: if (!OnCustomTelegram(intf, isBroadcast, _buffer, telegramLength, telegramContext, fc, dataPos, dataLength)) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalFunction); } break; } } catch { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.ServerDeviceFailure); } }
private void ReadDiscreteInputs(IModbusInterface intf, bool isBroadcast, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { if (dataLength < 4) { SendErrorResult(intf, isBroadcast, _deviceAddress, telegramContext, fc, ModbusErrorCode.IllegalDataValue); } else { ushort startAddress = ModbusUtils.ExtractUShort(_buffer, dataPos); ushort inputCount = ModbusUtils.ExtractUShort(_buffer, dataPos + 2); byte[] inputs; if (inputCount % 8 == 0) { inputs = new byte[inputCount / 8]; } else { inputs = new byte[inputCount / 8 + 1]; inputs[inputs.Length - 1] = 0; } var err = OnReadDiscreteInputs(isBroadcast, startAddress, inputCount, inputs); if (isBroadcast) { return; } if (err != ModbusErrorCode.NoError) { SendErrorResult(intf, false, _deviceAddress, telegramContext, fc, err); } else { short telegramLength; intf.CreateTelegram(_deviceAddress, (byte)fc, (short)(1 + inputs.Length), _buffer, out telegramLength, out dataPos, true, ref telegramContext); _buffer[dataPos] = (byte)(inputs.Length); Array.Copy(inputs, 0, _buffer, dataPos + 1, inputs.Length); intf.SendTelegram(_buffer, telegramLength); } } }
private void WriteExceptionResponse(ModbusFunctionCode functionCode, ModbusExceptionCode exceptionCode) { this.WriteExceptionResponse((byte)functionCode, exceptionCode); }
/// <summary> /// OnCustomTelegram is called for any function code which is not explicitly handeled by a On"FunctionCode" methos. /// </summary> /// <param name="intf">Interface which sent the request.</param> /// <param name="isBroadcast">true if the request is a broadcast</param> /// <param name="buffer">Buffer containing the message.</param> /// <param name="telegramLength">Total length of message in bytes.</param> /// <param name="telegramContext">Interface specific message context.</param> /// <param name="fc">Function code.</param> /// <param name="dataPos">Index of the function code specific data.</param> /// <param name="dataLength">Length of the function code specific data in bytes.</param> /// <returns></returns> /// <remarks> /// Look at http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf for more details about function codes. /// </remarks> protected virtual bool OnCustomTelegram(IModbusInterface intf, bool isBroadcast, byte[] buffer, short telegramLength, object telegramContext, ModbusFunctionCode fc, short dataPos, short dataLength) { return false; }
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)); }
private protected override Span <byte> TransceiveFrame(byte unitIdentifier, ModbusFunctionCode functionCode, Action <ExtendedBinaryWriter> extendFrame) { int frameLength; int partialLength; ushort transactionIdentifier; ushort protocolIdentifier; ushort bytesFollowing; byte rawFunctionCode; bool isParsed; ModbusFrameBuffer frameBuffer; ExtendedBinaryWriter writer; ExtendedBinaryReader reader; bytesFollowing = 0; frameBuffer = _frameBuffer; writer = _frameBuffer.Writer; reader = _frameBuffer.Reader; // build request writer.Seek(7, SeekOrigin.Begin); extendFrame(writer); frameLength = (int)writer.BaseStream.Position; writer.Seek(0, SeekOrigin.Begin); if (BitConverter.IsLittleEndian) { writer.WriteReverse(this.GetTransactionIdentifier()); // 00-01 Transaction Identifier writer.WriteReverse((ushort)0); // 02-03 Protocol Identifier writer.WriteReverse((ushort)(frameLength - 6)); // 04-05 Length } else { writer.Write(this.GetTransactionIdentifier()); // 00-01 Transaction Identifier writer.Write((ushort)0); // 02-03 Protocol Identifier writer.Write((ushort)(frameLength - 6)); // 04-05 Length } writer.Write(unitIdentifier); // 06 Unit Identifier // send request _networkStream.Write(frameBuffer.Buffer, 0, frameLength); // wait for and process response frameLength = 0; isParsed = false; reader.BaseStream.Seek(0, SeekOrigin.Begin); while (true) { partialLength = _networkStream.Read(frameBuffer.Buffer, frameLength, frameBuffer.Buffer.Length - frameLength); /* From MSDN (https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.read): * Implementations of this method read a maximum of count bytes from the current stream and store * them in buffer beginning at offset. The current position within the stream is advanced by the * number of bytes read; however, if an exception occurs, the current position within the stream * remains unchanged. Implementations return the number of bytes read. The implementation will block * until at least one byte of data can be read, in the event that no data is available. Read returns * 0 only when there is no more data in the stream and no more is expected (such as a closed socket or end of file). * An implementation is free to return fewer bytes than requested even if the end of the stream has not been reached. */ if (partialLength == 0) { throw new InvalidOperationException(ErrorMessage.ModbusClient_TcpConnectionClosedUnexpectedly); } frameLength += partialLength; if (frameLength >= 7) { if (!isParsed) // read MBAP header only once { // read MBAP header transactionIdentifier = reader.ReadUInt16Reverse(); // 00-01 Transaction Identifier protocolIdentifier = reader.ReadUInt16Reverse(); // 02-03 Protocol Identifier bytesFollowing = reader.ReadUInt16Reverse(); // 04-05 Length unitIdentifier = reader.ReadByte(); // 06 Unit Identifier if (protocolIdentifier != 0) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidProtocolIdentifier); } isParsed = true; } // full frame received if (frameLength - 6 >= bytesFollowing) { break; } } } rawFunctionCode = reader.ReadByte(); if (rawFunctionCode == (byte)ModbusFunctionCode.Error + (byte)functionCode) { this.ProcessError(functionCode, (ModbusExceptionCode)frameBuffer.Buffer[8]); } else if (rawFunctionCode != (byte)functionCode) { throw new ModbusException(ErrorMessage.ModbusClient_InvalidResponseFunctionCode); } return(frameBuffer.Buffer.AsSpan(7, frameLength - 7)); }