/// <summary> /// Send the response /// </summary> /// <param name="send_buffer">Send buffer</param> /// <param name="flux">Object for flux gest</param> /// <param name="unit_id">Slave ID</param> void SendReply(List <byte> send_buffer, object flux, byte unit_id, ushort transaction_id) { switch (_connectionType) { case ConnectionType.SERIAL_ASCII: // Add unit ID send_buffer.Insert(0, unit_id); // Enqueue LRC send_buffer.Add(LRC.CalcLRC(send_buffer.ToArray(), 0, send_buffer.Count)); // ASCII encoding send_buffer = GetASCIIBytesFromBinaryBuffer(send_buffer.ToArray()).ToList(); // Add START character send_buffer.Insert(0, Encoding.ASCII.GetBytes(new char[] { ASCII_START_FRAME }).First()); // Enqueue STOP chars send_buffer.AddRange(Encoding.ASCII.GetBytes(new char[] { ASCII_STOP_FRAME_1ST, ASCII_STOP_FRAME_2ND })); break; case ConnectionType.SERIAL_RTU: // Add unit ID send_buffer.Insert(0, unit_id); // Enqueue CRC send_buffer.AddRange(BitConverter.GetBytes(CRC16.CalcCRC16(send_buffer.ToArray(), 0, send_buffer.Count))); // Wait for interframe delay Thread.Sleep(_interframeDelay); break; case ConnectionType.UDP_IP: case ConnectionType.TCP_IP: // Build MBAP header send_buffer.InsertRange(0, GetBytes(transaction_id)); send_buffer.InsertRange(2, GetBytes(PROTOCOL_ID)); send_buffer.InsertRange(4, GetBytes((ushort)(1 + send_buffer.Count - 4))); send_buffer.Insert(6, unit_id); break; } // Send the buffer WriteBuffer(flux, send_buffer.ToArray(), 0, send_buffer.Count); }
/// <summary> /// Execute a query to a destination master /// </summary> /// <param name="deviceAddress">Salve device address</param> /// <param name="messageLength">Message lenght</param> protected void Query(byte deviceAddress, ushort messageLength) { // Build the message according to the selected protocol. switch (_connectionType) { case ConnectionType.SerialASCII: // Insert device address in front of the message. _sendBuffer.Insert(0, deviceAddress); // Calculate message LCR. byte[] lrc = GetASCIIBytesFromBinaryBuffer(new byte[] { LRC.CalcLRC(_sendBuffer.ToArray(), 0, _sendBuffer.Count) }); // Convert the message from binary to ASCII. _sendBuffer = GetASCIIBytesFromBinaryBuffer(_sendBuffer.ToArray()).ToList(); // Add LRC at the end of the message. _sendBuffer.AddRange(lrc); // Insert the start frame chararacter at the start of the message. _sendBuffer.Insert(0, Encoding.ASCII.GetBytes(new char[] { AsciiStartFrame }).First()); // Insert stop frame characters at the end of the message. char[] endFrame = new char[] { AsciiStopFrame1ST, AsciiStopFrame2ND }; _sendBuffer.AddRange(Encoding.ASCII.GetBytes(endFrame)); break; case ConnectionType.SerialRTU: // Insert device address in front of the message. _sendBuffer.Insert(0, deviceAddress); // Append CRC16 to end of message. _sendBuffer.AddRange(BitConverter.GetBytes(CRC16.CalcCRC16(_sendBuffer.ToArray(), 0, _sendBuffer.Count))); // Wait for interframe delay. Thread.Sleep(_interframeDelay); break; // For Modbus TCP/UDP, build MBAP header. case ConnectionType.UDPIP: case ConnectionType.TCPIP: // Transaction ID (incremented by 1 on each trasmission) _sendBuffer.InsertRange(0, GetBytes(_transactionID)); // Protocol ID (fixed value) _sendBuffer.InsertRange(2, GetBytes(ProtocolID)); // Message length _sendBuffer.InsertRange(4, GetBytes(messageLength)); // Remote unit ID _sendBuffer.Insert(6, deviceAddress); break; } // Send the message. Send(); // Clear the receive message buffer. _receiveBuffer.Clear(); bool done = false; bool firstByteReceived = false; long elapsedTime; // Start a timeout stopwatch. Stopwatch sw = new Stopwatch(); sw.Start(); do { // Try to read a byte. int rcv = ReceiveByte(); // If a byte was received... if (rcv > -1) { // If it is the first byte... if (firstByteReceived == false) { // Remember that at least one byte has been received. firstByteReceived = true; } // If we're using MODBUS ASCII... if (_connectionType == ConnectionType.SerialASCII) { // If the byte we received was a colon (the first byte of an ASCII message)... if ((byte)rcv == Encoding.ASCII.GetBytes(new char[] { AsciiStartFrame }).First()) { // Clear the receive message buffer of any previous partial message. _receiveBuffer.Clear(); } } // Add the received byte to the receive message buffer. _receiveBuffer.Add((byte)rcv); } // If we've stopped receiving bytes... else if ((rcv == -1) && firstByteReceived) { // Consider the message finished. done = true; } // Fetch how many milliseconds have elapsed. // Keep receiving until we stop receiving bytes. elapsedTime = sw.ElapsedMilliseconds; } while ((!done) && (RxTimeout > elapsedTime)); // Stop the stopwatch. _timeoutStopwatch.Stop(); sw.Stop(); // If the response timed out... if (elapsedTime >= RxTimeout) { throw new ModbusTimeoutException("Timeout waiting for response."); } // If we got a response... else { // Fetch the minimum message length... int minFrameLength; switch (_connectionType) { default: case ConnectionType.SerialRTU: minFrameLength = 5; break; case ConnectionType.SerialASCII: minFrameLength = 11; break; case ConnectionType.UDPIP: case ConnectionType.TCPIP: minFrameLength = 9; break; } // If the message was incomplete... if (_receiveBuffer.Count < minFrameLength) { throw new ModbusTimeoutException("Wrong message length."); } switch (_connectionType) { // If we're using MODBUS ASCII... case ConnectionType.SerialASCII: // If we didn't get the correct start character... if (_receiveBuffer[0] != _sendBuffer[0]) { throw new ModbusTimeoutException("Start character not found."); } // Remove the start character (a colon). _receiveBuffer.RemoveRange(0, 1); // If we didn't get the correct stop characters... char[] endFrameSent = new char[] { AsciiStopFrame1ST, AsciiStopFrame2ND }; char[] endFrameReceived = Encoding.ASCII.GetChars(_receiveBuffer.GetRange(_receiveBuffer.Count - 2, 2).ToArray()); if (!endFrameSent.SequenceEqual(endFrameReceived)) { throw new ModbusTimeoutException("End characters not found."); } // Remove the stop characters. _receiveBuffer.RemoveRange(_receiveBuffer.Count - 2, 2); // Convert receive buffer from ASCII to binary. _receiveBuffer = GetBinaryBufferFromASCIIBytes(_receiveBuffer.ToArray()).ToList(); // If the LRC is incorrect... byte lrc_calculated = LRC.CalcLRC(_receiveBuffer.ToArray(), 0, _receiveBuffer.Count - 1); byte lrc_received = _receiveBuffer[_receiveBuffer.Count - 1]; if (lrc_calculated != lrc_received) { throw new ModbusResponseException("Wrong LRC"); } // Remove the LRC. _receiveBuffer.RemoveRange(_receiveBuffer.Count - 1, 1); // Remove address byte. _receiveBuffer.RemoveRange(0, 1); break; // If we're using MODBUS RTU... case ConnectionType.SerialRTU: // If the 16-bit CRC is incorrect... ushort crcCalculated = CRC16.CalcCRC16(_receiveBuffer.ToArray(), 0, _receiveBuffer.Count - 2); ushort crcReceived = BitConverter.ToUInt16(_receiveBuffer.ToArray(), _receiveBuffer.Count - 2); if (crcReceived != crcCalculated) { // TODO: Read until there's nothing left in the queue. throw new ModbusResponseException("Wrong CRC."); } // If the device address is incorrect... byte addr = _receiveBuffer[0]; if (addr != _sendBuffer[0]) { throw new ModbusResponseException("Wrong response address."); } // Remove address. _receiveBuffer.RemoveRange(0, 1); // Remove CRC. _receiveBuffer.RemoveRange(_receiveBuffer.Count - 2, 2); break; // If we're using MODBUS IP... case ConnectionType.UDPIP: case ConnectionType.TCPIP: // If the MBAP header is incorrect... ushort tid = ToUInt16(_receiveBuffer.ToArray(), 0); if (tid != _transactionID) { throw new ModbusResponseException("Wrong transaction ID."); } // If the protocol ID is incorect... ushort pid = ToUInt16(_receiveBuffer.ToArray(), 2); if (pid != ProtocolID) { throw new ModbusResponseException("Wrong transaction ID."); } // If the length is incorrect... ushort len = ToUInt16(_receiveBuffer.ToArray(), 4); if ((_receiveBuffer.Count - MbapHeaderLength + 1) < len) { throw new ModbusResponseException("Wrong message length."); } // If the device address is incorrect... byte uid = _receiveBuffer[6]; if (uid != _sendBuffer[6]) { throw new ModbusResponseException("Wrong device address."); } // Remove the header from the message. _receiveBuffer.RemoveRange(0, MbapHeaderLength); break; } // If an error message was received... if (_receiveBuffer[0] > 0x80) { // An error has been reported. // Throw an error according to the exception code. switch (_receiveBuffer[1]) { case 1: throw new ModbusIllegalFunctionException(); case 2: throw new ModbusIllegalDataAddressException(); case 3: throw new ModbusIllegalDataValueException(); case 4: throw new ModbusSlaveDeviceFailureException(); } } } }
/// <summary> /// Modbus slave response manager /// </summary> /// <param name="send_buffer">Send buffer</param> /// <param name="receive_buffer">Receive buffer</param> /// <param name="flux">Object for flux manager</param> protected void IncomingMessagePolling(List <byte> send_buffer, List <byte> receive_buffer, object flux) { ushort transaction_id = 0; long elapsed_time; byte unit_id = 0; Stopwatch sw = new Stopwatch(); // Empting reception buffer receive_buffer.Clear(); // Start reception loop int data = -1; sw.Start(); bool in_ric = false; bool done = false; _charTimeout = 0; do { data = ReadByte(flux); if (data != -1) { if (!in_ric) { in_ric = true; } if (_connectionType == ConnectionType.SERIAL_ASCII) { if ((byte)data == Encoding.ASCII.GetBytes(new char[] { ASCII_START_FRAME }).First()) { receive_buffer.Clear(); } } receive_buffer.Add((byte)data); } else if ((data == -1) && in_ric) { done = true; } else { Thread.Sleep(1); } // Calc elapsed time since reception start elapsed_time = sw.ElapsedMilliseconds; } while ((elapsed_time < RxTimeout) && _run && (!done)); _timeoutStopwatch.Stop(); sw.Stop(); // Check for a stop request if (!_run) { // TODO: Not sure what type of exception is best to throw here; likely not a MODBUS one. throw new ModbusException("Thread block request."); } // Check for timeout error if (elapsed_time >= RxTimeout) { throw new ModbusTimeoutException("Timeout waiting for response."); } // Check message length if (receive_buffer.Count < 3) { throw new ModbusResponseException("Wrong message length."); } // Message received, start decoding... switch (_connectionType) { case ConnectionType.SERIAL_ASCII: // Check and delete start char if (Encoding.ASCII.GetChars(receive_buffer.ToArray()).FirstOrDefault() != ASCII_START_FRAME) { throw new ModbusTimeoutException("Start character not found."); } receive_buffer.RemoveAt(0); // Check and delete stop chars char[] end_chars = new char[] { ASCII_STOP_FRAME_1ST, ASCII_STOP_FRAME_2ND }; char[] last_two = Encoding.ASCII.GetChars(receive_buffer.GetRange(receive_buffer.Count - 2, 2).ToArray()); if (!end_chars.SequenceEqual(last_two)) { throw new ModbusTimeoutException("End characters not found."); } receive_buffer.RemoveRange(receive_buffer.Count - 2, 2); // Recode message in binary receive_buffer = GetBinaryBufferFromASCIIBytes(receive_buffer.ToArray()).ToList(); // Calc and remove LRC byte msg_lrc = receive_buffer[receive_buffer.Count - 1]; byte calc_lrc = LRC.CalcLRC(receive_buffer.ToArray(), 0, receive_buffer.Count - 1); if (msg_lrc != calc_lrc) { throw new ModbusResponseException("Wrong LRC"); } receive_buffer.RemoveAt(receive_buffer.Count - 1); // Analize destination address, if not present in database, discard message and continue unit_id = receive_buffer[0]; if (!ModbusDatabase.Any(x => x.UnitID == unit_id)) { return; } receive_buffer.RemoveAt(0); break; case ConnectionType.SERIAL_RTU: // Check CRC ushort msg_crc = BitConverter.ToUInt16(receive_buffer.ToArray(), receive_buffer.Count - 2); ushort calc_crc = CRC16.CalcCRC16(receive_buffer.ToArray(), 0, receive_buffer.Count - 2); if (msg_crc != calc_crc) { throw new ModbusResponseException("Wrong CRC."); } // Analize destination address, if not present in database, discard message and continue unit_id = receive_buffer[0]; if (!ModbusDatabase.Any(x => x.UnitID == unit_id)) { return; } // Message is ok, remove unit_id and CRC receive_buffer.RemoveRange(0, 1); receive_buffer.RemoveRange(receive_buffer.Count - 2, 2); break; case ConnectionType.UDP_IP: case ConnectionType.TCP_IP: // Decode MBAP Header transaction_id = ToUInt16(receive_buffer.ToArray(), 0); // Check protocol ID ushort protocol_id = ToUInt16(receive_buffer.ToArray(), 2); if (protocol_id != PROTOCOL_ID) { throw new ModbusResponseException("Wrong protocol ID."); } // Acquire data length and check it ushort len = ToUInt16(receive_buffer.ToArray(), 4); if ((receive_buffer.Count - 6) != len) { throw new ModbusResponseException("Wrong message length."); } // Analize destination address, if not present in database, discard message and continue unit_id = receive_buffer[6]; if (!ModbusDatabase.Any(x => x.UnitID == unit_id)) { return; } // Message is ok, remove MBAP header for reception buffer receive_buffer.RemoveRange(0, MBAP_HEADER_LEN); break; } // Adjust data and build response AdjAndReply(send_buffer, receive_buffer, flux, unit_id, transaction_id); }