forked from jcurl/RJCP.DLL.SerialPortStream
/
SerialPortStream.cs
2069 lines (1909 loc) · 93.4 KB
/
SerialPortStream.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright © Jason Curl 2012-2016
// Sources at https://github.com/jcurl/SerialPortStream
// Licensed under the Microsoft Public License (Ms-PL)
// Enable the following define to enable behaviour in the number of bytes similar to the MS
// implementation. It's recommended not to do this, as it's not consistent with the rest of
// this implementation.
//#define DRIVERBUFFEREDBYTES
namespace RJCP.IO.Ports
{
using System;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Threading;
using Datastructures;
using Native;
#if !NETSTANDARD15
using System.Runtime.Remoting.Messaging;
#else
using System.Runtime.InteropServices;
#endif
/// <summary>
/// The SerialPortStream is a stream class to communicate with serial port based devices.
/// </summary>
/// <remarks>
/// This implementation is a ground up reimplementation of the Microsoft SerialPort class
/// but one that is a stream. There are numerous small issues with the Microsoft .NET 4.0
/// implementation (and assumed earlier) that this class attempts to resolve.
/// <para>For detailed information about serial port programming, refer to the site:
/// http://msdn.microsoft.com/en-us/library/ms810467.aspx</para>
/// <para>When instantiating.</para>
/// </remarks>
public partial class SerialPortStream : Stream
{
private INativeSerial m_NativeSerial;
private SerialBuffer m_Buffer;
private ReadToCache m_ReadTo = new ReadToCache();
#region Public constants
/// <summary>
/// Indicates that no time out should occur.
/// </summary>
public const int InfiniteTimeout = -1;
#endregion
#region Constructor, Port Name, Open
/// <summary>
/// Constructor. Create a stream that doesn't connect to any port.
/// </summary>
/// <remarks>
/// This constructor initialises a stream object, but doesn't assign it to any COM port.
/// The properties then assume default settings. No COM port is opened and queried.
/// </remarks>
public SerialPortStream()
{
m_NativeSerial = CreateNativeSerial();
if (m_NativeSerial == null)
throw new NotSupportedException("SerialPortStream is not supported on this platform");
InitialiseEvents();
SerialTrace.AddRef();
}
/// <summary>
/// Constructor. Create a stream that connects to the specified port.
/// </summary>
/// <remarks>
/// This constructor attempts to bind directly to the port given. Properties assume
/// the settings of the port provided. Exceptions may occur if the port cannot be
/// opened.
/// </remarks>
/// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
public SerialPortStream(string port) : this()
{
if (port != null) m_NativeSerial.PortName = port;
}
/// <summary>
/// Constructor. Create a stream that connects to the specified port and sets the initial baud rate.
/// </summary>
/// <remarks>
/// The stream doesn't impose any arbitrary limits on setting the baud rate. It is passed
/// directly to the driver and it is up to the driver to determine if the baud rate is
/// settable or not. Normally, a driver will attempt to set a baud rate that is within 5%
/// of the requested baud rate (but not guaranteed).
/// </remarks>
/// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
/// <param name="baud">The baud rate that is passed to the underlying driver.</param>
public SerialPortStream(string port, int baud)
: this(port)
{
m_NativeSerial.BaudRate = baud;
}
/// <summary>
/// Constructor. Create a stream that connects to the specified port with standard parameters.
/// </summary>
/// <remarks>
/// The stream doesn't impose any arbitrary limits on setting the baud rate. It is passed
/// directly to the driver and it is up to the driver to determine if the baud rate is
/// settable or not. Normally, a driver will attempt to set a baud rate that is within 5%
/// of the requested baud rate (but not guaranteed).
/// <para>Not all combinations are supported. The driver will interpret the data and indicate
/// if configuration is possible or not.</para>
/// </remarks>
/// <param name="port">The name of the COM port, such as "COM1" or "COM33".</param>
/// <param name="baud">The baud rate that is passed to the underlying driver.</param>
/// <param name="data">The number of data bits. This is checked that the driver
/// supports the data bits provided. The special type 16X is not supported.</param>
/// <param name="parity">The parity for the data stream.</param>
/// <param name="stopbits">Number of stop bits.</param>
public SerialPortStream(string port, int baud, int data, Parity parity, StopBits stopbits)
: this(port)
{
m_NativeSerial.BaudRate = baud;
m_NativeSerial.DataBits = data;
m_NativeSerial.Parity = parity;
m_NativeSerial.StopBits = stopbits;
}
private static INativeSerial CreateNativeSerial()
{
#if NETSTANDARD15
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return new WinNativeSerial();
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return new UnixNativeSerial();
#else
int p = (int)Environment.OSVersion.Platform;
if (p == (int)PlatformID.Win32NT) {
return new WinNativeSerial();
} else if (p == 4 || p == 8 || p == 128) {
return new UnixNativeSerial();
}
#endif
return null;
}
/// <summary>
/// Get the version of this assembly (or components driving this assembly).
/// </summary>
/// <value>The version of the assembly and/or subcomponents.</value>
public string Version
{
get { return m_NativeSerial.Version; }
}
/// <summary>
/// Gets the port for communications, including but not limited to all available COM ports.
/// </summary>
/// <remarks>
/// A list of valid port names can be obtained using the GetPortNames method.
/// <para>When changing the port name, and the property UpdateOnPortSet is <b>true</b>, setting
/// this property will cause the port to be opened, status read and the port then closed. Thus, you
/// can use this behaviour to determine the actual settings of the port (which remain constant
/// until a program actually changes the port settings).</para>
/// <para>Setting this property to itself, while having UpdateOnPortSet to <b>true</b> has the
/// effect of updating the local properties based on the current port settings.</para>
/// </remarks>
public string PortName
{
get
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
return m_NativeSerial.PortName;
}
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Must provide a valid name for a COM port");
if (IsOpen && value != m_NativeSerial.PortName) throw new InvalidOperationException("Serial Port already opened");
m_NativeSerial.PortName = value;
}
}
/// <summary>
/// Update properties based on the current port, overwriting already existing properties.
/// </summary>
/// <exception cref="System.ObjectDisposedException"/>
/// <exception cref="System.InvalidOperationException">Serial Port already opened.</exception>
/// <remarks>
/// This method opens the serial port and retrieves the current settings from Windows.
/// These settings are then made available via the various properties, BaudRate, DataBits,
/// Parity, ParityReplace, Handshake, StopBits, TxContinueOnXoff, DiscardNull, XOnLimit
/// and XOffLimit.
/// </remarks>
public void GetPortSettings()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
m_NativeSerial.Open();
m_NativeSerial.GetPortSettings();
m_NativeSerial.Close();
}
/// <summary>
/// Opens a new serial port connection.
/// </summary>
/// <exception cref="InvalidOperationException">This object is already managing a serial port
/// connection.</exception>
/// <exception cref="System.ObjectDisposedException">SerialPortStream is disposed of.</exception>
/// <remarks>
/// Opens a connection to the serial port provided by the constructor or the Port property.
/// If this object is already managing a serial port, this object raises an exception.
/// <para>When opening the port, only the settings explicitly applied will be given to the
/// port. That is, if you read the default BaudRate as 115200, this value will only be
/// applied if you explicitly set it to 115200. Else the default baud rate of the serial
/// port when its opened will be used.</para>
/// <para>Normally when you instantiate this stream on a COM port, it is opened for a brief
/// time and queried for the capabilities and default settings. This allows your application
/// to use the settings that were already available (such as defined by the windows user
/// in the Control Panel, or the last open application). If you require to open the COM
/// port without briefly opening it to query its status, then you need to instantiate
/// this object through the default constructor. Set the property UpdateOnPortSet to false
/// and then set the Port property. Provide all the other properties you require then call
/// the Open() method. The port will be opened using the default properties providing
/// you with a consistent environment (independent of the state of the Operating System
/// or the driver beforehand).</para>
/// </remarks>
public void Open()
{
Open(true);
}
/// <summary>
/// Opens a new serial port connection with control if the port settings are initialised or not.
/// </summary>
/// <exception cref="System.ObjectDisposedException">SerialPortStream is disposed of.</exception>
/// <exception cref="System.InvalidOperationException">Serial Port already opened</exception>
/// <remarks>
/// Opens a connection to the serial port provided by the constructor or the Port property.
/// If this object is already managing a serial port, this object raises an exception.
/// <para>You can override the open so that no communication settings are retrieved or set.
/// This is useful for virtual COM ports that do not manage state bits (some as some emulated
/// COM ports or USB based communications that present themselves as a COM port but do not have
/// any underlying physical RS232 implementation).</para>
/// <note type="note">If you use this method to avoid setting parameters for the serial port,
/// instead only to open the serial port, you should be careful not to set any properties
/// associated with the serial port, as they will set the communications properties.</note>
/// </remarks>
public void OpenDirect()
{
Open(false);
}
private void Open(bool setCommState)
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
m_ReadCheckDeviceErrorNotified = false;
m_NativeSerial.Open();
try {
if (setCommState) {
m_NativeSerial.SetPortSettings();
// Fetch the actual settings and get the capabilities
m_NativeSerial.GetPortSettings();
}
// Create threads and start working with local buffers
if (m_Buffer == null) {
m_Buffer = m_NativeSerial.CreateSerialBuffer(m_ReadBufferSize, m_WriteBufferSize);
} else {
m_Buffer.Stream.Reset(true);
}
m_NativeSerial.StartMonitor(m_Buffer, PortName);
} catch {
m_NativeSerial.Close();
throw;
}
}
/// <summary>
/// Gets a value indicating the open or closed status of the SerialPortStream object.
/// </summary>
/// <remarks>
/// The IsOpen property tracks whether the port is open for use by the caller, not
/// whether the port is open by any application on the machine.
/// </remarks>
/// <value>
/// True if the serial port is open; otherwise, false. The default is false.
/// </value>
public bool IsOpen
{
get
{
lock (m_CloseLock) {
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
return m_NativeSerial.IsOpen;
}
}
}
private object m_CloseLock = new object();
/// <summary>
/// Closes the port connection, sets the IsOpen property to false. Does not dispose the object.
/// </summary>
/// <remarks>
/// This method will clean up the object so far as to close the port. Internal buffers remain
/// active that the stream can continue to read. Writes will throw an exception.
/// </remarks>
#if !NETSTANDARD15
public new void Close()
#else
public void Close()
#endif
{
lock (m_CloseLock) {
if (IsDisposed) return;
if (m_Buffer != null) m_Buffer.Stream.AbortWait();
m_NativeSerial.Close();
}
}
#endregion
#region Computer Configuration and Ports
/// <summary>
/// Gets an array of serial port names for the current computer.
/// </summary>
/// <returns>An array of serial port names for the current computer.</returns>
public static string[] GetPortNames()
{
using (INativeSerial serial = CreateNativeSerial()) {
if (serial == null) throw new NotSupportedException("SerialPortStream is not supported on this platform");
return serial.GetPortNames();
}
}
/// <summary>
/// Gets an array of serial port names and descriptions for the current computer.
/// </summary>
/// <remarks>
/// This method uses the Windows Management Interface to obtain its information. Therefore,
/// the list may be different to the list obtained using the GetPortNames() method which
/// uses other techniques.
/// <para>On Windows 7, this method shows to return normal COM ports, but not those
/// associated with a modem driver.</para>
/// </remarks>
/// <returns>An array of serial ports for the current computer.</returns>
public static PortDescription[] GetPortDescriptions()
{
using (INativeSerial serial = CreateNativeSerial()) {
if (serial == null) throw new NotSupportedException("SerialPortStream is not supported on this platform");
return serial.GetPortDescriptions();
}
}
#endregion
#region Reading and Writing Configuration
/// <summary>
/// Gets or sets the byte encoding for pre- and post-transmission conversion of text.
/// </summary>
/// <remarks>
/// The encoding is used for encoding string information to byte format when sending
/// over the serial port, or receiving data via the serial port. It is only used
/// with the read/write functions that accept strings (and not used for byte based
/// reading and writing).
/// </remarks>
public Encoding Encoding
{
get { return m_ReadTo.Encoding; }
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
m_ReadTo.Encoding = value;
}
}
private string m_NewLine = "\n";
/// <summary>
/// Gets or sets the value used to interpret the end of a call to the
/// ReadLine and WriteLine methods.
/// </summary>
/// <remarks>
/// A value that represents the end of a line. The default is a line feed, (NewLine).
/// </remarks>
public string NewLine
{
get { return m_NewLine; }
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (value == null) throw new ArgumentNullException("value", "Newline string may not be null");
if (string.IsNullOrEmpty(value)) throw new ArgumentException("Newline may not be empty", "value");
m_NewLine = value;
}
}
#endregion
#region Driver Settings
/// <summary>
/// Specify the driver In Queue at the time it is opened.
/// </summary>
/// <remarks>
/// This provides the driver a recommended internal input buffer, in bytes.
/// </remarks>
public int DriverInQueue
{
get
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
return m_NativeSerial.DriverInQueue;
}
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
m_NativeSerial.DriverInQueue = value;
}
}
/// <summary>
/// Specify the driver Out Queue at the time it is opened.
/// </summary>
/// <remarks>
/// This provides the driver a recommended internal output buffer, in bytes.
/// </remarks>
public int DriverOutQueue
{
get
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
return m_NativeSerial.DriverOutQueue;
}
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
m_NativeSerial.DriverOutQueue = value;
}
}
#endregion
/// <summary>
/// Gets a value that determines whether the current stream can time out.
/// </summary>
/// <returns>A value that determines whether the current stream can time out.</returns>
public override bool CanTimeout { get { return true; } }
#region Reading
/// <summary>
/// Check if this stream supports reading.
/// </summary>
/// <remarks>
/// Supported so long as the stream is not disposed.
/// </remarks>
public override bool CanRead
{
get { return !IsDisposed; }
}
private int m_ReadTimeout = -1;
/// <summary>
/// Define the time out when reading data from the stream.
/// </summary>
/// <remarks>
/// This defines the time out when data arrives in the buffered memory of this
/// stream, that is, when the driver indicates that data has arrived to the
/// application.
/// <para>Should the user perform a read operation and no data is available
/// to copy in the buffer, a time out will occur.</para>
/// <para>Set this property to -1 for an infinite time out.</para>
/// </remarks>
public override int ReadTimeout
{
get { return m_ReadTimeout; }
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (value < 0) {
m_ReadTimeout = -1;
} else {
m_ReadTimeout = value;
}
}
}
private int m_ReadBufferSize = 1048576;
/// <summary>
/// Gets or sets the size of the SerialPortStream input buffer.
/// </summary>
/// <remarks>
/// Sets the amount of buffering to use when reading data from the serial port.
/// Data is read locally into this buffered stream through another port.
/// <para>The Microsoft implementation uses this to set the buffer size of the
/// underlying driver. This implementation interprets the ReadBufferSize
/// differently by setting the local buffer which can be much larger (megabytes)
/// and independent of the low level driver.</para>
/// </remarks>
/// <exception cref="InvalidOperationException">An attempt was used to change
/// the size of the buffer while the port is open (and therefore buffering is
/// active).</exception>
public int ReadBufferSize
{
get { return m_ReadBufferSize; }
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (IsOpen) throw new InvalidOperationException("Serial Port already opened");
if (value <= 0) throw new ArgumentOutOfRangeException("value", "WriteBufferSize must be greater than zero");
int newBufferSize = value;
if (newBufferSize < 1024) newBufferSize = 1024;
if (m_ReadBufferSize != newBufferSize) {
if (m_Buffer != null) {
m_Buffer.Dispose();
m_Buffer = null;
}
m_ReadBufferSize = newBufferSize;
if (m_RxThreshold > value) m_RxThreshold = value;
}
}
}
private int m_RxThreshold = 1;
/// <summary>
/// Gets or sets the number of bytes in the read buffer before a DataReceived event occurs.
/// </summary>
public int ReceivedBytesThreshold
{
get
{
return m_RxThreshold;
}
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (value <= 0) throw new ArgumentOutOfRangeException("value", "Must be a positive value (1 or greater)");
if (value > m_ReadBufferSize)
throw new ArgumentOutOfRangeException("value", "Must be less or equal to the ReadBufferSize");
// Only raise an event if we think that we wouldn't have received an event otherwise
int btr = m_NativeSerial.BytesToRead;
if (btr < m_RxThreshold && btr > value) {
m_RxThreshold = value;
NativeSerial_DataReceived(this, new SerialDataReceivedEventArgs(SerialData.Chars));
} else {
m_RxThreshold = value;
}
}
}
#if DRIVERBUFFEREDBYTES
/// <summary>
/// Gets the number of bytes of data in the receive buffer and in the serial driver.
/// </summary>
/// <remarks>
/// This method returns the number of bytes available in the input read buffer.
/// Bytes that are cached by the driver itself are also (generally) accounted for.
/// <para>A small error in determining the number of bytes available to read
/// may occur, in particularly, an amount less than what is actually available
/// for reading may be returned.</para>
/// <para>Why we sometimes don't return the total number of bytes is due to
/// the asynchronous behaviour of reading from the serial port with consideration
/// to performance. Particularly, there might be a small time where the number
/// of bytes in the cached buffer is "x", and the number of bytes in the serial
/// driver is "y". Normally, the result will be the sum, "X+y". However, there
/// may be a very small window of opportunity that the size of the buffer will
/// be "x" and the number of bytes in the serial driver is zero, or less than
/// "y", because it has just been transferred to the local buffer, but has not
/// yet been accounted for.</para>
/// </remarks>
public int BytesToRead
{
get
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return 0;
lock (m_Buffer.ReadLock) {
return m_Buffer.Stream.BytesToRead + m_NativeSerial.BytesToRead;
}
}
}
#else
/// <summary>
/// Gets the number of bytes of data in the receive buffer.
/// </summary>
/// <remarks>
/// This method returns the number of bytes available in the input read buffer.
/// Bytes that are cached by the driver itself are not accounted for, as they
/// haven't yet been read by the local thread.
/// <para>This has the effect, that if the local buffer is full (let's say that
/// it is arbitrarily picked to be 64KB) and the local driver also has buffered
/// 4KB, only the size of the local buffer is given, so 64KB (instead of the
/// expected 68KB).</para>
/// </remarks>
public int BytesToRead
{
get
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return 0;
return m_Buffer.Stream.BytesToRead;
}
}
#endif
private void ReadCheck(byte[] 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");
}
private void ReadCheck(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");
}
private bool m_ReadCheckDeviceErrorNotified;
private bool ReadCheckDeviceError()
{
return ReadCheckDeviceError(false);
}
private bool ReadCheckDeviceError(bool immediate)
{
if (m_Buffer != null) {
if (IsOpen && !m_NativeSerial.IsRunning && m_Buffer.Stream.BytesToRead == 0) {
if (immediate || m_ReadCheckDeviceErrorNotified) {
// This should only happen if the monitoring/buffering threads
// have died without explicitly closing the serial port.
throw new System.IO.IOException("Device Error");
}
// We don't want to raise an exception the first time the issue is detected,
// to allow a Read to return zero for end-of-file conditions.
m_ReadCheckDeviceErrorNotified = true;
// Return true to indicate there was a device error. Next time this function
// is called, we raise an exception.
return true;
}
}
return false;
}
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the
/// position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns,
/// the buffer contains the specified byte array with the values between
/// <paramref name="offset" /> and (<paramref name="offset" />
/// + <paramref name="count" /> - 1) replaced by the bytes read from the
/// current source.</param>
/// <param name="offset">The zero-based byte offset in
/// <paramref name="buffer" /> at which to begin storing the data read
/// from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the
/// current stream.</param>
/// <exception cref="ObjectDisposedException"/>
/// <exception cref="ArgumentNullException">Null buffer provided.</exception>
/// <exception cref="ArgumentOutOfRangeException">Negative offset provided, or negative count provided.</exception>
/// <exception cref="ArgumentException">Offset and count exceed buffer boundaries.</exception>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than
/// the number of bytes requested if that many bytes are not currently
/// available, or zero (0) if the end of the stream has been reached.
/// </returns>
public override int Read(byte[] buffer, int offset, int count)
{
ReadCheck(buffer, offset, count);
if (m_Buffer == null) return 0;
if (ReadCheckDeviceError()) return 0;
if (count == 0) return 0;
return InternalBlockingRead(buffer, offset, count);
}
private int InternalBlockingRead(byte[] buffer, int offset, int count)
{
if (m_NativeSerial.IsRunning) {
bool ready = m_Buffer.Stream.WaitForRead(m_ReadTimeout);
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (!ready) {
ReadCheckDeviceError();
return 0;
}
}
return InternalRead(buffer, offset, count);
}
private int InternalRead(byte[] buffer, int offset, int count)
{
int bytes = m_Buffer.Stream.Read(buffer, offset, count);
if (bytes > 0) {
m_ReadTo.Reset(false);
}
return bytes;
}
private delegate int ReadDelegate(byte[] buffer, int offset, int count);
#if !NETSTANDARD15
/// <summary>
/// Begins an asynchronous read operation.
/// </summary>
/// <param name="buffer">The buffer to read the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data read from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="callback">An optional asynchronous callback, to be called when the read is complete.</param>
/// <param name="state">A user-provided object that distinguishes this particular asynchronous read request from other requests.</param>
/// <returns>An <see cref="IAsyncResult"/> object to be used with <see cref="EndRead"/>.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
ReadCheck(buffer, offset, count);
if (m_Buffer == null || count == 0 || m_Buffer.Stream.WaitForRead(0)) {
// Data in the buffer, we can return immediately
LocalAsync<int> ar = new LocalAsync<int>(state);
if (m_Buffer != null && count > 0) {
ar.Result = InternalRead(buffer, offset, count);
} else {
ar.Result = 0;
}
ar.IsCompleted = true;
ar.CompletedSynchronously = true;
if (callback != null) callback(ar);
return ar;
} else {
// No data in buffer, so we create a thread in the background
ReadDelegate read = InternalBlockingRead;
return read.BeginInvoke(buffer, offset, count, callback, state);
}
}
/// <summary>
/// Waits for the pending asynchronous read to complete.
/// </summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <exception cref="ObjectDisposedException"/>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
/// <returns>The number of bytes read from the stream, between zero (0) and the number of bytes you requested.
/// Streams return zero (0) only at the end of the stream, otherwise, they should block until at least one byte is available.</returns>
public override int EndRead(IAsyncResult asyncResult)
{
LocalAsync<int> localAsync = asyncResult as LocalAsync<int>;
if (localAsync != null) {
if (!localAsync.IsCompleted) localAsync.AsyncWaitHandle.WaitOne(-1);
localAsync.Dispose();
if (localAsync.Result == 0) ReadCheckDeviceError();
return localAsync.Result;
} else {
AsyncResult ar = (AsyncResult)asyncResult;
ReadDelegate caller = (ReadDelegate)ar.AsyncDelegate;
return caller.EndInvoke(asyncResult);
}
}
#endif
/// <summary>
/// Synchronously reads one byte from the SerialPort input buffer.
/// </summary>
/// <returns>The byte, cast to an Int32, or -1 if the end of the stream has been read.</returns>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
public override int ReadByte()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return -1;
if (ReadCheckDeviceError()) return -1;
if (m_NativeSerial.IsRunning) {
if (!m_Buffer.Stream.WaitForRead(m_ReadTimeout)) {
ReadCheckDeviceError();
return -1;
}
}
int value = m_Buffer.Stream.ReadByte();
if (value != -1) m_ReadTo.Reset(false);
return value;
}
/// <summary>
/// Reads a number of characters from the SerialPortStream input buffer and writes
/// them into an array of characters at a given offset.
/// </summary>
/// <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 time out.</returns>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
/// <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>
public int Read(char[] buffer, int offset, int count)
{
ReadCheck(buffer, offset, count);
if (m_Buffer == null) return 0;
if (ReadCheckDeviceError()) return 0;
if (count == 0) return 0;
TimerExpiry te = new TimerExpiry(m_ReadTimeout);
int chars;
do {
chars = m_ReadTo.Read(m_Buffer, buffer, offset, count);
if (chars == 0) {
if (!m_Buffer.Stream.WaitForRead(te.Timeout)) {
ReadCheckDeviceError();
return 0;
}
}
} while (chars == 0 && !te.Expired);
return chars;
}
/// <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 time out.</returns>
public int ReadChar()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return -1;
if (ReadCheckDeviceError()) return -1;
TimerExpiry te = new TimerExpiry(m_ReadTimeout);
int chars;
do {
chars = m_ReadTo.ReadChar(m_Buffer);
if (chars == -1) {
if (!m_Buffer.Stream.WaitForRead(te.Timeout)) {
ReadCheckDeviceError();
return -1;
}
}
} while (chars == -1 && !te.Expired);
return chars;
}
/// <summary>
/// Reads up to the NewLine value in the input buffer.
/// </summary>
/// <returns>The contents of the input buffer up to the first occurrence of
/// a NewLine value.</returns>
/// <exception cref="TimeoutException">Data was not available in the timeout specified.</exception>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
/// <exception cref="ObjectDisposedException"/>
public string ReadLine()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
return ReadTo(m_NewLine);
}
/// <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 fall back character. The original byte
/// sequence is removed and replaced with the byte equivalent of the fall back
/// character.</para>
/// <para>This method is rather slow, because it tries to preserve the byte buffer
/// in case of failure.</para>
/// <para>In case the data cannot be read, an exception is always thrown. So you may
/// assume that if this method returns, you have valid data.</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>
/// <exception cref="TimeoutException">Data was not available in the timeout specified.</exception>
/// <exception cref="IOException">Device Error (e.g. device removed).</exception>
/// <exception cref="ObjectDisposedException"/>
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");
if (m_Buffer == null) return null;
if (ReadCheckDeviceError()) return null;
TimerExpiry te = new TimerExpiry(m_ReadTimeout);
bool dataAvailable = false;
do {
string line;
if (m_ReadTo.ReadTo(m_Buffer, text, out line)) return line;
dataAvailable =
m_NativeSerial.IsRunning &&
m_ReadTo.ReadToWaitForNewData(m_Buffer, te.RemainingTime());
} while (dataAvailable && !te.Expired);
if (!dataAvailable) ReadCheckDeviceError(true);
throw new TimeoutException();
}
/// <summary>
/// Reads all immediately available bytes.
/// </summary>
/// <remarks>
/// Reads all data in the current buffer. If there is no data available, then no data
/// is returned. This is different to the Microsoft implementation, that will read all
/// data, and if there is no data, then it waits for data based on the time outs. This
/// method employs no time outs.
/// <para>Because this method returns only the data that is currently in the cached
/// buffer and ignores the data that is actually buffered by the driver itself,
/// there may be a slight discrepancy between the value returned by BytesToRead and the
/// actual length of the string returned.</para>
/// <para>This method differs slightly from the Microsoft implementation in that this
/// function doesn't initiate a read operation, as we have a dedicated thread to reading
/// data that is running independently.</para>
/// </remarks>
/// <returns>The contents of the stream and the input buffer of the SerialPortStream.</returns>
public string ReadExisting()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return null;
if (ReadCheckDeviceError()) return null;
return m_ReadTo.ReadExisting(m_Buffer);
}
/// <summary>
/// Discards data from the serial driver's receive buffer.
/// </summary>
/// <remarks>
/// This function will discard the receive buffer of the SerialPortStream.
/// </remarks>
public void DiscardInBuffer()
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (m_Buffer == null) return;
lock (m_Buffer.ReadLock) {
m_Buffer.Stream.DiscardInBuffer();
if (m_NativeSerial.IsOpen) m_NativeSerial.DiscardInBuffer();
}
}
#endregion
#region Writing
/// <summary>
/// Check if this stream supports writing.
/// </summary>
/// <remarks>
/// Supported so long as the stream is not disposed.
/// </remarks>
public override bool CanWrite
{
get { return !IsDisposed && IsOpen && m_NativeSerial.IsRunning; }
}
private int m_WriteTimeout = -1;
/// <summary>
/// Define the time out when writing data to the local buffer.
/// </summary>
/// <remarks>
/// This defines the time out when writing data to the local buffer.
/// No guarantees are given to when the data will actually be transferred
/// over to the serial port as this is dependent on the hardware configuration
/// and flow control.
/// <para>When writing data to the stream buffer, a time out will
/// occur if not all data can be written to the local buffer and the buffer
/// wasn't able to empty itself in the period given by the time out.</para>
/// <para>Naturally then, this depends on the size of the send buffer
/// in use, how much data is already in the buffer, how fast the data
/// can leave the buffer.</para>
/// <para>In case the data cannot be written to the buffer in the given
/// time out, no data will be written at all.</para>
/// </remarks>
public override int WriteTimeout
{
get { return m_WriteTimeout; }
set
{
if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
if (value < 0) {