Serial API によって、ウェブサイトがスクリプトを介してシリアルデバイスに接続することができます。このようなAPIは、マイクロコントローラや3Dプリンタなどのシリアルデバイスとドキュメントが通信できるようにすることで、ウェブと物理的な世界の架け橋となるでしょう。
手引書 も用意されています。
{{Navigator}} インターフェースの拡張
[Exposed=Window, SecureContext]
partial interface Navigator {
[SameObject] readonly attribute Serial serial;
};
serial 属性
取得時、 {{Navigator/serial}} 属性は常に {{Serial}} オブジェクトの同じインスタンスを返します。
{{WorkerNavigator}} インターフェースの拡張
[Exposed=DedicatedWorker, SecureContext]
partial interface WorkerNavigator {
[SameObject] readonly attribute Serial serial;
};
serial 属性
取得時、 {{WorkerNavigator/serial}} 属性は常に {{Serial}} オブジェクトの同じインスタンスを返します。
{{Serial}} インターフェース
[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 = {});
};
requestPort() メソッド
ユーザーが最初にサイトにアクセスしたとき、シリアルデバイスにアクセスする権限は持っていません。
サイトは最初に {{Serial/requestPort()}} を呼び出さなくてはなりません。
この呼び出しにより、ブラウザはプロンプトを出し、サイトがどのデバイスを制御をするかの許可をユーザーに求める機会が与えられます。
サイトが常に USB 経由で接続されている特定のデバイスで動作するように設計されている場合、サイトは、ユーザーが選択できるデバイスを互換性のあるデバイスのみに制限するフィルターを提供できます。
例えば、 Arduino を搭載したロボットをプログラムするサイトでは、次のように指定して選択可能なポートのセットを Arduino の USB ベンダー ID を持つ USB デバイスのみに制限できます。
const filter = { usbVendorId: 0x2341 };
const port = await navigator.serial.requestPort({ filters: [filter] });
一方、サイトが多種多様なデバイスや USB からシリアル変換器を介して接続されたデバイスでの使用を想定している場合は、フィルタを全く指定せず、ユーザーが適切なデバイスを選択することに頼ることができます。
const port = await navigator.serial.requestPort();
ユーザーにポートを選択させるには、ユーザーにプロンプトを表示させる必要があり、サイトにはユーザーがボタンをクリックする、というような 一時的なアクティブ化([=transient activation=]) を持っていなければなりません。
<button id="connect">Connect</button>
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.
}
});
ユーザーはデバイスを選択しないことを選択できます。その場合、 {{Promise}} は "{{NotFoundError}}" {{DOMException}} でリジェクトされますので、サイトが処理する必要があります。
{{Serial/requestPort()}} メソッドの手順は次のとおりです:
|promise:Promise| を 新しい promise ([=a new promise=]) とします。
[=this=] が 関連するグローバルオブジェクト([=relevant global object=]) の 関連付けられたドキュメント([=associated Document=]) が `"serial"` という名前の ポリシー制御機能([=policy-controlled feature=]) で 使用を許可([=allowed to use=]) されない場合、 "{{SecurityError}}" {{DOMException}} で |promise| を リジェクト([=reject=]) し、 |promise| を返します。
[=this=] の 関連するグローバルオブジェクト([=relevant global object=]) が 一時的なアクティブ化([=transient activation=]) がされていない場合は、 |promise| を "{{SecurityError}}" {{DOMException}}を使用して リジェクト([=reject=]) し、 |promise| を返します。
もし |options|["{{SerialPortRequestOptions/filters}}"] が存在していれば、|options|["{{SerialPortRequestOptions/filters}}"] 内のそれぞれの |filter:SerialPortFilter| に対して次のステップを実行します :
もし |filter|["{{SerialPortFilter/usbVendorId}}"] が存在していない場合、
|promise| を {{TypeError}} で リジェクト([=reject=]) し、 |promise| を返します。
このチェックは、
{{SerialPortFilter}} は空には出来ず、
{{SerialPortFilter/usbProductId}} が指定されている場合は
{{SerialPortFilter/usbVendorId}} も指定しなくてはならない、という複合ルールを実装します。
以下の手順を 並列([=in parallel=]) に実行します:
|options|["{{SerialPortRequestOptions/filters}}"] が存在する場合、そのフィルターの どれかに一致 ([=match any filter=]) する使用可能なポートのリストを提示し、存在しない場合はすべての使用可能なポートを提示して、サイトがシリアルポートにアクセスする許可を与えるようにユーザーに促します。
ユーザーがポートを選択しなかった場合は、 [=this=] が 関連するグローバルオブジェクト ([=relevant global object=]) で、 [=シリアルポートタスクソース=] を使用して、グローバルタスクをキューに入れ ([=queue a global task=]) 、 {{"AbortError"}} {{DOMException}} で |promise| を リジェクト ([=reject=]) し、これらの手順を中止します。
|port:SerialPort| を、ユーザが選択したポートを表す {{SerialPort}} とします。
[=シリアルポートタスクソース=] を使用して、 [=this=] に 関連するグローバルオブジェクト ([=relevant global object=]) で グローバルタスクをキューに入れ ([=Queue a global task=]) 、 |port| で |promise| を リゾルブ ([=resolve=]) します。
|promise| を返します。
SerialPortRequestOptions ディクショナリー
dictionary SerialPortRequestOptions {
sequence<SerialPortFilter> filters;
};
filters メンバー
シリアルポートに対するフィルター
SerialPortFilter ディクショナリー
dictionary SerialPortFilter {
unsigned short usbVendorId;
unsigned short usbProductId;
};
usbVendorId メンバー
USB ベンダー ID
usbProductId メンバー
USB プロダクト ID
以下の手順が `true` を返す場合、シリアルポートは |filter:SerialPortFilter| と 一致 します:
もし |filter|["{{SerialPortFilter/usbVendorId}}"] が存在しない場合は、 `true` を返します。
シリアルポートが USB デバイスでない場合は、 `false` を返します。
USB デバイスのベンダー ID が |filter|["{{SerialPortFilter/usbVendorId}}"] と等しくない場合は、 `false`を返します。
もし |filter|["{{SerialPortFilter/usbProductId}}"] が存在しない場合は、 `true` を返します。
もし USB デバイスのプロダクトID が |filter|["{{SerialPortFilter/usbProductId}}"] と等しくない場合は、 `false` を返します。
それ以外の場合は `true` を返します。
以下の手順が `true` を返す場合、シリアルポートは {{SerialPortFilter}} のシーケンス内の どれかのフィルターと一致 します:
シーケンス内の |filter| ごとに、次のサブステップを実行します:
もしシリアルポートが |filter| と [=一致=] でない場合は、 false を返します。
true を返します。
getPorts() メソッド
シリアルポートが USB デバイスによって提供されている場合、そのデバイスはシステムに接続されているか、システムから切断されている可能性があります。 サイトがポートにアクセスする権限を取得すると、これらのイベントを受信して、現在アクセスできる接続デバイスのセットを照会できます。
// 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.port| to the UI or automatically connect.
});
navigator.serial.addEventListener('disconnect', e => {
// Remove |e.port| from the UI. If the device was open the
// disconnection can also be observed as a stream error.
});
{{Serial/getPorts()}} メソッドの手順は:
|promise:Promise| を 新しい promise ([=a new promise=]) とします。
[=this=] が 関連するグローバルオブジェクト([=relevant global object=]) の 関連付けられたドキュメント([=associated Document=]) が `"serial"` という名前の ポリシー制御機能([=policy-controlled feature=]) で 使用を許可([=allowed to use=]) されない場合、 "{{SecurityError}}" {{DOMException}} で |promise| を リジェクト([=reject=]) し、 |promise| を返します。
以下の手順を 並列 ([=in parallel=]) に実行します:
|availablePorts| を {{Serial/requestPort()}} を呼び出した結果である、ユーザーがサイトにアクセスを許可したシステム上の利用可能なシリアルポートのシーケンスとします。
|ports| を、 |availablePorts| のポートを表す {{SerialPort}} のシーケンスとします。
[=シリアルポートタスクソース=] を使用して、 [=this=] に 関連するグローバルオブジェクト ([=relevant global object=]) で グローバルタスクをキューに入れ ([=Queue a global task=])、 |ports| を持って |promise| を リゾルブ ([=resolve=]) します。
|promise| を返す。
onconnect 属性
{{Serial/onconnect}} は、connect イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。
ondisconnect 属性
{{Serial/ondisconnect}} は、disconnect イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。
SerialPort インターフェース
[Exposed=(DedicatedWorker,Window), SecureContext]
interface SerialPort : EventTarget {
attribute EventHandler onconnect;
attribute EventHandler ondisconnect;
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();
};
このインターフェイスのメソッドは通常、非同期で完了し、 シリアルポートタスクソース 上で作業をキューに入れます。
{{SerialPort}} の [=get the parent=] アルゴリズムは、 {{SerialPort}} の 関連するグローバルオブジェクト ([=relevant global object=]) の {{Navigator}} オブジェクトの {{Navigator/serial}} 取得によって返されるのと同じ {{Serial}} インスタンスを返します。
{{SerialPort}} のインスタンスは、次の表で説明されている内部スロットと共に作成されます。
内部スロット
初期値
説明 (非基準)
[[\state]]
`"closed"`
{{SerialPort}} のアクティブ状態を追跡します
[[\bufferSize]]
undefined
送信および受信用にバッファリングするデータの量
[[\readable]]
`null`
ポートからデータを受信する {{ReadableStream}}
[[\readFatal]]
`false`
ポートで致命的な読み取りエラーが発生したことを示すフラグ
[[\writable]]
`null`
ポートにデータを送信する {{WritableStream}}
[[\writeFatal]]
`false`
ポートで致命的な書き込みエラーが発生したことを示すフラグ
[[\pendingClosePromise]]
`null`
{{SerialPort/readable}} と {{SerialPort/writable}} が close されるのを待つために使用される {{Promise}}
onconnect 属性
{{SerialPort/onconnect}} は、 {{connect}} イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。
ユーザーが以前に {{Serial/requestPort()}} を呼び出して、サイトがアクセスする事を許可したシステム上のシリアルポートが使用可能になったら、次の手順を実行します:
|port:SerialPort| を、ポートを表す {{SerialPort}} とします。
{{Event/bubbles}} 属性を `true` に初期化して、 |port| で {{connect}} という名前の イベントを発行 ([=Fire an event=]) します。
ondisconnect 属性
{{SerialPort/ondisconnect}} は、 {{disconnect}} イベントの イベントハンドラ IDL 属性 ([=event handler IDL attribute=]) です。
ユーザーが以前に {{Serial/requestPort()}} を呼び出して、サイトがアクセスする事を許可したシステム上のシリアルポートが使用不能になったら、次の手順を実行します:
|port:SerialPort| を、ポートを表す {{SerialPort}} とします。
{{Event/bubbles}} 属性を `true` に初期化して、 |port| で {{disconnect}} という名前の イベントを発行 ([=Fire an event=]) します。
getInfo() メソッド
{{SerialPort/getInfo()}} メソッドの手順は次のとおりです:
|info:SerialPortInfo| を 新しい([=new=]) {{SerialPortInfo}} ディクショナリーとします。
もしそのポートが USB デバイスならば、次の手順を実行します:
|info|["{{SerialPortInfo/usbVendorId}}"] をそのデバイスのベンダー ID に設定します。
|info|["{{SerialPortInfo/usbProductId}}"] をそのデバイスのプロダクト ID に設定します。
|info| を返します。
SerialPortInfo ディクショナリー
dictionary SerialPortInfo {
unsigned short usbVendorId;
unsigned short usbProductId;
};
usbVendorId メンバー
ポートが USB デバイスの一部である場合、このメンバーはそのデバイスの 16 ビットのベンダー ID になります。 それ以外の場合は `undefined` になります。
usbProductId メンバー
ポートが USB デバイスの一部である場合、このメンバーはそのデバイスの 16 ビットのプロダクト ID になります。 それ以外の場合は `undefined` になります。
open() メソッド
{{SerialPort/open()}} メソッドの手順は次のとおりです:
|promise| を 新しい promise ([=a new promise=]) とします。
もし [=this=].{{[[state]]}} が "closed" ではない場合、 "{{InvalidStateError}}" {{DOMException}} で |promise| をリジェクトし、 |promise| を返します。
もし |options|["{{SerialOptions/dataBits}}"] が 7 または 8 ではない場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
もし |options|["{{SerialOptions/stopBits}}"] が 1 または 2 ではない場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
もし |options|["{{SerialOptions/bufferSize}}"] が 0 の場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
更に、もし |options|["{{SerialOptions/bufferSize}}"] が実装がサポートする範囲よりも大きい場合、 {{TypeError}} で |promise| をリジェクトし、 |promise| を返します。
[=this=].{{[[state]]}} を `"opening"` に設定します。
以下の手順を 並列 ([=in parallel=]) に実行します。
|options| で指定された接続パラメータ ( またはそれらのデフォルト値 ) を使用して、オペレーティングシステムを呼び出し、シリアルポートを開きます。
これが何らかの理由で失敗した場合は、 [=シリアルポートタスクソース=] を使用して、 [=this=] が関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=queue a global task=]) 、"{{NetworkError}} {{DOMException}}" で |promise| を リジェクト ([=reject=]) して、これらの手順を中止します。
[=this=].{{[[state]]}} を `"opened"` に設定します。
[=this=].{{[[bufferSize]]}} を |options|["{{SerialOptions/bufferSize}}"] に設定します。
|promise| を `undefined` でリゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れます ([=Queue a global task=])。
|promise| を返します。
SerialOptions ディクショナリー
dictionary SerialOptions {
required [EnforceRange] unsigned long baudRate;
[EnforceRange] octet dataBits = 8;
[EnforceRange] octet stopBits = 1;
ParityType parity = "none";
[EnforceRange] unsigned long bufferSize = 255;
FlowControlType flowControl = "none";
};
baudRate メンバー
シリアル通信を確立するボーレートを示す 0 ではない正の値。
{{SerialOptions/baudRate}} は、このディクショナリーでの唯一の必須メンバーです。 他の接続パラメータには一般的なデフォルト値がありますが、開発者は、接続しようとするデバイスのドキュメントを検討および参照して、正しい値を決定することが重要です。 幾つかの一般的な値はありますが、ボーレートには標準となる値がありません。 この仕様で何らかのデフォルト値を決めてしまうよりもこのパラメータを要求するようにする事で混乱の可能性を下げる事ができます。
dataBits メンバー
フレームあたりのデータビット数です。 7 または 8 のいずれかになります。
stopBits メンバー
フレームの終わりのストップビットの数です。 1 または 2 のいずれかになります。
parity メンバー
パリティモードです。
bufferSize メンバー
作成する読み取りおよび書き込みバッファーのサイズを示す 0 ではない正の値です。
flowControl メンバー
フロー制御のモードです。
ParityType 列挙値
enum ParityType {
"none",
"even",
"odd"
};
none
データワードごとにパリティビットは送信されません。
even
データワードとパリティビットの和は偶数パリティになります。
odd
データワードとパリティビットの和は奇数パリティになります。
FlowControlType 列挙値
enum FlowControlType {
"none",
"hardware"
};
none
フロー制御は使用しません。
hardware
RTS と CTS 信号によるハードウェアフロー制御を使用します。
readable 属性
シリアルポートからデータを受信するアプリケーションは、通常、次のような入れ子になったループのペアを使用します:
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();
}
}
内側のループは、エラーが発生するまでポートからデータのブロックを読み取ります。エラーが発生すると、"catch" ブロックのコードが実行されます。 外側のループは、新しいリーダーを開くことにより、パリティチェックの失敗などの回復可能なエラーを処理します。 致命的なエラーが発生すると、 {{SerialPort/readable}} が `null` になり、ループが終了します。
シリアルポートが開いている限り、データを生成し続けることができ、 {{ReadableStreamDefaultReader/read()}} によって返される各ブロックのデータの量は、基本的に呼び出されたタイミングによります。 それが完全なメッセージを構成するかどうかは、デバイスと通信するコード次第です。
たとえば、デバイスは各メッセージが改行 ( またはシーケンス `"\r\n"` ) で終わるASCII形式のテキストを使用してホストと通信する場合があります。 {{TransformStream}} のパイプラインを使用して、 {{SerialPort/readable}} から供給される {{Uint8Array}} のブロックを、それぞれテキストの行全体を含む {{DOMString}} に自動的に変換できます。
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();
メッセージの境界をエンコードする他の方法としては、各メッセージにその長さを表すプレフィックスを付けたり、次のメッセージを送信する前に定義された長さの時間を待ったりする方法があります。これらのタイプのメッセージの境界のために {{TransformStream}} を実装することは、読者のための演習とします。
{{SerialPort/readable}} を取得する際の手順は次のとおりです:
もし [=this=].{{[[readable]]}} が `null` でない場合は [=this=].{{[[readable]]}} を返します。
もし [=this=].{{[[state]]}} が `"opened"` でない場合は `null` を返します。
もし [=this=].{{[[readFatal]]}} が `true` の場合は `null` を返します。
|stream| を 新しい ([=new=]) {{ReadableStream}} とします。
|pullAlgorithm| を次の手順とします:
|desiredSize| を [=this=].{{[[readable]]}} の 内部キュー の desired size とします。
次の手順を 並列 ([=in parallel=]) に実行します:
オペレーティングシステムを呼び出して、ポートから最大で |desiredSize| バイトまで読み込み、その結果を バイト列 ([=byte sequence=]) |bytes| に格納します。
[=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れて ([=Queue a global task=])、次のステップを実行します:
エラーが発生しなかった場合は、以下の手順を実行します:
|buffer| を |bytes| から作成された 新しい ([=new=]) {{ArrayBuffer}} とします。
|chunk| を |buffer| に対する、長さが |bytes| の 新しい ([=new=]) {{Uint8Array}} のビューとします。
[=this=].{{[[readable]]}} に対して |chunk| を持って エンキュー ([=ReadableStream/enqueue=]) を呼び出します。
もしバッファオーバーラン状態になった場合は、 [=this=].{{[[readable]]}} に対して "BufferOverrunError
" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
もしブレーク状態になった場合は、 [=this=].{{[[readable]]}} に対して "BreakError
" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
もしフレーミングエラー状態になった場合は、 [=this=].{{[[readable]]}} に対して "FramingError
" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
もしパリティエラー状態になった場合は、 [=this=].{{[[readable]]}} に対して "ParityError
" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
もしオペレーティングシステムのエラーが発生した場合は、 [=this=].{{[[readable]]}} に対して "{{UnknownError}}" {{DOMException}} を持って エラー ([=ReadableStream/error=]) を呼び出し、 [=readable ストリームのクローズ=] の手順を呼び出します。
もしポートが切断された場合、次の手順を実行します:
[=this=].{{[[readFatal]]}} を `true` に設定します。
"{{NetworkError}}" {{DOMException}} を持って [=this=].{{[[readable]]}} 上で [=ReadableStream/error=] を呼び出します。
[=readable ストリームのクローズ=] の手順を呼び出します。
`undefined` を持って promise をリゾルブ ([=a promise resolved with=]) して返します。
このアルゴリズムによって返される {{Promise}} は、ストリームのキャンセルをブロックしないように即座にリゾルブされます。 [[STREAMS]] は、チャンクがエンキューされるまでこのアルゴリズムが再び呼び出されないことを指定します。
|cancelAlgorithm| を次の手順とします:
|promise| を 新しい promise ([=a new promise=]) とします。
次の手順を 並列 ([=in parallel=]) に実行します。
オペレーティングシステムを呼び出して、そのポートのすべてのソフトウェアおよびハードウェアの受信バッファの内容を破棄します。
[=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れて ([=Queue a global task=]) 次のステップを実行します。
[=readable ストリームのクローズ=] 処理を行うためのステップを呼び出します。
`undefined` を持って |promise| を リゾルブ ([=Resolve=]) します。
|promise| を返します。
pullAlgorithm に |pullAlgorithm| 、 cancelAlgorithm に |cancelAlgorithm| 、 highWaterMark に [=this=].{{[[bufferSize]]}} 、 sizeAlgorithm に バイトカウントサイズアルゴリズム を設定して |stream| をセットアップ ([=ReadableStream/Set up=])します。
[=this=].{{[[readable]]}} を |stream| に設定します。
|stream| を返します。
readable ストリームのクローズ 処理を行うには、以下の手順を実行します。
steps:
[=this=].{{[[readable]]}} を `null` に設定します。
もし [=this=].{{[[writable]]}} が `null` で、 [=this=].{{[[pendingClosePromise]]}} が `null` でない場合、 [=this=].{{[[pendingClosePromise]]}} を `undefined` でリゾルブ ([=resolve=]) します。
writable 属性
データのブロックをそれぞれポートに書き込むため、必要に応じて {{WritableStreamDefaultWriter}} の作成や解放ができます。 この例では、 {{DOMString}} を送信に必要な {{Uint8Array}} にエンコードするために `TextEncoder` を使用しています。
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
await writer.write(encoder.encode("PING"));
writer.releaseLock();
より大きなブロックを書き込む場合、アプリケーションによって生成されるデータに対してシリアル送信が遅れ過ぎないように、ポートにバックプレッシャーを摘要することが重要になることがあります。
{{WritableStreamDefaultWriter/write()}} メソッドは、データが書き込まれたときに解決する {{Promise}} を返します。送信バッファに利用可能なデータをいくつか持っていることは、良好なスループットを維持するために重要ですが、データのブロックを過剰に生成する前にこの {{Promise}} を待って、過度のバッファリングを避ける事をお勧めします。
{{SerialPort/writable}} を取得する際の手順は次のとおりです:
もし [=this=].{{[[writable]]}} が `null` でない場合は [=this=].{{[[writable]]}} を返します。
もし [=this=].{{[[state]]}} が `"opened"` でない場合は `null` を返します。
もし [=this=].{{[[writeFatal]]}} が `true` であれば `null` を返します。
|stream:WritableStream| を 新しい ([=new=]) {{WritableStream}} とします。
与えられた |chunk| に対して |writeAlgorithm| は次の手順とします:
|promise:Promise| を 新しい promise ([=a new promise=]) とします。
もし |chunk| が {{BufferSource}} 型の IDL 値に変換 ([=converted to an IDL value=]) できなければ、 |promise| を {{TypeError}} でリジェクトして返します。 そうでなければ、変換の結果を |source:BufferSource| に保存します。
|source| のコピーを取得 ([=Get a copy of the buffer source=]) し、|bytes| に保存します。
並列 ([=In parallel=]) に、次の手順を実行します:
ポートに |bytes| を書き込むためにオペレーティングシステムを呼び出します。あるいは、後で結合するために chunk を保存します。
オペレーティングシステムは |bytes| が送信された後ではなく、送信のためにキューに入れられた時点でこの操作から復帰することができます。
[=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) て、次の手順を実行します:
chunk が正常に書き込まれたか、今後の結合のために保存された場合、 |promise| を `undefined` で リゾルブ ([=resolve=]) します。
[[STREAMS]] は、このアルゴリズムの以前の呼び出しによって返された {{Promise}} がリゾルブされた後にのみ |writeAlgorithm| が呼び出されることを指定します。実装は効率化のために、この {{Promise}} を早期にリゾルブし、 {{WritableStream}} の内部キューで待機している複数のチャンクをオペレーティングシステムへの単一のリクエストとして結合しても構いません。
オペレーティングシステムのエラーが発生した場合は "{{UnknownError}}" {{DOMException}} で |promise| をリジェクト ([=reject=]) します。
もしポートが切断された場合、次の手順を実行します:
Set [=this=].{{[[writeFatal]]}} to `true`.
|promise| を "{{NetworkError}}" {{DOMException}} でリジェクト ([=Reject=]) します。
[=writable ストリームのクローズ=] を呼び出します。
|promise| を返します。
|abortAlgorithm| を次の手順とします:
|promise| を 新しい promise ([=a new promise=]) とします。
次の手順を 並列 ([=in parallel=]) に実行します。
オペレーティングシステムを呼び出して、ポートのすべてのソフトウェアおよびハードウェアの送信バッファの内容を破棄します。
[=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=])、 [=シリアルポートタスクソース=]を使用して、以下のステップを実行します:
[=writable ストリームのクローズ=] の手順を呼び出します。
|promise| を `undefined` で リゾルブ ([=Resolve=]) します。
|promise| を返します。
[[STREAMS]] は、以前の |writeAlgorithm| の呼び出しによって返された {{Promise}} ( もしあれば ) がリゾルブした後にのみ |abortAlgorithm| が呼び出されることを指定します。これは、直近の書き込み操作の完了時までアボートをブロックします。これは |WriteAlgorithm| に {{AbortSignal}} を渡すことで修正できます。
この拡張については whatwg/streams#1015 で追跡されています。
|closeAlgorithm| を次の手順とします:
|promise| を 新しい promise ([=a new promise=])とします。
次の手順を 並列 ([=in parallel=]) に実行します。
オペレーティングシステムを呼び出して、ポートのすべてのソフトウェアとハードウェアの送信バッファの内容をフラッシュします。
[=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) 、以下の手順を実行します:
[=writable ストリームのクローズ=] の手順を呼び出します。
`undefined` で |promise| を リゾルブ ([=Resolve=])します。
|promise| を返します。
writeAlgorithm に |writeAlgorithm| 、 abortAlgorithm に |abortAlgorithm| 、closeAlgorithm に |closeAlgorithm| 、 highWaterMark に [=this=].{{[[bufferSize]]}} 、 sizeAlgorithm にバイトカウントサイズアルゴリズムを設定して |stream| をセットアップ ([=WritableStream/Set up=])します。
[=this=].{{[[writable]]}} を |stream| にします。
|stream| を返します。
writable ストリームのクローズ 処理は、以下の手順を実行します:
[=this=].{{[[writable]]}} を `null` に設定します。
もし [=this=].{{[[readable]]}} が `null` で、 [=this=].{{[[pendingClosePromise]]}} が `null` でない場合、 [=this=].{{[[pendingClosePromise]]}} を `undefined` で リゾルブ ([=resolve=]) します。
setSignals() メソッド
シリアルポートには、デバイスの検出とフロー制御のための多くの追加の信号が含まれており、これらは明示的に問い合わせたり設定することができます。例えば、幾つかのマイクロコントローラをプログラミングするには、まず、"Data Terminal Ready" ( または DTR ) 信号を反転することにより、 "プログラミング" モードに入る必要があります。
await port.setSignals({ dataTerminalReady: false });
await new Promise(resolve => setTimeout(resolve, 200));
await port.setSignals({ dataTerminalReady: true });
{{SerialPort/setSignals()}} メソッドの手順は以下の通りです:
|promise| を 新しい promise ([=a new promise=]) とします。
もし [=this=].{{[[state]]}} が `"opened"` でなければ |promise| は "{{InvalidStateError}}" {{DOMException}} でリジェクトし |promise| を返します。
もし |signals| の指定されたメンバーが存在していない場合は |promise| を {{TypeError}} でリジェクトし、 |promise| を返します。
以下の手続きを 並列 ([=in parallel=]) に実行します:
もし |signals|["{{SerialOutputSignals/dataTerminalReady}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポート上の "data terminal ready" または "DTR" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
もし |signals|["{{SerialOutputSignals/requestToSend}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポート上の "request to send" または "RTS" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
もし |signals|["{{SerialOutputSignals/break}}"] が存在する場合、オペレーティングシステムを呼び出して、シリアルポートの "break" 信号をアサート ( `true` の場合 ) またはデアサート ( `false` の場合 ) します。
"break" 信号は、典型的には送信ラインを "マーク" 電圧で保持することにより、インバンド信号として実装されますので、それがアサートされたままである間、データ伝送はできません。
オペレーティングシステムが何らかの理由でこれらの信号の状態変更に失敗した場合、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で [=シリアルポートタスクソース=] を使用して グローバルタスクをキューに入れ ([=queue a global task=]) 、"{{NetworkError}}" {{DOMException}} で |promise| をリジェクトします。
|promise| を `undefined` で リゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れます ([=Queue a global task=])。
|promise| を返します。
SerialOutputSignals ディクショナリー
dictionary SerialOutputSignals {
boolean dataTerminalReady;
boolean requestToSend;
boolean break;
};
dataTerminalReady
Data Terminal Ready (DTR)
requestToSend
Request To Send (RTS)
break
Break
getSignals() メソッド
{{SerialPort/getSignals()}} メソッドの手順は次の通りです:
|promise:Promise| を 新しい promise ([=a new promise=]) とします。
もし [=this=].{{[[state]]}} が `"opened"` でない場合 |promise| を "{{InvalidStateError}}" {{DOMException}} でリジェクトし、 |promise| を返します。
以下の手順を 並列 ([=in parallel=]) に実行します:
シリアルポートに接続されたデバイスがアサートする可能性のある制御信号の状態をオペレーティングシステムに問い合わせます。
もしオペレーティングシステムが何らかの理由でこれらの信号の状態を判断できない場合は、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で [=シリアルポートタスクソース=] を使用して グローバルタスクをキューに入れ ([=queue a global task=])、 |promise| を "{{NetworkError}}" {{DOMException}} でリジェクトして、これらの手順を中止します。
|signals:SerialInputSignals| を 新しい ([=new=]) {{SerialInputSignals}} とします。
もし "data carrier detect" または ""CTS" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/dataCarrierDetect}}"] を `true` に設定し、そうでなければ `false` に設定します。
もし "clear to send" または "CTS" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/clearToSend}}"] を `true` に設定し、そうでなければ `false` に設定します。
もし "ring indicator" または "RI" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/ringIndicator}}"] を `true` に設定し、そうでなければ `false` に設定します。
もし "data set ready" または "DSR" 信号がデバイスによってアサートされていれば |signals|["{{SerialInputSignals/dataSetReady}}"] を `true` に設定し、そうでなければ `false` に設定します。
|signals| で |promise| を リゾルブ ([=resolve=]) するために、 [=シリアルポートタスクソース=] を使用して、 [=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上の グローバルタスクをキューに入れます ([=Queue a global task=]) 。
|promise| を返します。
SerialInputSignals ディクショナリー
dictionary SerialInputSignals {
required boolean dataCarrierDetect;
required boolean clearToSend;
required boolean ringIndicator;
required boolean dataSetReady;
};
dataCarrierDetect メンバー
Data Carrier Detect (DCD)
clearToSend メンバー
Clear To Send (CTS)
ringIndicator メンバー
Ring Indicator (RI)
dataSetReady メンバー
Data Set Ready (DSR)
close() メソッド
ポートとの通信が不要になった場合は、ポートを閉じて関連するリソースをシステムから解放することができます。
`port.`{{SerialPort/close()}} を呼び出すと、バッファリングされたデータをクリアするために、暗黙のうちに `port.`{{SerialPort/readable}}`.`{{ReadableStream/cancel()}} と `port.`{{SerialPort/writable}}`.`{{WritableStream/abort()}} が呼び出されます。アプリケーションが `port.`{{SerialPort/readable}}`.`{{ReadableStream/getReader()}} や `port.`{{SerialPort/writable}}`.`{{WritableStream/getWriter()}} を呼び出した場合、ストリームはロックされ、ポートを閉じることができなくなります。このため、開発者は進行中の読み書き操作をどのように処理するかを決定しなければなりません。
例えば、ポートが閉じられる前にバッファリングされたデータがすべて送信されたことを確認するには、アプリケーションは `writer.`{{WritableStreamDefaultWriter/close()}} が返す {{Promise}} を待たなければなりません。
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
writer.write(encoder.encode("A long message that will take..."));
await writer.close();
await port.close();
未送信のデータを破棄するには、アプリケーションは代わりに `writer.`{{WritableStreamDefaultWriter/abort()}} を呼び出すことができます。
`port`.{{SerialPort/writable}} に {{TransformStream}} がパイプで接続されている場合、`writer.`{{WritableStreamDefaultWriter/close()}} が返す {{Promise}} がリゾルブされるのを待つだけでは不十分です。アプリケーションは、代わりに {{ReadableStream/pipeTo()}} が返す {{Promise}} がリゾルブされるのを待って、パイプチェーンがクローズされるのを待たなければなりません。
const encoder = new TextEncoderStream();
const writableStreamClosed = encoder.readable.pipeTo(port.writable);
const writer = encoder.writable.getWriter();
writer.write("A long message that will take...");
writer.close();
await writableStreamClosed;
await port.close();
[[[#readable-example]]] で行ったように、ポートからチャンクを読み込むためにループを使用している場合は、 `port.`{{SerialPort/close()}} を呼び出す前にループを終了しなければなりません。
let keepReading = true;
let reader;
async function readUntilClosed() {
while (port.readable && keepReading) {
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();
}
}
await port.close();
}
const closed = readUntilClosed();
// Sometime later...
keepReading = false;
reader.cancel();
await closed;
`reader.`{{ReadableStreamGenericReader/cancel()}} を呼び出すと `reader.`{{ReadableStreamDefaultReader/read()}} への呼び出しがすぐに戻り、内側のループを終了して `reader.`{{ReadableStreamDefaultReader/releaseLock()}} を呼び出します。 そしてストリームのロックが解除され、 `keepReading` が `false` に設定されているため、外側のループが終了し、 `port.`{{SerialPort/close()}} が正常に完了します。
`reader.`{{ReadableStreamGenericReader/cancel()}} が返す {{Promise}} を待った直後に `port.`{{SerialPort/close()}} を呼び出すことも可能ですが、 致命的なエラーが発生して `port.`{{SerialPort/readable}} が `null` になったときにポートが閉じられるように、 `readUntilClosed()` の最後のステップとして `port.`{{SerialPort/close()}} を呼び出すようにする方がより良いです。
{{SerialPort/close()}} メソッドの手順は次の通りです:
|promise| を 新しい promise ([=a new promise=]) とします。
|cancelPromise:Promise| を [=this=].{{[[readable]]}} に対して [=ReadableStream/cancel=] を呼び出した結果、または [=this=].{{[[readable]]}} が `null` であれば、 `undefined` で リゾルブされた promise ([=a promise resolved with=]) であるとします。
|abortPromise:Promise| を [=this=].{{[[writable]]}} に対して [=WritableStream/abort=] を呼び出した結果、または [=this=].{{[[writable]]}} が `null` の場合は `undefined` でリゾルブされた promise ([=a promise
resolved with=]) であるとします。
|pendingClosePromise| を 新しい promise ([=a new promise=]) とします。
もし [=this=].{{[[readable]]}} と [=this=].{{[[writable]]}} が `null` であれば [=resolve=] |pendingClosePromise| を `undefined` で リゾルブ ([=resolve=]) します。
[=this=].{{[[pendingClosePromise]]}} を |pendingClosePromise| とします。
«|cancelPromise|, |abortPromise|, |pendingClosePromise|» の 全てを待つ promise を取得 ([=getting a promise to wait for all=]) した結果を |combinedPromise| とします。
[=this=].{{[[state]]}} を `"closing"` に設定します。
|combinedPromise| に反応 ([=promise/React=]) します。
|combinedPromise| が満たされていた場合:
以下の手続きを 並列 ([=in parallel=]) に実行します:
オペレーティング システムを呼び出してシリアル ポートを閉じ、関連するリソースを解放します。
[=this=].{{[[state]]}} を `"closed"` に設定します。
[=this=].{{[[readFatal]]}} と [=this=].{{[[writeFatal]]}} を `false` に設定します。
[=this=].{{[[pendingClosePromise]]}} を `null` に設定します。
[=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) て、 [=シリアルポートタスクソース=] を使って |promise| を `undefined` でリゾルブ ([=resolve=]) します。
もし |combinedPromise| が理由 |r| でリジェクトされた場合:
[=this=].{{[[pendingClosePromise]]}} を `null` に設定します。
[=this=] の 関連するグローバルオブジェクト ([=relevant global object=]) 上で グローバルタスクをキューに入れ ([=Queue a global task=]) 、[=シリアルポートタスクソース=] を使って |r| で |promise| を リジェクト ([=reject=]) します。
|promise| を返します。
謝辞
The following people contributed to the development of this document.