📄 serialport.cs
字号:
// Read calls: size * n; GetCharCount calls: size * n; each byte "counted": size times.
// Solution II: Use a binary reduction and backtracking to reduce the number of calls.
// Read calls: size * log n; GetCharCount calls: size * log n; each byte "counted": size * (log n) / n times.
// We use the second, more complicated solution here. Note log is actually log_(size/size - 1)...
// we need to read some from the stream
// read *up to* the maximum number of bytes from the stream
// we can read more since we receive everything instantaneously, and we don't have enough,
// so when we do receive any data, it will be necessary and sufficient.
if (readTimeout == 0) return ReadBufferIntoChars(buffer, offset, count);
int startTicks = SafeNativeMethods.GetTickCount();
int justRead;
do {
internalSerialStream.Read(inBuffer, readLen, Encoding.GetMaxByteCount(count - charsWeAlreadyHave));
justRead = ReadBufferIntoChars(buffer, offset, count);
if (justRead > 0) return justRead;
} while (readTimeout == SerialPort.InfiniteTimeout || readTimeout - (SafeNativeMethods.GetTickCount() - startTicks) > 0);
// must've timed out w/o getting a character.
return 0;
}
// ReadBufferIntoChars reads from Serial Port's inBuffer up to *count* chars and
// places them in *buffer* starting at *offset*.
// This does not call any stream Reads, and so takes "no time".
private int ReadBufferIntoChars(char[] buffer, int offset, int count)
{
if (buffer==null)
throw new ArgumentNullException("buffer", InternalResources.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", InternalResources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", InternalResources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(InternalResources.GetResourceString("Argument_InvalidOffLen"));
if (!isOpen)
throw new InvalidOperationException("Serial Port Read - port not open");
if (count == 0) return 0;
// variables required for Binary-Reduce-Read algorithm developed below.
int totalBytesRead = 0; // total Bytes read from outside the buffer, i.e. the stream
int totalCharsRead = 0; // total chars read from outside the stream, => totalCharsRead <= totalBytesRead
int totalBytesJustRead; // total bytes read on any one internalSerialStream.Read call
int totalCharsJustRead; // total chars COMPLETED by any one such Read call, => totalCharsJustRead <= totalBytesJustRead
int lastFullCharPos = readPos; // first index AFTER last full char read, capped at ReadLen.
int backtrack = 0; // for backtracking part of the algorithm, temporary index walking back from the end of what we've read.
if (encoding.GetMaxByteCount(1) == 1)
{ // kill ASCII/ANSI encoding easily.
// read at least one and at most *count* characters
int bytesToRead = (count < (readLen - readPos) ? count : readLen - readPos);
encoding.GetChars(inBuffer, readPos, bytesToRead, buffer, offset);
readPos += bytesToRead;
if (readPos == readLen) readPos = readLen = 0;
return bytesToRead;
}
else
{
do
{
backtrack = 0;
// "read" count - totalCharsRead, one byte for each expected char.
totalBytesJustRead = count - totalCharsRead; // if there are count - totalChars Read chars left, we can safely read that many bytes.
totalBytesRead += totalBytesJustRead; // here totalBytesRead means total examined in inBuffer.
totalCharsJustRead = encoding.GetCharCount(inBuffer, lastFullCharPos, readPos + totalBytesRead - lastFullCharPos);
if (totalCharsJustRead > 0)
{
// go backwards until we know we have a full set of totalCharsJustRead bytes with no extra lead-bytes.
do
{
backtrack += 1;
} while (encoding.GetCharCount(inBuffer, lastFullCharPos, readPos + totalBytesRead - lastFullCharPos - backtrack) == totalCharsJustRead);
lastFullCharPos = readPos + totalBytesRead - backtrack + 1; // go back to starting position of last known char.
totalCharsRead += totalCharsJustRead;
}
} while (totalCharsRead < count);
// fill into destination buffer all the COMPLETE characters we've read.
int numCharsRead = encoding.GetChars(inBuffer, readPos, lastFullCharPos - readPos, buffer, offset);
readPos = lastFullCharPos;
if (readPos == readLen) readPos = readLen = 0;
return numCharsRead;
}
}
public int ReadByte()
{
if (!isOpen)
throw new InvalidOperationException("Serial Port Read - port not open");
if (readLen != readPos) // stuff left in buffer, so we can read from it
return inBuffer[readPos++];
return internalSerialStream.ReadByte(); // otherwise, ask the stream.
}
public string ReadAvailable()
{
byte [] bytesReceived = new byte[InBufferBytes];
if (readPos < readLen)
{ // stuff in internal buffer
Buffer.BlockCopy(inBuffer, readPos, bytesReceived, 0, readLen - readPos);
}
internalSerialStream.Read(bytesReceived, readLen - readPos, bytesReceived.Length - (readLen - readPos)); // get everything
int numCharsReceived = Encoding.GetCharCount(bytesReceived);
int lastFullCharIndex = bytesReceived.Length;
if (numCharsReceived == 0)
{
Buffer.BlockCopy(bytesReceived, 0, inBuffer, 0, bytesReceived.Length); // put it all back!
// don't change readPos. --> readPos == 0?
readPos = 0;
readLen = bytesReceived.Length;
return "";
}
do
{
lastFullCharIndex--;
} while (Encoding.GetCharCount(bytesReceived, 0, lastFullCharIndex) == numCharsReceived);
// we cleared the buffers above, so we can reset, but we needn't resize, since it was originally
// big enough to fit our trailing characters AND MORE.
readPos = readLen = 0;
Buffer.BlockCopy(bytesReceived, lastFullCharIndex + 1, inBuffer, 0, bytesReceived.Length - (lastFullCharIndex + 1));
return Encoding.GetString(bytesReceived, 0, lastFullCharIndex + 1);
}
// Design discussion:
// Problem: if '\r' occurs at the end of available stream data, should we wait for the potential '\n'?
// Solution: We have chosen to NOT wait for \n if \r occurs at the end of a ReadLine().
// Additionally, we have chosen to essentially "forget" about the potential '\n' following any terminating \r,
// NOT remembering through internal state that '\r' occurred as the most-recently read char, and in a ReadLine() call.
// Cons: 1. This means we may drop '\n' onto another read, possibly binary, if it follows in a subsequent transmission but
// was intended to be read by this ReadLine().
// 2. We thus give users the feeling we're corrupting their data, which may be happening.
// Pros: 1. We don't hang until timeout if we get "xxx\r"
// 2. We don't remove any '\n' text patterns or 0x0a (= 10 = '\n') from tranmissions to which a
// leading '\n' should be grouped.
// 3. We do not clutter an otherwise intuitive, self-explanatory SerialPort API with a property requiring users to read documentation.
// 4. We recongize all flavors of newline: '\r', '\n', "\r\n".
// 5. We follow the pattern of the StreamReader class in Pro #4, and in disregarding an absent line feed char.
public string ReadLine()
{
if (!isOpen)
throw new InvalidOperationException("Serial Port Read - port not open");
string inBufferString;
bool carriageReturnFlag = false;
int startTicks = SafeNativeMethods.GetTickCount();
int lastChar; // holds return of ReadOneChar(), which may be a timeout indicator, so we need an int.
int beginReadPos = readPos;
// store encoding-based byte lengths of carraige return, line feed characters.
char [] charTestBuf = new char[1];
charTestBuf[0] = '\r';
int crLength = encoding.GetByteCount(charTestBuf);
charTestBuf[0] = '\n';
int lfLength = encoding.GetByteCount(charTestBuf);
int timeUsed = 0;
int timeNow;
// for timeout issues, best to read everything already on the stream into our buffers.
readLen += internalSerialStream.Read(inBuffer, readLen, InBufferBytes - (readLen - readPos));
// read through the buffer one *character* at a time to find '\r', '\n', or '\r\n'
while (true)
{
timeNow = SafeNativeMethods.GetTickCount();
lastChar = ReadOneChar((readTimeout == InfiniteTimeout) ? InfiniteTimeout : readTimeout - timeUsed);
timeUsed += SafeNativeMethods.GetTickCount() - timeNow;
if (lastChar == -1) break; // we timed out.
// note, we assume this for all encodings, true for Unicode, ASCII, UTF7, UTF8.
if ((char) lastChar == '\r')
{
if (InBufferBytes == 0)
{
// return string representation of all characters UP TO '\r'
inBufferString = encoding.GetString(inBuffer, beginReadPos, readPos - beginReadPos - crLength);
readPos = readLen = 0; // reset read buffer, since we're at the very end
return inBufferString;
}
else if (carriageReturnFlag == true)
{
inBufferString = encoding.GetString(inBuffer, beginReadPos, readPos - beginReadPos - 2 * crLength);
// "unread" last non-linefeed character, which means we can't set it to zero beforehand in ReadOneChar().
readPos -= crLength;
return inBufferString;
}
else
{
// wait to look at next char, to see if it's '\n'. We're returning after next pass-through.
carriageReturnFlag = true;
}
}
else if ((char) lastChar == '\n')
{
// case: we found '\r\n'. Return everything up to, not including, those characters.
if (carriageReturnFlag == true)
{
inBufferString = encoding.GetString(inBuffer, beginReadPos, readPos - beginReadPos - crLength - lfLength);
// readPos incremented in ReadOneChar(), so do not do it here.
if (readPos == readLen) readPos = readLen = 0;
return inBufferString;
}
else
{
// case: we found '\n'. Return everything up to, not including, that character.
inBufferString = encoding.GetString(inBuffer, beginReadPos, readPos - beginReadPos - lfLength);
// readPos incremented in ReadOneChar(), so don't do it here.
if (readPos == readLen) readPos = readLen = 0;
return inBufferString;
}
}
else
{
// here we have "peek"ed beyond '\r' to see if '\n' next, and we just found '\rX...', char x != '\n'.
// Put X "back in the stream", return everything up to '\r'.
if (carriageReturnFlag == true)
{
charTestBuf[0] = (char) lastChar;
int lastCharLength = encoding.GetByteCount(charTestBuf);
inBufferString = encoding.GetString(inBuffer, beginReadPos, readPos - beginReadPos - crLength - lastCharLength);
// "unread" last non-linefeed character, which means we can't set it to zero beforehand in ReadOneChar().
readPos -= lastCharLength;
return inBufferString;
}
}
}
// we broke out due to timeout.
// need to reset read position, since ReadOneChar() advances it.
readPos = beginReadPos;
// Important note: we are necessarily in a time-out mode here, because even if "ABC\nDEF" occurs at the end of the stream, we
// can only return ABC as full line, and then wait for whatever will complete DEF, since we have no reason to expect
// that no more data exists. We then save DEF in our buffer, which can be read char-by-char, or however the user desires.
// we haven't found a line before time ran out, but we've already tossed everything into the read buffer
return (string) null;
}
public void SetBreak()
{
if (!isOpen)
throw new InvalidOperationException("SetBreak - port not open");
internalSerialStream.SetBreak();
inBreak = true;
}
// Writes string to output, no matter string's length.
public void Write(string str)
{
if (!isOpen)
throw new InvalidOperationException("Serial Port Write - port not open!");
if (str == null)
throw new ArgumentNullException("write buffer", InternalResources.GetResourceString("ArgumentNull_String"));
if (str.Length == 0) return;
byte [] bytesToWrite;
bytesToWrite = encoding.GetBytes(str);
internalSerialStream.Write(bytesToWrite, 0, bytesToWrite.Length, writeTimeout);
}
// encoding-dependent Write-chars method.
// Probably as performant as direct conversion from ASCII to bytes, since we have to cast anyway (we can just call GetBytes)
public void Write(char[] buffer, int offset, int count) {
if (!isOpen)
throw new InvalidOperationException("Serial Port Write - port not open!");
if (buffer == null)
throw new ArgumentNullException("write buffer", InternalResources.GetResourceString("ArgumentNull_String"));
if (buffer.Length == 0) return;
byte [] byteArray = Encoding.GetBytes(buffer,offset, count);
Write(byteArray, 0, byteArray.Length);
}
// Writes a specified section of a byte buffer to output.
public void Write(byte[] buffer, int offset, int count)
{
if (!isOpen)
throw new InvalidOperationException("Serial Port Write - port not open!");
if (buffer==null)
throw new ArgumentNullException("buffer", InternalResources.GetResourceString("ArgumentNull_Buffer"));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", InternalResources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", InternalResources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
if (buffer.Length - offset < count)
throw new ArgumentException(InternalResources.GetResourceString("Argument_InvalidOffLen"));
if (buffer.Length == 0) return;
internalSerialStream.Write(buffer, offset, count, writeTimeout);
}
// ----- SECTION: internal utility methods ----------------*
// included here just to use the event filter to block unwanted invocations of the Serial Port's events.
// Plus, this enforces the requirement on the received event that the number of buffered bytes >= receivedBytesThreshold
private void CatchErrorEvents(object src, SerialEventArgs e)
{
int eventsCaught = (int) e.EventType & (int) eventFilter; // nix any unwanted events.
if ((eventsCaught & (int) (SerialEvents.Frame | SerialEvents.Overrun | SerialEvents.RxOver
| SerialEvents.RxParity | SerialEvents.TxFull)) != 0)
{
ErrorEvent(src, e);
}
}
private void CatchPinChangedEvents(object src, SerialEventArgs e)
{
int eventsCaught = (int) e.EventType & (int) eventFilter; // nix any unwanted events.
if (((eventsCaught & (int) (SerialEvents.CDChanged | SerialEvents.CtsChanged | SerialEvents.DsrChanged | SerialEvents.Ring | SerialEvents.Break)) != 0))
{
PinChangedEvent(src, e);
}
}
private void CatchReceivedEvents(object src, SerialEventArgs e)
{
int eventsCaught = (int) e.EventType & (int) eventFilter; // nix any unwanted events.
int inBufferBytes = InBufferBytes;
if (((eventsCaught & (int) (SerialEvents.ReceivedChars | SerialEvents.EofReceived)) != 0)
&& (InBufferBytes >= receivedBytesThreshold))
ReceivedEvent(src, e); // here, do your reading, etc.
}
// doubles size of buffer, copying data resident in old buffer to new buffer, like a C realloc() call.
private void ResizeBuffer()
{
Debug.Assert(inBuffer.Length >= readLen, "ResizeBuffer - readLen > inBuffer.Length");
byte[] newBuffer = new byte[inBuffer.Length * 2];
Buffer.BlockCopy(inBuffer, 0, newBuffer, 0, inBuffer.Length);
inBuffer = newBuffer;
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -