-
Notifications
You must be signed in to change notification settings - Fork 49
/
index.html
2036 lines (2024 loc) · 81.9 KB
/
index.html
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
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>
Web Serial API
</title>
<script class='remove' src='https://www.w3.org/Tools/respec/respec-w3c'
async></script>
<script class='remove' src="respecConfig.js"></script>
<link rel="stylesheet" href="styles/spec.css">
</head>
<body>
<section id='abstract'>
The <cite>Serial API</cite> provides a way for websites to read and write
from a serial device through script. Such an API would bridge the web and
the physical world, by allowing documents to communicate with devices
such as microcontrollers, 3D printers, and other serial devices. There is
also a companion <a href=
"https://github.com/WICG/serial/blob/main/EXPLAINER.md">explainer</a>
document.
</section>
<section id='sotd'>
This is a work in progress. All <a href=
"https://github.com/WICG/serial">contributions</a> welcome.
</section>
<section data-dfn-for="Navigator">
<h2>
Extensions to the {{Navigator}} interface
</h2>
<pre class="idl">
[Exposed=Window, SecureContext]
partial interface Navigator {
[SameObject] readonly attribute Serial serial;
};
</pre>
<h3>
<dfn>serial</dfn> attribute
</h3>When getting, the {{Navigator/serial}} attribute always returns the
same instance of the {{Serial}} object.
</section>
<section data-dfn-for="WorkerNavigator">
<h2>
Extensions to the {{WorkerNavigator}} interface
</h2>
<pre class="idl">
[Exposed=DedicatedWorker, SecureContext]
partial interface WorkerNavigator {
[SameObject] readonly attribute Serial serial;
};
</pre>
<h3>
<dfn>serial</dfn> attribute
</h3>When getting, the {{WorkerNavigator/serial}} attribute always
returns the same instance of the {{Serial}} object.
</section>
<section data-dfn-for="Serial">
<h2>
{{Serial}} interface
</h2>
<pre class="idl">
[Exposed=(DedicatedWorker, Window), SecureContext]
interface Serial : EventTarget {
attribute EventHandler onconnect;
attribute EventHandler ondisconnect;
Promise<sequence<SerialPort>> getPorts();
[Exposed=Window] Promise<SerialPort> requestPort(optional SerialPortRequestOptions options = {});
};
</pre>
<section>
<h3>
<dfn>requestPort()</dfn> method
</h3>
<aside class="example">
<p>
When the user first visits a site it will not have permission to
access any serial devices. A site must first call
{{Serial/requestPort()}}. This call gives the browser the
opportunity to prompt the user for which device the site should be
allowed to control. If the site is designed to work with a
particular device which is always connected via USB the site can
provide a filter restricting the devices the user can select to
only those that would be compatible. For example, a site which
programs Arduino-powered robots could specify a like the following
to limit the set of selectable ports to only USB devices with
Arduino's USB vendor ID,
</p>
<pre class="js">
const filter = { usbVendorId: 0x2341 };
const port = await navigator.serial.requestPort({ filters: [filter] });
</pre>
<p>
If on the other hand the site expects to be used with a wide
variety of devices or devices connected through a USB to serial
converter it may specify no filter at all and rely on the user to
select the appropriate device,
</p>
<pre class="js">
const port = await navigator.serial.requestPort();
</pre>
<p>
Asking the user to choose a port requires showing a prompt to the
user and so the site must have [=transient activation=] from
something like the user clicking a button.
</p>
<pre class="html">
<button id="connect">Connect</button>
</pre>
<pre class="js">
const connectButton = document.getElementById("connect");
connectButton.addEventListener('click', () => {
try {
const port = await navigator.serial.requestPort();
// Continue connecting to the device attached to |port|.
} catch (e) {
// The prompt has been dismissed without selecting a device.
}
});
</pre>
<p>
The user may choose not to select a device, in which case the
{{Promise}} will be rejected with a "{{NotFoundError}}"
{{DOMException}} that the site must handle.
</p>
</aside>
<p>
The {{Serial/requestPort()}} method steps are:
</p>
<ol>
<li>Let |promise:Promise| be [=a new promise=].
</li>
<li>If [=this=]'s [=relevant global object=]'s [=associated
Document=] is not [=allowed to use=] the [=policy-controlled
feature=] named "[=policy-controlled feature/serial=]", [=reject=]
|promise| with a "{{SecurityError}}" {{DOMException}} and return
|promise|.
</li>
<li>If the [=relevant global object=] of [=this=] does not have
[=transient activation=], [=reject=] |promise| with a
"{{SecurityError}}" {{DOMException}} and return |promise|.
</li>
<li>If |options|["{{SerialPortRequestOptions/filters}}"] is present,
then for each |filter:SerialPortFilter| in
|options|["{{SerialPortRequestOptions/filters}}"] run the following
steps:
<ol>
<li>If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is
present:
<ol>
<li>
If |filter|["{{SerialPortFilter/usbVendorId}}"] is present,
[=reject=] |promise| with a {{TypeError}} and return |promise|.
</li>
<li>
If |filter|["{{SerialPortFilter/usbProductId}}"] is present,
[=reject=] |promise| with a {{TypeError}} and return |promise|.
</li>
</ol>
</li>
<li>If |filter|["{{SerialPortFilter/usbVendorId}}"] is not
present, [=reject=] |promise| with a {{TypeError}} and return
|promise|.
<div class="note">
This check implements the combined rule that a
{{SerialPortFilter}} cannot be empty and if
{{SerialPortFilter/usbProductId}} is specified then
{{SerialPortFilter/usbVendorId}} must also be specified.
</div>
</li>
</ol>
</li>
<li>Run the following steps [=in parallel=]:
<ol>
<li>Let |allPorts:list<SerialPort>| be an empty [=list=].
</li>
<li>[=list/For each=] Bluetooth device registered with the system:
<ol>
<li>[=list/For each=] {{BluetoothServiceUUID}}
|uuid:BluetoothServiceUUID| supported by the device:
<ol>
<li>If |uuid| is not a [=blocked Bluetooth service class
UUID=]:
<ul>
<li>If |uuid| is equal to the [=Serial Port Profile
service class ID=], or
</li>
<li>
|options|["{{SerialPortRequestOptions/allowedBluetoothServiceClassIds}}"]
is present and [=list/contains=] |uuid|:
<ol>
<li>Let |port:SerialPort| be a {{SerialPort}}
representing the service on the Bluetooth device.
</li>
<li>[=list/Append=] |port| to |allPorts|.
</li>
</ol>
</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
<li>[=list/For each=] [=available=] non-Bluetooth serial port:
<ol>
<li>Let |port:SerialPort| be a {{SerialPort}} representing
the port.
</li>
<li>[=list/Append=] |port| to |allPorts|.
</li>
</ol>
</li>
<li>Prompt the user to grant the site access to a serial port by
presenting them with a list of ports in |allPorts| that [=match
any filter=] in |options|["{{SerialPortRequestOptions/filters}}"]
if present and |allPorts| otherwise.
</li>
<li>If the user does not choose a port, [=queue a global task=]
on the [=relevant global object=] of [=this=] using the [=serial
port task source=] to [=reject=] |promise| with a
{{"NotFoundError"}} {{DOMException}} and abort these steps.
</li>
<li>Let |port:SerialPort| be a {{SerialPort}} representing the
port chosen by the user.
</li>
<li>[=Queue a global task=] on the [=relevant global object=] of
[=this=] using the [=serial port task source=] to [=resolve=]
|promise| with |port|.
</li>
</ol>
</li>
<li>Return |promise|.
</li>
</ol>
<p>
A serial port is <dfn>available</dfn> if it is a wired serial port and
the port is physically connected to the system, or if it is a wireless
serial port and the wireless device hosting the port is registered
with the system.
</p>
<section data-dfn-for="SerialPortRequestOptions">
<h4>
<dfn>SerialPortRequestOptions</dfn> dictionary
</h4>
<pre class="idl">
dictionary SerialPortRequestOptions {
sequence<SerialPortFilter> filters;
sequence<BluetoothServiceUUID> allowedBluetoothServiceClassIds;
};
</pre>
<dl>
<dt>
<dfn>filters</dfn> member
</dt>
<dd>
Filters for serial ports
</dd>
<dt>
<dfn>allowedBluetoothServiceClassIds</dfn> member
</dt>
<dd>
A list of {{BluetoothServiceUUID}} values representing Bluetooth
service class IDs. Bluetooth ports with custom service class IDs
are excluded from the list of ports presented to the user unless
the service class ID is included in this list.
</dd>
</dl>
</section>
<section data-dfn-for="SerialPortFilter">
<h4>
<dfn>SerialPortFilter</dfn> dictionary
</h4>
<pre class="idl">
dictionary SerialPortFilter {
unsigned short usbVendorId;
unsigned short usbProductId;
BluetoothServiceUUID bluetoothServiceClassId;
};
</pre>
<dl>
<dt>
<dfn>usbVendorId</dfn> member
</dt>
<dd>
USB Vendor ID
</dd>
<dt>
<dfn>usbProductId</dfn> member
</dt>
<dd>
USB Product ID
</dd>
<dt>
<dfn>bluetoothServiceClassId</dfn> member
</dt>
<dd>
Bluetooth service class ID
</dd>
</dl>
<p>
A serial port |port:SerialPort| <dfn>matches the filter</dfn>
|filter:SerialPortFilter| if these steps return `true`:
</p>
<ol>
<li>Let |info:SerialPortInfo| be the result of calling
|port|.{{SerialPort/getInfo()}}.
</li>
<li>If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"] is
present:
<ol>
<li>If the serial port is not part of a Bluetooth device,
return `false`.
</li>
<li>If |filter|["{{SerialPortFilter/bluetoothServiceClassId}}"]
is equal to
|info|["{{SerialPortInfo/bluetoothServiceClassId}}"], return
`true`.
</li>
<li>Otherwise, return `false`.
</li>
</ol>
</li>
<li>If |filter|["{{SerialPortFilter/usbVendorId}}"] is not present,
return `true`.
</li>
<li>If the serial port is not part of a USB device, return `false`.
</li>
<li>If |info|["{{SerialPortInfo/usbVendorId}}"] is not equal to
|filter|["{{SerialPortFilter/usbVendorId}}"], return `false`.
</li>
<li>If |filter|["{{SerialPortFilter/usbProductId}}"] is not
present, return `true`.
</li>
<li>If |info|["{{SerialPortInfo/usbProductId}}"] is not equal to
|filter|["{{SerialPortFilter/usbProductId}}"], return `false`.
</li>
<li>Otherwise, return `true`.
</li>
</ol>
<p>
A serial port |port:SerialPort| <dfn data-lt=
"match any filter">matches any filter</dfn> in a sequence of
{{SerialPortFilter}} if these steps return `true`:
</p>
<ol>
<li>For each |filter| in the sequence, run these sub-steps:
<ol>
<li>If |port| [=matches the filter=] |filter|, return `true`.
</li>
</ol>
</li>
<li>Return `false`.
</li>
</ol>
</section>
</section>
<section>
<h3>
<dfn>getPorts()</dfn> method
</h3>
<aside class="example">
<p>
If a serial port is provided by a USB device then that device may
be connected or disconnected from the system. Once a site has
permission to access a port it can receive these events and query
for the set of connected devices it currently has access to.
</p>
<pre class="js">
// Check to see what ports are available when the page loads.
document.addEventListener('DOMContentLoaded', async () => {
let ports = await navigator.serial.getPorts();
// Populate the UI with options for the user to select or
// automatically connect to devices.
});
navigator.serial.addEventListener('connect', e => {
// Add |e.target| to the UI or automatically connect.
});
navigator.serial.addEventListener('disconnect', e => {
// Remove |e.target| from the UI. If the device was open the
// disconnection can also be observed as a stream error.
});
</pre>
</aside>
<p>
The {{Serial/getPorts()}} method steps are:
</p>
<ol>
<li>Let |promise:Promise| be [=a new promise=].
</li>
<li>If [=this=]'s [=relevant global object=]'s [=associated
Document=] is not [=allowed to use=] the [=policy-controlled
feature=] named `"serial"`, [=reject=] |promise| with a
"{{SecurityError}}" {{DOMException}} and return |promise|.
</li>
<li>Run the following steps [=in parallel=]:
<ol>
<li>Let |availablePorts| be the sequence of [=available=] serial
ports which the user has allowed the site to access
as the result of a previous call to {{Serial/requestPort()}}.
</li>
<li>Let |ports| be the sequence of the {{SerialPort}}s
representing the ports in |availablePorts|.
</li>
<li>[=Queue a global task=] on the [=relevant global object=] of
[=this=] using the [=serial port task source=] to [=resolve=]
|promise| with |ports|.
</li>
</ol>
</li>
<li>Return |promise|.
</li>
</ol>
</section>
<section>
<h3>
<dfn>onconnect</dfn> attribute
</h3>{{Serial/onconnect}} is an [=event handler IDL attribute=] for the
<dfn>connect</dfn> event type.
</section>
<section>
<h3>
<dfn>ondisconnect</dfn> attribute
</h3>{{Serial/ondisconnect}} is an [=event handler IDL attribute=] for
the <dfn>disconnect</dfn> event type.
</section>
</section>
<section data-dfn-for="SerialPort">
<h2>
<dfn>SerialPort</dfn> interface
</h2>
<pre class="idl">
[Exposed=(DedicatedWorker,Window), SecureContext]
interface SerialPort : EventTarget {
attribute EventHandler onconnect;
attribute EventHandler ondisconnect;
readonly attribute boolean connected;
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
SerialPortInfo getInfo();
Promise<undefined> open(SerialOptions options);
Promise<undefined> setSignals(optional SerialOutputSignals signals = {});
Promise<SerialInputSignals> getSignals();
Promise<undefined> close();
Promise<undefined> forget();
};
</pre>
<p>
Methods on this interface typically complete asynchronously, queuing
work on the <dfn>serial port task source</dfn>.
</p>
<p>
The [=get the parent=] algorithm for {{SerialPort}} returns the same
{{Serial}} instance that is returned by the {{SerialPort}}'s [=relevant
global object=]'s {{Navigator}} object's {{Navigator/serial}} getter.
</p>
<p>
Instances of {{SerialPort}} are created with the internal slots
described in the following table:
</p>
<table class="simple" data-dfn-for="SerialPort">
<tr>
<th>
Internal slot
</th>
<th>
Initial value
</th>
<th>
Description (non-normative)
</th>
</tr>
<tr>
<td>
<dfn>[[\state]]</dfn>
</td>
<td>
`"closed"`
</td>
<td>
Tracks the active state of the {{SerialPort}}
</td>
</tr>
<tr>
<td>
<dfn>[[\bufferSize]]</dfn>
</td>
<td>
undefined
</td>
<td>
The amount of data to buffer for transmit and receive
</td>
</tr>
<tr>
<td>
<dfn>[[\connected]]</dfn>
</td>
<td>
`false`
</td>
<td>
A flag indicating the logical connection state of serial port
</td>
</tr>
<tr>
<td>
<dfn>[[\readable]]</dfn>
</td>
<td>
`null`
</td>
<td>
A {{ReadableStream}} that receives data from the port
</td>
</tr>
<tr>
<td>
<dfn>[[\readFatal]]</dfn>
</td>
<td>
`false`
</td>
<td>
A flag indicating that the port has encountered a fatal read error
</td>
</tr>
<tr>
<td>
<dfn>[[\writable]]</dfn>
</td>
<td>
`null`
</td>
<td>
A {{WritableStream}} that transmits data to the port
</td>
</tr>
<tr>
<td>
<dfn>[[\writeFatal]]</dfn>
</td>
<td>
`false`
</td>
<td>
A flag indicating that the port has encountered a fatal write error
</td>
</tr>
<tr>
<td>
<dfn>[[\pendingClosePromise]]</dfn>
</td>
<td>
`null`
</td>
<td>
A {{Promise}} used to wait for {{SerialPort/readable}} and
{{SerialPort/writable}} to close
</td>
</tr>
</table>
<section>
<h3>
<dfn>onconnect</dfn> attribute
</h3>{{SerialPort/onconnect}} is an [=event handler IDL attribute=] for
the {{connect}} event type.
<p>
When a serial port that the user has allowed the site to access as the
result of a previous call to {{Serial/requestPort()}} becomes
[=logically connected=], run the following steps:
</p>
<ol>
<li>Let |port:SerialPort| be a {{SerialPort}} representing the port.
</li>
<li>Set |port|.{{SerialPort/[[connected]]}} to `true`.
</li>
<li>[=Fire an event=] named {{connect}} at |port| with its
{{Event/bubbles}} attribute initialized to `true`.
</li>
</ol>
<p>
A serial port is <dfn>logically connected</dfn> if it is a wired
serial port and the port is physically connected to the system, or if
it is a wireless serial port and the system has active connections to
the wireless device (e.g. an open Bluetooth L2CAP channel).
</p>
</section>
<section>
<h3>
<dfn>ondisconnect</dfn> attribute
</h3>{{SerialPort/ondisconnect}} is an [=event handler IDL attribute=]
for the {{disconnect}} event type.
<p>
When a serial port that the user has allowed the site to access as the
result of a previous call to {{Serial/requestPort()}} is no longer
[=logically connected=], run the following steps:
</p>
<ol>
<li>Let |port:SerialPort| be a {{SerialPort}} representing the port.
</li>
<li>Set |port|.{{SerialPort/[[connected]]}} to `false`.
</li>
<li>[=Fire an event=] named {{disconnect}} at |port| with its
{{Event/bubbles}} attribute initialized to `true`.
</li>
</ol>
</section>
<section>
<h3>
<dfn>getInfo()</dfn> method
</h3>The {{SerialPort/getInfo()}} method steps are:
<ol>
<li>Let |info:SerialPortInfo| be a [=new=] {{SerialPortInfo}}
dictionary.
</li>
<li>If the port is part of a USB device, perform the following steps:
<ol>
<li>Set |info|["{{SerialPortInfo/usbVendorId}}"] to the vendor ID
of the device.
</li>
<li>Set |info|["{{SerialPortInfo/usbProductId}}"] to the product
ID of the device.
</li>
</ol>
</li>
<li>If the port is a service on a Bluetooth device, perform the
following steps:
<ol>
<li>Set |info|["{{SerialPortInfo/bluetoothServiceClassId}}"] to
the service class UUID of the Bluetooth service.
</li>
</ol>
</li>
<li>Return |info|.
</li>
</ol>
<section data-dfn-for="SerialPortInfo">
<h4>
<dfn>SerialPortInfo</dfn> dictionary
</h4>
<pre class="idl">
dictionary SerialPortInfo {
unsigned short usbVendorId;
unsigned short usbProductId;
BluetoothServiceUUID bluetoothServiceClassId;
};
</pre>
<dl>
<dt>
<dfn>usbVendorId</dfn> member
</dt>
<dd>
If the port is part of a USB device this member will be the
16-bit vendor ID of that device. Otherwise it will be
`undefined`.
</dd>
<dt>
<dfn>usbProductId</dfn> member
</dt>
<dd>
If the port is part of a USB device this member will be the
16-bit product ID of that device. Otherwise it will be
`undefined`.
</dd>
<dt>
<dfn>bluetoothServiceClassId</dfn> member
</dt>
<dd>
If the port is a service on a Bluetooth device this member will
be a {{BluetoothServiceUUID}} containing the service class UUID.
Otherwise it will be `undefined`.
</dd>
</dl>
</section>
</section>
<section>
<h3>
<dfn>open()</dfn> method
</h3>
<aside class="example">
<p>
Before communicating on a serial port it must be opened. Opening
the port allows the site to specify the necessary parameters which
control how data is transmitted and received. Developers should
check the documentation for the device they are connecting to for
the appropriate parameters.
</p>
<pre class="js">
await port.open({ baudRate: /* pick your baud rate */ });
</pre>
<p>
Once {{SerialPort/open()}} has resolved the {{SerialPort/readable}}
and {{SerialPort/writable}} attributes can be accessed to get the
{{ReadableStream}} and {{WritableStream}} instances for receiving
data from and sending data to the connected device.
</p>
</aside>
<p>
The {{SerialPort/open()}} method steps are:
</p>
<ol>
<li>Let |promise| be [=a new promise=].
</li>
<li>If [=this=].{{SerialPort/[[state]]}} is not `"closed"`, reject
|promise| with an "{{InvalidStateError}}" {{DOMException}} and return
|promise|.
</li>
<li>If |options|["{{SerialOptions/dataBits}}"] is not 7 or 8, reject
|promise| with {{TypeError}} and return |promise|.
</li>
<li>If |options|["{{SerialOptions/stopBits}}"] is not 1 or 2, reject
|promise| with {{TypeError}} and return |promise|.
</li>
<li>If |options|["{{SerialOptions/bufferSize}}"] is 0, reject
|promise| with {{TypeError}} and return |promise|.
</li>
<li>Optionally, if |options|["{{SerialOptions/bufferSize}}"] is
larger than the implementation is able to support, reject |promise|
with a {{TypeError}} and return |promise|.
</li>
<li>Set [=this=].{{SerialPort/[[state]]}} to `"opening"`.
</li>
<li>Perform the following steps [=in parallel=].
<ol>
<li>Invoke the operating system to open the serial port using the
connection parameters (or their defaults) specified in |options|.
</li>
<li>If this fails for any reason, [=queue a global task=] on the
[=relevant global object=] of [=this=] using the [=serial port
task source=] to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
</li>
<li>Set [=this=].{{SerialPort/[[state]]}} to `"opened"`.
</li>
<li>Set [=this=].{{SerialPort/[[bufferSize]]}} to
|options|["{{SerialOptions/bufferSize}}"].
</li>
<li>[=Queue a global task=] on the [=relevant global object=] of
[=this=] using the [=serial port task source=] to [=resolve=]
|promise| with `undefined`.
</li>
</ol>
</li>
<li>Return |promise|.
</li>
</ol>
<section data-dfn-for="SerialOptions">
<h4>
<dfn>SerialOptions</dfn> dictionary
</h4>
<pre class="idl">
dictionary SerialOptions {
[EnforceRange] required unsigned long baudRate;
[EnforceRange] octet dataBits = 8;
[EnforceRange] octet stopBits = 1;
ParityType parity = "none";
[EnforceRange] unsigned long bufferSize = 255;
FlowControlType flowControl = "none";
};
</pre>
<dl>
<dt>
<dfn>baudRate</dfn> member
</dt>
<dd>
A positive, non-zero value indicating the baud rate at which
serial communication should be established.
<div class="note">
{{SerialOptions/baudRate}} is the only required member of this
dictionary. While there are common default for other connection
parameters it is important for developers to consider and
consult with the documentation for devices they intend to
connect to determine the correct values. While some values are
common there is no standard baud rate. Requiring this parameter
reduces the potential for confusion if an arbitrary default
were chosen by this specification.
</div>
</dd>
<dt>
<dfn>dataBits</dfn> member
</dt>
<dd>
The number of data bits per frame. Either 7 or 8.
</dd>
<dt>
<dfn>stopBits</dfn> member
</dt>
<dd>
The number of stop bits at the end of a frame. Either 1 or 2.
</dd>
<dt>
<dfn>parity</dfn> member
</dt>
<dd>
The parity mode.
</dd>
<dt>
<dfn>bufferSize</dfn> member
</dt>
<dd>
A positive, non-zero value indicating the size of the read and
write buffers that should be created.
</dd>
<dt>
<dfn>flowControl</dfn> member
</dt>
<dd>
The flow control mode.
</dd>
</dl>
<section data-dfn-for="ParityType">
<h5>
<dfn>ParityType</dfn> enum
</h5>
<pre class="idl">
enum ParityType {
"none",
"even",
"odd"
};
</pre>
<dl>
<dt>
<dfn>none</dfn>
</dt>
<dd>
No parity bit is sent for each data word.
</dd>
<dt>
<dfn>even</dfn>
</dt>
<dd>
Data word plus parity bit has even parity.
</dd>
<dt>
<dfn>odd</dfn>
</dt>
<dd>
Data word plus parity bit has odd parity.
</dd>
</dl>
</section>
<section data-dfn-for="FlowControlType">
<h5>
<dfn>FlowControlType</dfn> enum
</h5>
<pre class="idl">
enum FlowControlType {
"none",
"hardware"
};
</pre>
<dl>
<dt>
<dfn>none</dfn>
</dt>
<dd>
No flow control is enabled.
</dd>
<dt>
<dfn>hardware</dfn>
</dt>
<dd>
Hardware flow control using the RTS and CTS signals is enabled.
</dd>
</dl>
</section>
</section>
</section>
<section>
<h2>
<dfn>connected</dfn> attribute
</h2>
<p>
The {{SerialPort/connected}} getter steps are:
</p>
<ol>
<li>Return [=this=].{{SerialPort/[[connected]]}}.
</li>
</ol>
</section>
<section>
<h2>
<dfn>readable</dfn> attribute
</h2>
<aside class="example" id="readable-example">
<p>
An application receiving data from a serial port will typically use
a nested pair of loops like this,
</p>
<pre class="js">
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// |reader| has been canceled.
break;
}
// Do something with |value|...
}
} catch (error) {
// Handle |error|...
} finally {
reader.releaseLock();
}
}
</pre>
<p>
The inner loop will read chunks of data from the port until an
error is encountered, at which point the code in the "catch" block
will be executed. The outer loop handles recoverable errors such as
parity check failures by opening a new reader. Fatal errors will
cause {{SerialPort/readable}} to become `null` and the loop to end.
</p>
<p>
As long as the serial port is open it can continue to produce data
and the amount of data in each of the chunks returned by
{{ReadableStreamDefaultReader/read()}} will be essentially
arbitrary based on the timing of when it is called. It is up to the
device and the code communicating with it to decide what
constitutes a complete message. For example, a device might
communicate with the host using ASCII-formatted text where each
message ends with a newline (or the sequence `"\r\n"`). A pipeline
of {{TransformStream}}s can be used to automatically convert the
{{Uint8Array}} chunks provided by {{SerialPort/readable}} into
{{DOMString}}s containing an entire line of text each.
</p>
<pre class="js">
class LineBreakTransformer {
constructor() {
this.container = '';
}
transform(chunk, controller) {
this.container += chunk;
const lines = this.container.split('\r\n');
this.container = lines.pop();
lines.forEach(line => controller.enqueue(line));
}
flush(controller) {
controller.enqueue(this.container);
}
}
const lineReader = port.readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.getReader();
</pre>
<p>
Some other ways of encoding message boundaries are to prefix each
message with its length or to wait a defined length of time before
transmitting the next message. Implementing a {{TransformStream}}
for these types of message boundaries is left as an exercise for
the reader.
</p>
<p>
While the {{ReadableStreamDefaultReader/read()}} method is
asynchronous and does not block execution, in code using
async/await syntax it can seem as if it does. In this situation it
may be helpful to implement a timeout which will allow the code to
continue execution if no data is received for a period of time. The
example below uses the
{{ReadableStreamDefaultReader/releaseLock()}} method to interrupt a
call to {{ReadableStreamDefaultReader/read()}} after a timer
expires. This will not close the stream and so any data received
after the timeout can still be read later after calling
{{ReadableStream/getReader()}} again.
</p>
<pre class="js">
async function readWithTimeout(port, timeout) {
const reader = port.readable.getReader();
const timer = setTimeout(() => {
reader.releaseLock();
}, timeout);
const result = await reader.read();
clearTimeout(timer);
reader.releaseLock();
return result;
}
</pre>
<p>
This feature of {{ReadableStreamDefaultReader/releaseLock()}} was
added in <a href=
"https://github.com/whatwg/streams/pull/1168">whatwg/streams#1168</a>
and has only recently been implemented by browsers.
</p>
</aside>
<p>
The {{SerialPort/readable}} getter steps are:
</p>
<ol>
<li>If [=this=].{{SerialPort/[[readable]]}} is not `null`, return