/// <summary> /// Reads a string up to the specified <i>text</i> in the input buffer. /// </summary> /// <remarks> /// The ReadTo() function will read text from the byte buffer up to a predetermined /// limit (1024 characters) when looking for the string <i>text</i>. If <i>text</i> /// is not found within this limit, data is thrown away and more data is read /// (effectively consuming the earlier bytes). /// <para>this method is provided as compatibility with the Microsoft implementation. /// There are some important differences however. This method attempts to fix a minor /// pathological problem with the Microsoft implementation. If the string <i>text</i> /// is not found, the MS implementation may modify the internal state of the decoder. /// As a workaround, it pushes all decoded characters back into its internal byte /// buffer, which fixes the problem that a second call to the ReadTo() method returns /// the consistent results, but a call to Read(byte[], ..) may return data that was /// not actually transmitted by the DCE. This would happen in case that an invalid /// byte sequence was found, converted to a fallback character. The original byte /// sequence is removed and replaced with the byte equivalent of the fallback /// character.</para> /// <para>This method is rather slow, because it tries to preserve the byte buffer /// in case of failure. If your application works entirely by using character /// sequences, you may get much better performance by using Read(char[], ...) and /// searching for string sequences yourself, as Read(char[], ...) does a complete /// conversion bytes to characters in as few steps as possible.</para> /// </remarks> /// <param name="text">The text to indicate where the read operation stops.</param> /// <returns>The contents of the input buffer up to the specified <i>text</i>.</returns> public string ReadTo(string text) { if (IsDisposed) throw new ObjectDisposedException("SerialPortStream"); if (string.IsNullOrEmpty(text)) throw new ArgumentException("Parameter text shall not be null or empty", "text"); // Decoders in .NET are designed for streams and not really for reading // a little bit of data. By design, they are "greedy", they consume as much // byte data as possible. The data that they consume is cached internally. // Because it's not possible to ask the decoder only to decode if there // is sufficient bytes, we have to keep account of how many bytes are // consumed for each character. // Hence, m_ReadIncomplete tells us if we've consumed bytes, but have not // yet received a converted character. m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: BEGIN"); bool changedtext = !text.Equals(m_ReadToString); if (changedtext) { // Last search might have had a read overflow and we're now looking for a different // string. So we need to reset our buffer and start from the beginning ReadToOverflowCheck(); } int readlen = m_Read.Length - (m_ReadIncomplete ? 1 : 0); if (changedtext && readlen >= text.Length) { // Check if the text already exists string lbuffer = m_Read.GetString(readlen); int p = lbuffer.IndexOf(text, StringComparison.Ordinal); if (p != -1) { // It does exist, so consume up to the buffered portion string result = lbuffer.Substring(0, p); int l = p + text.Length; ReadToConsume(l); return result; } } TimerExpiry te = new TimerExpiry(m_ReadTimeout); while (!ReadToMatch(text)) { int bytesRead; int c = m_SerialPort.SerialPortIo.PeekChar(m_ReadOffset, Decoder, out bytesRead); m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: c = {0} = PeekChar(m_ReadOffset={1}, Decoder, out bytesRead={2})", c, m_ReadOffset, bytesRead); if (c != -1) { if (m_ReadIncomplete) { int lb = m_Read.Length - 1; m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]); m_Read[lb] = (char)c; m_ReadOffsets[lb] += bytesRead; m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == true; m_Read[{0}]={1}; m_ReadOffsets[{0}]={2}", lb, (char)c, m_ReadOffsets[lb]); } else { if (m_Read.Free == 0) { m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free==0"); // Discard the oldest character in preference for the newer character if (m_ReadOverflow == -1) { // Overflow. We capture the first byte, because we'll have to reset the decoder later m_ReadOverflowChar = m_Read[0]; m_ReadOverflow = m_ReadOffsets[0]; } m_Read.Consume(1); m_ReadOffsets.Consume(1); } m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Append({0}); m_ReadOffsets.Append({1})", (char)c, bytesRead); m_Read.Append((char)c); m_ReadOffsets.Append(bytesRead); } m_ReadOffset += bytesRead; m_ReadIncomplete = false; m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset); } else { if (bytesRead > 0) { if (!m_ReadIncomplete) { // The decoder consumed bytes, even though we don't have a character if (m_Read.Free == 0) { m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == 0"); m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_Read.Consume(1)"); m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0} - {1}", m_ReadOffset, m_ReadOffsets[0]); // Discard the oldest character in preference for the newer character if (m_ReadOverflow == -1) { // Overflow. We capture the first byte, because we'll have to reset the decoder later m_ReadOverflowChar = m_Read[0]; m_ReadOverflow = m_ReadOffsets[0]; } m_Read.Consume(1); m_ReadOffsets.Consume(1); m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadOffset = {0}", m_ReadOffset); } m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == false; m_Read.Free == {0}", m_Read.Free); m_Read.Append((char)0); m_ReadOffsets.Append(bytesRead); m_ReadOffset += bytesRead; m_ReadIncomplete = true; } else { int lb = m_Read.Length - 1; m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]); m_ReadOffsets[lb] += bytesRead; m_ReadOffset += bytesRead; m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: m_ReadIncomplete == true; m_ReadOffsets[{0}]={1}", lb, m_ReadOffsets[lb]); } } else { m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: WaitForReadEvent({0}, {1})", m_ReadOffset + 1, te.RemainingTime()); // Need to wait for the next byte if (!m_SerialPort.SerialPortIo.WaitForReadEvent(m_ReadOffset + 1, te.RemainingTime())) { m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: TIMEOUT"); // Remember the string for the next timeout m_ReadToString = text; throw new TimeoutException(); } } } } m_SerialPort.SerialPortIo.DiscardInBuffer(m_ReadOffset); string rbuffer = m_Read.GetString(); rbuffer = rbuffer.Substring(0, m_Read.Length - text.Length); ReadToReset(); m_TraceRT.TraceEvent(System.Diagnostics.TraceEventType.Verbose, 0, "ReadTo: RESULT = {0}", rbuffer); return rbuffer; }
/// <summary> /// Synchronously reads one character from the SerialPortStream input buffer. /// </summary> /// <returns>The character that was read. -1 indicates no data was available /// within the timeout.</returns> public int ReadChar() { if (IsDisposed) throw new ObjectDisposedException("SerialPortStream"); // Dealing with cached characters read by the ReadTo() method ReadToOverflowCheck(); if (IsReadToBuffered()) { // Check there is at least one valid byte. if the condition: // (m_ReadIncomplete == true && m_Read.Length == 1 || m_Read.Length < 1) // then there are no valid bytes available. if (!m_ReadIncomplete || m_Read.Length > 1) { int value = m_Read[0]; ReadToConsume(1); return value; } } ReadToReset(); // Reading data from the byte buffer TimerExpiry t = new TimerExpiry(m_ReadTimeout); int timeout = m_ReadTimeout; do { if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return -1; int c = m_SerialPort.SerialPortIo.ReadChar(this.Decoder); if (c != -1) return c; timeout = t.RemainingTime(); } while (timeout > 0); return -1; }
/// <summary> /// Wait for sufficient buffer space to be available in the write buffer within a timeout. /// </summary> /// <param name="count">The number of bytes that should be available.</param> /// <param name="timeout">The timeout in milliseconds.</param> /// <returns><b>true</b> if there is at least <c>count</c> bytes available in the write /// buffer within <c>timeout</c> milliseconds. <b>false</b> is always returned if no /// buffers have been allocated or if the serial thread isn't running (without a timeout). /// </returns> public bool WaitForWriteEvent(int count, int timeout) { if (m_Buffers == null || !IsRunning) return false; int free; lock (m_WriteLock) { // If we can't write the entire buffer to the WRITE buffer, we should wait free = m_Buffers.WriteBuffer.Free; if (free < count) m_WriteBufferNotFullEvent.Reset(); } // Wait until we can write the full amount of data to the buffer TimerExpiry exp = new TimerExpiry(timeout); while (free < count) { if (!m_WriteBufferNotFullEvent.WaitOne(exp.RemainingTime())) { return false; } else { lock (m_WriteLock) { // If we can't write the entire buffer to the WRITE buffer, we should wait free = m_Buffers.WriteBuffer.Free; if (free < count) m_WriteBufferNotFullEvent.Reset(); } } } return true; }
/// <summary> /// Reads a number of characters from the SerialPortStream input buffer and writes /// them into an array of characters at a given offset. /// </summary> /// <remarks> /// This function converts the data in the local buffer to characters based on the /// encoding defined by the encoding property. The encoder used may buffer data between /// calls if characters may require more than one byte of data for its interpretation /// as a character. /// </remarks> /// <param name="buffer">The character array to write the input to.</param> /// <param name="offset">Offset into the buffer where to start putting the data.</param> /// <param name="count">Maximum number of bytes to read into the buffer.</param> /// <returns>The actual number of bytes copied into the buffer, 0 if there was a timeout.</returns> public int Read(char[] buffer, int offset, int count) { if (IsDisposed) throw new ObjectDisposedException("SerialPortStream"); if (buffer == null) throw new ArgumentNullException("buffer", "NULL buffer provided"); if (offset < 0) throw new ArgumentOutOfRangeException("offset", "Negative offset provided"); if (count < 0) throw new ArgumentOutOfRangeException("count", "Negative count provided"); if (buffer.Length - offset < count) throw new ArgumentException("offset and count exceed buffer boundaries"); if (count == 0) return 0; // Dealing with cached characters read by the ReadTo() method int cached = 0; ReadToOverflowCheck(); if (IsReadToBuffered()) { int readlen = Math.Min(count, m_Read.Length - (m_ReadIncomplete ? 1 : 0)); if (readlen > 0) { cached = m_Read.CopyTo(buffer, offset, readlen); ReadToConsume(cached); if (cached == count) { // Read operation is complete, may/may not be data in the cache return cached; } offset += cached; count -= cached; // If there are no bytes in the serial driver, we don't need to wait if (m_SerialPort.SerialPortIo.BufferedBytesToRead == 0) return cached; } } ReadToReset(); // Obtaining bytes from our buffered I/O thread TimerExpiry t = new TimerExpiry(m_ReadTimeout); int timeout = m_ReadTimeout; do { if (!m_SerialPort.SerialPortIo.WaitForReadEvent(timeout)) return 0; int c = m_SerialPort.SerialPortIo.Read(buffer, offset, count, this.Decoder); if (c > 0) return c; timeout = t.RemainingTime(); } while (timeout > 0); return 0; }
/// <summary> /// Wait for there to be at least <i>bytes</i> by a timeout. /// </summary> /// <param name="bytes">Number of bytes expected to be in the input buffer</param> /// <param name="timeout">Timeout in milliseconds</param> /// <returns><b>true</b> if there is at least <i>bytes</i> number of data in the /// read buffer. <b>false</b> if there is not enough data within the timeout /// specified.</returns> public bool WaitForReadEvent(int bytes, int timeout) { if (IsRunning) { TimerExpiry te = new TimerExpiry(timeout); while (true) { lock (m_ReadLock) { if (m_Buffers.ReadBuffer.Length >= bytes) return true; m_ReadBufferEvent.Reset(); } if (!m_ReadBufferEvent.WaitOne(timeout)) return false; timeout = te.RemainingTime(); } } else { if (m_Buffers == null) return false; if (m_Buffers.ReadBuffer.Length < bytes) { // No data in the buffer to read. No need to timeout, as we're not running // and therefore don't expect more data return false; } } return true; }