@gurezo/web-serial-rxjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.ja.md +599 -0
- package/README.md +598 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 gurezo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# web-serial-rxjs
|
|
2
|
+
|
|
3
|
+
Web Serial API を RxJS ベースのリアクティブなラッパーで提供する TypeScript ライブラリです。Web アプリケーションでシリアルポート通信を簡単に実現できます。
|
|
4
|
+
|
|
5
|
+
## 目次
|
|
6
|
+
|
|
7
|
+
- [機能](#機能)
|
|
8
|
+
- [ブラウザサポート](#ブラウザサポート)
|
|
9
|
+
- [インストール](#インストール)
|
|
10
|
+
- [クイックスタート](#クイックスタート)
|
|
11
|
+
- [使用例](#使用例)
|
|
12
|
+
- [API リファレンス](#api-リファレンス)
|
|
13
|
+
- [フレームワーク別の例](#フレームワーク別の例)
|
|
14
|
+
- [高度な使用方法](#高度な使用方法)
|
|
15
|
+
- [貢献](#貢献)
|
|
16
|
+
- [ライセンス](#ライセンス)
|
|
17
|
+
- [リンク](#リンク)
|
|
18
|
+
|
|
19
|
+
## 機能
|
|
20
|
+
|
|
21
|
+
- **RxJS ベースのリアクティブ API**: RxJS Observables を活用したリアクティブなシリアルポート通信
|
|
22
|
+
- **TypeScript サポート**: 完全な TypeScript 型定義を含む
|
|
23
|
+
- **ブラウザ検出**: ブラウザサポートの検出とエラーハンドリング機能を内蔵
|
|
24
|
+
- **エラーハンドリング**: カスタムエラークラスとエラーコードによる包括的なエラーハンドリング
|
|
25
|
+
- **フレームワーク非依存**: 任意の JavaScript/TypeScript フレームワークまたはバニラ JavaScript で使用可能
|
|
26
|
+
|
|
27
|
+
## ブラウザサポート
|
|
28
|
+
|
|
29
|
+
Web Serial API は現在、Chromium ベースのブラウザでのみサポートされています:
|
|
30
|
+
|
|
31
|
+
- **Chrome** 89+
|
|
32
|
+
- **Edge** 89+
|
|
33
|
+
- **Opera** 75+
|
|
34
|
+
|
|
35
|
+
このライブラリには、使用前に Web Serial API のサポートを確認するためのブラウザ検出ユーティリティが含まれています。
|
|
36
|
+
|
|
37
|
+
## インストール
|
|
38
|
+
|
|
39
|
+
npm または pnpm を使用してパッケージをインストールします:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @gurezo/web-serial-rxjs
|
|
43
|
+
# または
|
|
44
|
+
pnpm add @gurezo/web-serial-rxjs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### ピア依存関係
|
|
48
|
+
|
|
49
|
+
このライブラリは RxJS をピア依存関係として必要とします:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install rxjs
|
|
53
|
+
# または
|
|
54
|
+
pnpm add rxjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**最小要件バージョン**: RxJS ^7.8.0
|
|
58
|
+
|
|
59
|
+
## クイックスタート
|
|
60
|
+
|
|
61
|
+
簡単な使用例:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
createSerialClient,
|
|
66
|
+
isBrowserSupported,
|
|
67
|
+
} from '@gurezo/web-serial-rxjs';
|
|
68
|
+
|
|
69
|
+
// ブラウザサポートをチェック
|
|
70
|
+
if (!isBrowserSupported()) {
|
|
71
|
+
console.error('このブラウザは Web Serial API をサポートしていません');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// シリアルクライアントを作成
|
|
76
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
77
|
+
|
|
78
|
+
// シリアルポートに接続
|
|
79
|
+
client.connect().subscribe({
|
|
80
|
+
next: () => {
|
|
81
|
+
console.log('シリアルポートに接続しました');
|
|
82
|
+
|
|
83
|
+
// シリアルポートからデータを読み取る
|
|
84
|
+
client.getReadStream().subscribe({
|
|
85
|
+
next: (data: Uint8Array) => {
|
|
86
|
+
const decoder = new TextDecoder('utf-8');
|
|
87
|
+
const text = decoder.decode(data);
|
|
88
|
+
console.log('受信:', text);
|
|
89
|
+
},
|
|
90
|
+
error: (error) => {
|
|
91
|
+
console.error('読み取りエラー:', error);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// シリアルポートにデータを書き込む
|
|
96
|
+
const encoder = new TextEncoder();
|
|
97
|
+
const data = encoder.encode('Hello, Serial Port!\n');
|
|
98
|
+
client.write(data).subscribe({
|
|
99
|
+
next: () => console.log('データを書き込みました'),
|
|
100
|
+
error: (error) => console.error('書き込みエラー:', error),
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
error: (error) => {
|
|
104
|
+
console.error('接続エラー:', error);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 使用例
|
|
110
|
+
|
|
111
|
+
### 基本的な接続
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
115
|
+
|
|
116
|
+
const client = createSerialClient({
|
|
117
|
+
baudRate: 115200,
|
|
118
|
+
dataBits: 8,
|
|
119
|
+
stopBits: 1,
|
|
120
|
+
parity: 'none',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 接続(ユーザーにポート選択を促す)
|
|
124
|
+
client.connect().subscribe({
|
|
125
|
+
next: () => console.log('接続しました'),
|
|
126
|
+
error: (error) => console.error('接続に失敗しました:', error),
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### データの読み取り
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
134
|
+
import { map } from 'rxjs/operators';
|
|
135
|
+
|
|
136
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
137
|
+
|
|
138
|
+
client.connect().subscribe({
|
|
139
|
+
next: () => {
|
|
140
|
+
// データを読み取ってデコード
|
|
141
|
+
client
|
|
142
|
+
.getReadStream()
|
|
143
|
+
.pipe(
|
|
144
|
+
map((data: Uint8Array) => {
|
|
145
|
+
const decoder = new TextDecoder('utf-8');
|
|
146
|
+
return decoder.decode(data);
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
.subscribe({
|
|
150
|
+
next: (text) => console.log('受信:', text),
|
|
151
|
+
error: (error) => console.error('読み取りエラー:', error),
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### データの書き込み
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
161
|
+
import { from } from 'rxjs';
|
|
162
|
+
import { map } from 'rxjs/operators';
|
|
163
|
+
|
|
164
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
165
|
+
|
|
166
|
+
client.connect().subscribe({
|
|
167
|
+
next: () => {
|
|
168
|
+
// 単一のチャンクを書き込む
|
|
169
|
+
const encoder = new TextEncoder();
|
|
170
|
+
const data = encoder.encode('Hello\n');
|
|
171
|
+
client.write(data).subscribe({
|
|
172
|
+
next: () => console.log('書き込みました'),
|
|
173
|
+
error: (error) => console.error('書き込みエラー:', error),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Observable ストリームから書き込む
|
|
177
|
+
const messages = ['メッセージ 1\n', 'メッセージ 2\n', 'メッセージ 3\n'];
|
|
178
|
+
const dataStream$ = from(messages).pipe(
|
|
179
|
+
map((msg) => new TextEncoder().encode(msg)),
|
|
180
|
+
);
|
|
181
|
+
client.writeStream(dataStream$).subscribe({
|
|
182
|
+
next: () => console.log('すべてのメッセージを書き込みました'),
|
|
183
|
+
error: (error) => console.error('ストリーム書き込みエラー:', error),
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### エラーハンドリング
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import {
|
|
193
|
+
createSerialClient,
|
|
194
|
+
SerialError,
|
|
195
|
+
SerialErrorCode,
|
|
196
|
+
} from '@gurezo/web-serial-rxjs';
|
|
197
|
+
|
|
198
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
199
|
+
|
|
200
|
+
client.connect().subscribe({
|
|
201
|
+
next: () => console.log('接続しました'),
|
|
202
|
+
error: (error) => {
|
|
203
|
+
if (error instanceof SerialError) {
|
|
204
|
+
switch (error.code) {
|
|
205
|
+
case SerialErrorCode.BROWSER_NOT_SUPPORTED:
|
|
206
|
+
console.error('ブラウザが Web Serial API をサポートしていません');
|
|
207
|
+
break;
|
|
208
|
+
case SerialErrorCode.PORT_NOT_AVAILABLE:
|
|
209
|
+
console.error('シリアルポートが利用できません');
|
|
210
|
+
break;
|
|
211
|
+
case SerialErrorCode.CONNECTION_LOST:
|
|
212
|
+
console.error('接続が切断されました');
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
console.error('シリアルエラー:', error.message);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
console.error('不明なエラー:', error);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### ポートフィルタリング
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
228
|
+
|
|
229
|
+
// USB ベンダー ID でポートをフィルタリング
|
|
230
|
+
const client = createSerialClient({
|
|
231
|
+
baudRate: 9600,
|
|
232
|
+
filters: [{ usbVendorId: 0x1234 }, { usbVendorId: 0x5678 }],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// 特定のポートをリクエスト
|
|
236
|
+
client.requestPort().subscribe({
|
|
237
|
+
next: (port) => {
|
|
238
|
+
console.log('ポートが選択されました:', port);
|
|
239
|
+
// 選択されたポートに接続
|
|
240
|
+
client.connect(port).subscribe({
|
|
241
|
+
next: () => console.log('フィルタリングされたポートに接続しました'),
|
|
242
|
+
error: (error) => console.error('接続エラー:', error),
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
error: (error) => console.error('ポートリクエストエラー:', error),
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## API リファレンス
|
|
250
|
+
|
|
251
|
+
### `createSerialClient(options?)`
|
|
252
|
+
|
|
253
|
+
新しい `SerialClient` インスタンスを作成します。
|
|
254
|
+
|
|
255
|
+
**パラメータ:**
|
|
256
|
+
|
|
257
|
+
- `options?` (オプション): `SerialClientOptions` - シリアルクライアントの設定オプション
|
|
258
|
+
|
|
259
|
+
**戻り値:** `SerialClient` - 新しい SerialClient インスタンス
|
|
260
|
+
|
|
261
|
+
**例:**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const client = createSerialClient({
|
|
265
|
+
baudRate: 9600,
|
|
266
|
+
dataBits: 8,
|
|
267
|
+
stopBits: 1,
|
|
268
|
+
parity: 'none',
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### `SerialClient` インターフェース
|
|
273
|
+
|
|
274
|
+
シリアルポートと対話するためのメインインターフェースです。
|
|
275
|
+
|
|
276
|
+
#### メソッド
|
|
277
|
+
|
|
278
|
+
##### `requestPort(): Observable<SerialPort>`
|
|
279
|
+
|
|
280
|
+
ユーザーからシリアルポートをリクエストします。ポート選択のためのブラウザダイアログを開きます。
|
|
281
|
+
|
|
282
|
+
**戻り値:** `Observable<SerialPort>` - 選択された `SerialPort` インスタンスを発行
|
|
283
|
+
|
|
284
|
+
##### `getPorts(): Observable<SerialPort[]>`
|
|
285
|
+
|
|
286
|
+
ユーザーが以前にアクセスを許可したすべての利用可能なシリアルポートを取得します。
|
|
287
|
+
|
|
288
|
+
**戻り値:** `Observable<SerialPort[]>` - 利用可能な `SerialPort` インスタンスの配列を発行
|
|
289
|
+
|
|
290
|
+
##### `connect(port?: SerialPort): Observable<void>`
|
|
291
|
+
|
|
292
|
+
シリアルポートに接続します。ポートが提供されない場合、ユーザーにリクエストします。
|
|
293
|
+
|
|
294
|
+
**パラメータ:**
|
|
295
|
+
|
|
296
|
+
- `port?` (オプション): `SerialPort` - 接続するポート
|
|
297
|
+
|
|
298
|
+
**戻り値:** `Observable<void>` - ポートが開かれたときに完了
|
|
299
|
+
|
|
300
|
+
##### `disconnect(): Observable<void>`
|
|
301
|
+
|
|
302
|
+
シリアルポートから切断します。
|
|
303
|
+
|
|
304
|
+
**戻り値:** `Observable<void>` - ポートが閉じられたときに完了
|
|
305
|
+
|
|
306
|
+
##### `getReadStream(): Observable<Uint8Array>`
|
|
307
|
+
|
|
308
|
+
シリアルポートから読み取ったデータを発行する Observable を取得します。
|
|
309
|
+
|
|
310
|
+
**戻り値:** `Observable<Uint8Array>` - データが受信されると `Uint8Array` チャンクを発行
|
|
311
|
+
|
|
312
|
+
##### `writeStream(data$: Observable<Uint8Array>): Observable<void>`
|
|
313
|
+
|
|
314
|
+
Observable ストリームからシリアルポートにデータを書き込みます。
|
|
315
|
+
|
|
316
|
+
**パラメータ:**
|
|
317
|
+
|
|
318
|
+
- `data$`: `Observable<Uint8Array>` - 書き込む `Uint8Array` チャンクを発行する Observable
|
|
319
|
+
|
|
320
|
+
**戻り値:** `Observable<void>` - 書き込みが完了したときに完了
|
|
321
|
+
|
|
322
|
+
##### `write(data: Uint8Array): Observable<void>`
|
|
323
|
+
|
|
324
|
+
シリアルポートに単一のデータチャンクを書き込みます。
|
|
325
|
+
|
|
326
|
+
**パラメータ:**
|
|
327
|
+
|
|
328
|
+
- `data`: `Uint8Array` - 書き込むデータ
|
|
329
|
+
|
|
330
|
+
**戻り値:** `Observable<void>` - データが書き込まれたときに完了
|
|
331
|
+
|
|
332
|
+
#### プロパティ
|
|
333
|
+
|
|
334
|
+
- `connected: boolean` - ポートが現在開いているかどうかを示す読み取り専用プロパティ
|
|
335
|
+
- `currentPort: SerialPort | null` - 現在の `SerialPort` インスタンス、または接続されていない場合は `null` の読み取り専用プロパティ
|
|
336
|
+
|
|
337
|
+
### `SerialClientOptions` インターフェース
|
|
338
|
+
|
|
339
|
+
`SerialClient` を作成するための設定オプションです。
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
interface SerialClientOptions {
|
|
343
|
+
baudRate?: number; // デフォルト: 9600
|
|
344
|
+
dataBits?: 7 | 8; // デフォルト: 8
|
|
345
|
+
stopBits?: 1 | 2; // デフォルト: 1
|
|
346
|
+
parity?: 'none' | 'even' | 'odd'; // デフォルト: 'none'
|
|
347
|
+
bufferSize?: number; // デフォルト: 255
|
|
348
|
+
flowControl?: 'none' | 'hardware'; // デフォルト: 'none'
|
|
349
|
+
filters?: SerialPortFilter[]; // オプションのポートフィルター
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**オプション:**
|
|
354
|
+
|
|
355
|
+
- `baudRate` (オプション): 通信速度(ビット/秒)。デフォルト: `9600`
|
|
356
|
+
- `dataBits` (オプション): 文字あたりのデータビット数。`7` または `8`。デフォルト: `8`
|
|
357
|
+
- `stopBits` (オプション): ストップビット数。`1` または `2`。デフォルト: `1`
|
|
358
|
+
- `parity` (オプション): パリティチェックモード。`'none'`、`'even'`、または `'odd'`。デフォルト: `'none'`
|
|
359
|
+
- `bufferSize` (オプション): 読み取りバッファのサイズ。デフォルト: `255`
|
|
360
|
+
- `flowControl` (オプション): フロー制御モード。`'none'` または `'hardware'`。デフォルト: `'none'`
|
|
361
|
+
- `filters` (オプション): 利用可能なポートをフィルタリングする `SerialPortFilter` オブジェクトの配列
|
|
362
|
+
|
|
363
|
+
### エラーハンドリング
|
|
364
|
+
|
|
365
|
+
#### `SerialError` クラス
|
|
366
|
+
|
|
367
|
+
シリアルポート操作のためのカスタムエラークラスです。
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
class SerialError extends Error {
|
|
371
|
+
readonly code: SerialErrorCode;
|
|
372
|
+
readonly originalError?: Error;
|
|
373
|
+
|
|
374
|
+
is(code: SerialErrorCode): boolean;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**プロパティ:**
|
|
379
|
+
|
|
380
|
+
- `code`: `SerialErrorCode` - エラーコード
|
|
381
|
+
- `originalError?`: `Error` - このエラーを引き起こした元のエラー(存在する場合)
|
|
382
|
+
|
|
383
|
+
**メソッド:**
|
|
384
|
+
|
|
385
|
+
- `is(code: SerialErrorCode): boolean` - エラーが特定のエラーコードと一致するかチェック
|
|
386
|
+
|
|
387
|
+
#### `SerialErrorCode` 列挙型
|
|
388
|
+
|
|
389
|
+
さまざまなタイプのシリアルポートエラーのエラーコード:
|
|
390
|
+
|
|
391
|
+
- `BROWSER_NOT_SUPPORTED` - ブラウザが Web Serial API をサポートしていない
|
|
392
|
+
- `PORT_NOT_AVAILABLE` - シリアルポートが利用できない
|
|
393
|
+
- `PORT_OPEN_FAILED` - シリアルポートを開くのに失敗した
|
|
394
|
+
- `PORT_ALREADY_OPEN` - シリアルポートは既に開いている
|
|
395
|
+
- `PORT_NOT_OPEN` - シリアルポートが開いていない
|
|
396
|
+
- `READ_FAILED` - シリアルポートからの読み取りに失敗した
|
|
397
|
+
- `WRITE_FAILED` - シリアルポートへの書き込みに失敗した
|
|
398
|
+
- `CONNECTION_LOST` - シリアルポート接続が切断された
|
|
399
|
+
- `INVALID_FILTER_OPTIONS` - 無効なフィルターオプション
|
|
400
|
+
- `OPERATION_CANCELLED` - 操作がキャンセルされた
|
|
401
|
+
- `UNKNOWN` - 不明なエラー
|
|
402
|
+
|
|
403
|
+
### ブラウザ検出ユーティリティ
|
|
404
|
+
|
|
405
|
+
#### `isBrowserSupported(): boolean`
|
|
406
|
+
|
|
407
|
+
ブラウザが Web Serial API をサポートしているかチェックします(例外を投げないバージョン)。
|
|
408
|
+
|
|
409
|
+
**戻り値:** `boolean` - サポートされている場合は `true`、それ以外は `false`
|
|
410
|
+
|
|
411
|
+
#### `checkBrowserSupport(): void`
|
|
412
|
+
|
|
413
|
+
ブラウザが Web Serial API をサポートしているかチェックします。サポートされていない場合は `SerialError` を投げます。
|
|
414
|
+
|
|
415
|
+
**例外:** Web Serial API をサポートしていない場合、`BROWSER_NOT_SUPPORTED` コードを持つ `SerialError` を投げます
|
|
416
|
+
|
|
417
|
+
#### `detectBrowserType(): BrowserType`
|
|
418
|
+
|
|
419
|
+
ユーザーエージェントからブラウザタイプを検出します。
|
|
420
|
+
|
|
421
|
+
**戻り値:** `BrowserType` - `CHROME`、`EDGE`、`OPERA`、または `UNKNOWN` のいずれか
|
|
422
|
+
|
|
423
|
+
#### `hasWebSerialSupport(): boolean`
|
|
424
|
+
|
|
425
|
+
機能検出を使用してブラウザが Web Serial API サポートを持っているかチェックします。
|
|
426
|
+
|
|
427
|
+
**戻り値:** `boolean` - Web Serial API が利用可能な場合は `true`、それ以外は `false`
|
|
428
|
+
|
|
429
|
+
### I/O ユーティリティ
|
|
430
|
+
|
|
431
|
+
#### `readableToObservable(stream: ReadableStream<Uint8Array>): Observable<Uint8Array>`
|
|
432
|
+
|
|
433
|
+
`ReadableStream` を RxJS `Observable` に変換します。
|
|
434
|
+
|
|
435
|
+
**パラメータ:**
|
|
436
|
+
|
|
437
|
+
- `stream`: `ReadableStream<Uint8Array>` - 変換するストリーム
|
|
438
|
+
|
|
439
|
+
**戻り値:** `Observable<Uint8Array>` - データチャンクを発行する Observable
|
|
440
|
+
|
|
441
|
+
#### `observableToWritable(observable: Observable<Uint8Array>): WritableStream<Uint8Array>`
|
|
442
|
+
|
|
443
|
+
RxJS `Observable` を `WritableStream` に変換します。
|
|
444
|
+
|
|
445
|
+
**パラメータ:**
|
|
446
|
+
|
|
447
|
+
- `observable`: `Observable<Uint8Array>` - 変換する observable
|
|
448
|
+
|
|
449
|
+
**戻り値:** `WritableStream<Uint8Array>` - observable からデータを書き込む書き込み可能なストリーム
|
|
450
|
+
|
|
451
|
+
#### `subscribeToWritable(observable: Observable<Uint8Array>, stream: WritableStream<Uint8Array>): { unsubscribe: () => void }`
|
|
452
|
+
|
|
453
|
+
Observable を購読し、その値を WritableStream に書き込みます。
|
|
454
|
+
|
|
455
|
+
**パラメータ:**
|
|
456
|
+
|
|
457
|
+
- `observable`: `Observable<Uint8Array>` - 購読する observable
|
|
458
|
+
- `stream`: `WritableStream<Uint8Array>` - 書き込むストリーム
|
|
459
|
+
|
|
460
|
+
**戻り値:** `unsubscribe()` メソッドを持つ購読オブジェクト
|
|
461
|
+
|
|
462
|
+
## フレームワーク別の例
|
|
463
|
+
|
|
464
|
+
このリポジトリには、さまざまなフレームワークで web-serial-rxjs を使用する方法を示すサンプルアプリケーションが含まれています:
|
|
465
|
+
|
|
466
|
+
- **[Vanilla JavaScript](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vanilla-js)** - バニラ JavaScript での基本的な使用方法
|
|
467
|
+
- **[Vanilla TypeScript](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vanilla-ts)** - RxJS を使用した TypeScript の例
|
|
468
|
+
- **[React](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-react)** - カスタムフック(`useSerialClient`)を使用した React の例
|
|
469
|
+
- **[Vue](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vue)** - Composition API を使用した Vue 3 の例
|
|
470
|
+
- **[Svelte](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-svelte)** - Svelte Store を使用した Svelte の例
|
|
471
|
+
- **[Angular](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-angular)** - Service を使用した Angular の例
|
|
472
|
+
|
|
473
|
+
各例には、セットアップと使用方法の説明を含む README が含まれています。
|
|
474
|
+
|
|
475
|
+
## 高度な使用方法
|
|
476
|
+
|
|
477
|
+
### Observable パターン
|
|
478
|
+
|
|
479
|
+
RxJS オペレーターを使用してシリアルデータを処理できます:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { map, filter, bufferTime } from 'rxjs/operators';
|
|
483
|
+
|
|
484
|
+
client
|
|
485
|
+
.getReadStream()
|
|
486
|
+
.pipe(
|
|
487
|
+
map((data: Uint8Array) => {
|
|
488
|
+
const decoder = new TextDecoder('utf-8');
|
|
489
|
+
return decoder.decode(data);
|
|
490
|
+
}),
|
|
491
|
+
filter((text) => text.trim().length > 0),
|
|
492
|
+
bufferTime(1000), // 1 秒間メッセージをバッファリング
|
|
493
|
+
)
|
|
494
|
+
.subscribe({
|
|
495
|
+
next: (messages) => {
|
|
496
|
+
console.log('バッファリングされたメッセージ:', messages);
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### ストリーム処理
|
|
502
|
+
|
|
503
|
+
RxJS オペレーターでデータストリームを処理:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { map, scan, debounceTime } from 'rxjs/operators';
|
|
507
|
+
|
|
508
|
+
// 受信データを累積
|
|
509
|
+
client
|
|
510
|
+
.getReadStream()
|
|
511
|
+
.pipe(
|
|
512
|
+
map((data: Uint8Array) => {
|
|
513
|
+
const decoder = new TextDecoder('utf-8');
|
|
514
|
+
return decoder.decode(data);
|
|
515
|
+
}),
|
|
516
|
+
scan((acc, current) => acc + current, ''),
|
|
517
|
+
debounceTime(500),
|
|
518
|
+
)
|
|
519
|
+
.subscribe({
|
|
520
|
+
next: (accumulated) => {
|
|
521
|
+
console.log('累積データ:', accumulated);
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### カスタムフィルター
|
|
527
|
+
|
|
528
|
+
ポートフィルターを使用して利用可能なポートを制限:
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
const client = createSerialClient({
|
|
532
|
+
baudRate: 9600,
|
|
533
|
+
filters: [
|
|
534
|
+
{ usbVendorId: 0x1234, usbProductId: 0x5678 },
|
|
535
|
+
{ usbVendorId: 0xabcd },
|
|
536
|
+
],
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### エラー回復
|
|
541
|
+
|
|
542
|
+
エラー回復パターンを実装:
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
import { retry, catchError } from 'rxjs/operators';
|
|
546
|
+
import { of } from 'rxjs';
|
|
547
|
+
|
|
548
|
+
client
|
|
549
|
+
.getReadStream()
|
|
550
|
+
.pipe(
|
|
551
|
+
retry({
|
|
552
|
+
count: 3,
|
|
553
|
+
delay: 1000,
|
|
554
|
+
}),
|
|
555
|
+
catchError((error) => {
|
|
556
|
+
console.error('リトライ後も失敗:', error);
|
|
557
|
+
return of(null); // 空の observable を返す
|
|
558
|
+
}),
|
|
559
|
+
)
|
|
560
|
+
.subscribe({
|
|
561
|
+
next: (data) => {
|
|
562
|
+
if (data) {
|
|
563
|
+
console.log('受信:', data);
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## 開発とリリース戦略
|
|
570
|
+
|
|
571
|
+
このプロジェクトは**trunk-based開発**アプローチに従います:
|
|
572
|
+
|
|
573
|
+
- **`main`ブランチ**: 常にリリース可能な状態
|
|
574
|
+
- **短命ブランチ**: `feature/*`, `fix/*`, `docs/*` はプルリクエスト用
|
|
575
|
+
- **リリース**: ブランチではなくGitタグ(例: `v1.0.0`)で管理
|
|
576
|
+
- **バージョン保守**: 複数のメジャーバージョンを保守する必要がある場合のみ `release/v*` ブランチを追加
|
|
577
|
+
|
|
578
|
+
詳細な貢献ガイドラインについては、[CONTRIBUTING.ja.md](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.ja.md) を参照してください。
|
|
579
|
+
|
|
580
|
+
## 貢献
|
|
581
|
+
|
|
582
|
+
貢献を歓迎します!詳細については、[貢献ガイド](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.ja.md)を参照してください:
|
|
583
|
+
|
|
584
|
+
- 開発環境のセットアップ
|
|
585
|
+
- コードスタイルガイドライン
|
|
586
|
+
- コミットメッセージの規約
|
|
587
|
+
- プルリクエストのプロセス
|
|
588
|
+
|
|
589
|
+
英語版の貢献ガイドは [CONTRIBUTING.md](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.md) を参照してください。
|
|
590
|
+
|
|
591
|
+
## ライセンス
|
|
592
|
+
|
|
593
|
+
このプロジェクトは MIT ライセンスの下で公開されています。詳細は [LICENSE](https://github.com/gurezo/web-serial-rxjs/blob/main/LICENSE) ファイルを参照してください。
|
|
594
|
+
|
|
595
|
+
## リンク
|
|
596
|
+
|
|
597
|
+
- **GitHub リポジトリ**: [https://github.com/gurezo/web-serial-rxjs](https://github.com/gurezo/web-serial-rxjs)
|
|
598
|
+
- **イシュー**: [https://github.com/gurezo/web-serial-rxjs/issues](https://github.com/gurezo/web-serial-rxjs/issues)
|
|
599
|
+
- **Web Serial API 仕様**: [https://wicg.github.io/serial/](https://wicg.github.io/serial/)
|
package/README.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# web-serial-rxjs
|
|
2
|
+
|
|
3
|
+
A TypeScript library that provides a reactive RxJS-based wrapper for the Web Serial API, enabling easy serial port communication in web applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Browser Support](#browser-support)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Usage Examples](#usage-examples)
|
|
12
|
+
- [API Reference](#api-reference)
|
|
13
|
+
- [Framework Examples](#framework-examples)
|
|
14
|
+
- [Advanced Usage](#advanced-usage)
|
|
15
|
+
- [Contributing](#contributing)
|
|
16
|
+
- [License](#license)
|
|
17
|
+
- [Links](#links)
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **RxJS-based reactive API**: Leverage the power of RxJS Observables for reactive serial port communication
|
|
22
|
+
- **TypeScript support**: Full TypeScript type definitions included
|
|
23
|
+
- **Browser detection**: Built-in browser support detection and error handling
|
|
24
|
+
- **Error handling**: Comprehensive error handling with custom error classes and error codes
|
|
25
|
+
- **Framework agnostic**: Works with any JavaScript/TypeScript framework or vanilla JavaScript
|
|
26
|
+
|
|
27
|
+
## Browser Support
|
|
28
|
+
|
|
29
|
+
The Web Serial API is currently only supported in Chromium-based browsers:
|
|
30
|
+
|
|
31
|
+
- **Chrome** 89+
|
|
32
|
+
- **Edge** 89+
|
|
33
|
+
- **Opera** 75+
|
|
34
|
+
|
|
35
|
+
The library includes built-in browser detection utilities to check for Web Serial API support before attempting to use it.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install the package using npm or pnpm:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @gurezo/web-serial-rxjs
|
|
43
|
+
# or
|
|
44
|
+
pnpm add @gurezo/web-serial-rxjs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Peer Dependencies
|
|
48
|
+
|
|
49
|
+
This library requires RxJS as a peer dependency:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install rxjs
|
|
53
|
+
# or
|
|
54
|
+
pnpm add rxjs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Minimum required version**: RxJS ^7.8.0
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
Here's a simple example to get you started:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
createSerialClient,
|
|
66
|
+
isBrowserSupported,
|
|
67
|
+
} from '@gurezo/web-serial-rxjs';
|
|
68
|
+
|
|
69
|
+
// Check browser support
|
|
70
|
+
if (!isBrowserSupported()) {
|
|
71
|
+
console.error('Web Serial API is not supported in this browser');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create a serial client
|
|
76
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
77
|
+
|
|
78
|
+
// Connect to a serial port
|
|
79
|
+
client.connect().subscribe({
|
|
80
|
+
next: () => {
|
|
81
|
+
console.log('Connected to serial port');
|
|
82
|
+
|
|
83
|
+
// Read data from the serial port
|
|
84
|
+
client.getReadStream().subscribe({
|
|
85
|
+
next: (data: Uint8Array) => {
|
|
86
|
+
const decoder = new TextDecoder('utf-8');
|
|
87
|
+
const text = decoder.decode(data);
|
|
88
|
+
console.log('Received:', text);
|
|
89
|
+
},
|
|
90
|
+
error: (error) => {
|
|
91
|
+
console.error('Read error:', error);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Write data to the serial port
|
|
96
|
+
const encoder = new TextEncoder();
|
|
97
|
+
const data = encoder.encode('Hello, Serial Port!\n');
|
|
98
|
+
client.write(data).subscribe({
|
|
99
|
+
next: () => console.log('Data written'),
|
|
100
|
+
error: (error) => console.error('Write error:', error),
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
error: (error) => {
|
|
104
|
+
console.error('Connection error:', error);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Usage Examples
|
|
110
|
+
|
|
111
|
+
### Basic Connection
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
115
|
+
|
|
116
|
+
const client = createSerialClient({
|
|
117
|
+
baudRate: 115200,
|
|
118
|
+
dataBits: 8,
|
|
119
|
+
stopBits: 1,
|
|
120
|
+
parity: 'none',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Connect (will prompt user to select a port)
|
|
124
|
+
client.connect().subscribe({
|
|
125
|
+
next: () => console.log('Connected'),
|
|
126
|
+
error: (error) => console.error('Connection failed:', error),
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Reading Data
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
134
|
+
import { map } from 'rxjs/operators';
|
|
135
|
+
|
|
136
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
137
|
+
|
|
138
|
+
client.connect().subscribe({
|
|
139
|
+
next: () => {
|
|
140
|
+
// Read and decode data
|
|
141
|
+
client
|
|
142
|
+
.getReadStream()
|
|
143
|
+
.pipe(
|
|
144
|
+
map((data: Uint8Array) => {
|
|
145
|
+
const decoder = new TextDecoder('utf-8');
|
|
146
|
+
return decoder.decode(data);
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
.subscribe({
|
|
150
|
+
next: (text) => console.log('Received:', text),
|
|
151
|
+
error: (error) => console.error('Read error:', error),
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Writing Data
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
161
|
+
import { from } from 'rxjs';
|
|
162
|
+
|
|
163
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
164
|
+
|
|
165
|
+
client.connect().subscribe({
|
|
166
|
+
next: () => {
|
|
167
|
+
// Write a single chunk
|
|
168
|
+
const encoder = new TextEncoder();
|
|
169
|
+
const data = encoder.encode('Hello\n');
|
|
170
|
+
client.write(data).subscribe({
|
|
171
|
+
next: () => console.log('Written'),
|
|
172
|
+
error: (error) => console.error('Write error:', error),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Write from an Observable stream
|
|
176
|
+
const messages = ['Message 1\n', 'Message 2\n', 'Message 3\n'];
|
|
177
|
+
const dataStream$ = from(messages).pipe(
|
|
178
|
+
map((msg) => new TextEncoder().encode(msg)),
|
|
179
|
+
);
|
|
180
|
+
client.writeStream(dataStream$).subscribe({
|
|
181
|
+
next: () => console.log('All messages written'),
|
|
182
|
+
error: (error) => console.error('Stream write error:', error),
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Error Handling
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import {
|
|
192
|
+
createSerialClient,
|
|
193
|
+
SerialError,
|
|
194
|
+
SerialErrorCode,
|
|
195
|
+
} from '@gurezo/web-serial-rxjs';
|
|
196
|
+
|
|
197
|
+
const client = createSerialClient({ baudRate: 9600 });
|
|
198
|
+
|
|
199
|
+
client.connect().subscribe({
|
|
200
|
+
next: () => console.log('Connected'),
|
|
201
|
+
error: (error) => {
|
|
202
|
+
if (error instanceof SerialError) {
|
|
203
|
+
switch (error.code) {
|
|
204
|
+
case SerialErrorCode.BROWSER_NOT_SUPPORTED:
|
|
205
|
+
console.error('Browser does not support Web Serial API');
|
|
206
|
+
break;
|
|
207
|
+
case SerialErrorCode.PORT_NOT_AVAILABLE:
|
|
208
|
+
console.error('Serial port is not available');
|
|
209
|
+
break;
|
|
210
|
+
case SerialErrorCode.CONNECTION_LOST:
|
|
211
|
+
console.error('Connection lost');
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
console.error('Serial error:', error.message);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
console.error('Unknown error:', error);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Port Filtering
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { createSerialClient } from '@gurezo/web-serial-rxjs';
|
|
227
|
+
|
|
228
|
+
// Filter ports by USB vendor ID
|
|
229
|
+
const client = createSerialClient({
|
|
230
|
+
baudRate: 9600,
|
|
231
|
+
filters: [{ usbVendorId: 0x1234 }, { usbVendorId: 0x5678 }],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Request a specific port
|
|
235
|
+
client.requestPort().subscribe({
|
|
236
|
+
next: (port) => {
|
|
237
|
+
console.log('Port selected:', port);
|
|
238
|
+
// Connect to the selected port
|
|
239
|
+
client.connect(port).subscribe({
|
|
240
|
+
next: () => console.log('Connected to filtered port'),
|
|
241
|
+
error: (error) => console.error('Connection error:', error),
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
error: (error) => console.error('Port request error:', error),
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### `createSerialClient(options?)`
|
|
251
|
+
|
|
252
|
+
Creates a new `SerialClient` instance.
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
|
|
256
|
+
- `options?` (optional): `SerialClientOptions` - Configuration options for the serial client
|
|
257
|
+
|
|
258
|
+
**Returns:** `SerialClient` - A new SerialClient instance
|
|
259
|
+
|
|
260
|
+
**Example:**
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const client = createSerialClient({
|
|
264
|
+
baudRate: 9600,
|
|
265
|
+
dataBits: 8,
|
|
266
|
+
stopBits: 1,
|
|
267
|
+
parity: 'none',
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### `SerialClient` Interface
|
|
272
|
+
|
|
273
|
+
The main interface for interacting with serial ports.
|
|
274
|
+
|
|
275
|
+
#### Methods
|
|
276
|
+
|
|
277
|
+
##### `requestPort(): Observable<SerialPort>`
|
|
278
|
+
|
|
279
|
+
Requests a serial port from the user. Opens a browser dialog for port selection.
|
|
280
|
+
|
|
281
|
+
**Returns:** `Observable<SerialPort>` - Emits the selected `SerialPort` instance
|
|
282
|
+
|
|
283
|
+
##### `getPorts(): Observable<SerialPort[]>`
|
|
284
|
+
|
|
285
|
+
Gets all available serial ports that the user has previously granted access to.
|
|
286
|
+
|
|
287
|
+
**Returns:** `Observable<SerialPort[]>` - Emits an array of available `SerialPort` instances
|
|
288
|
+
|
|
289
|
+
##### `connect(port?: SerialPort): Observable<void>`
|
|
290
|
+
|
|
291
|
+
Connects to a serial port. If no port is provided, will request one from the user.
|
|
292
|
+
|
|
293
|
+
**Parameters:**
|
|
294
|
+
|
|
295
|
+
- `port?` (optional): `SerialPort` - The port to connect to
|
|
296
|
+
|
|
297
|
+
**Returns:** `Observable<void>` - Completes when the port is opened
|
|
298
|
+
|
|
299
|
+
##### `disconnect(): Observable<void>`
|
|
300
|
+
|
|
301
|
+
Disconnects from the serial port.
|
|
302
|
+
|
|
303
|
+
**Returns:** `Observable<void>` - Completes when the port is closed
|
|
304
|
+
|
|
305
|
+
##### `getReadStream(): Observable<Uint8Array>`
|
|
306
|
+
|
|
307
|
+
Gets an Observable that emits data read from the serial port.
|
|
308
|
+
|
|
309
|
+
**Returns:** `Observable<Uint8Array>` - Emits `Uint8Array` chunks as data is received
|
|
310
|
+
|
|
311
|
+
##### `writeStream(data$: Observable<Uint8Array>): Observable<void>`
|
|
312
|
+
|
|
313
|
+
Writes data to the serial port from an Observable stream.
|
|
314
|
+
|
|
315
|
+
**Parameters:**
|
|
316
|
+
|
|
317
|
+
- `data$`: `Observable<Uint8Array>` - Observable that emits `Uint8Array` chunks to write
|
|
318
|
+
|
|
319
|
+
**Returns:** `Observable<void>` - Completes when writing is finished
|
|
320
|
+
|
|
321
|
+
##### `write(data: Uint8Array): Observable<void>`
|
|
322
|
+
|
|
323
|
+
Writes a single chunk of data to the serial port.
|
|
324
|
+
|
|
325
|
+
**Parameters:**
|
|
326
|
+
|
|
327
|
+
- `data`: `Uint8Array` - Data to write
|
|
328
|
+
|
|
329
|
+
**Returns:** `Observable<void>` - Completes when the data is written
|
|
330
|
+
|
|
331
|
+
#### Properties
|
|
332
|
+
|
|
333
|
+
- `connected: boolean` - Read-only property indicating if the port is currently open
|
|
334
|
+
- `currentPort: SerialPort | null` - Read-only property with the current `SerialPort` instance, or `null` if not connected
|
|
335
|
+
|
|
336
|
+
### `SerialClientOptions` Interface
|
|
337
|
+
|
|
338
|
+
Configuration options for creating a `SerialClient`.
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
interface SerialClientOptions {
|
|
342
|
+
baudRate?: number; // Default: 9600
|
|
343
|
+
dataBits?: 7 | 8; // Default: 8
|
|
344
|
+
stopBits?: 1 | 2; // Default: 1
|
|
345
|
+
parity?: 'none' | 'even' | 'odd'; // Default: 'none'
|
|
346
|
+
bufferSize?: number; // Default: 255
|
|
347
|
+
flowControl?: 'none' | 'hardware'; // Default: 'none'
|
|
348
|
+
filters?: SerialPortFilter[]; // Optional port filters
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Options:**
|
|
353
|
+
|
|
354
|
+
- `baudRate` (optional): Communication speed in bits per second. Default: `9600`
|
|
355
|
+
- `dataBits` (optional): Number of data bits per character. Either `7` or `8`. Default: `8`
|
|
356
|
+
- `stopBits` (optional): Number of stop bits. Either `1` or `2`. Default: `1`
|
|
357
|
+
- `parity` (optional): Parity checking mode. `'none'`, `'even'`, or `'odd'`. Default: `'none'`
|
|
358
|
+
- `bufferSize` (optional): Size of the read buffer. Default: `255`
|
|
359
|
+
- `flowControl` (optional): Flow control mode. `'none'` or `'hardware'`. Default: `'none'`
|
|
360
|
+
- `filters` (optional): Array of `SerialPortFilter` objects to filter available ports
|
|
361
|
+
|
|
362
|
+
### Error Handling
|
|
363
|
+
|
|
364
|
+
#### `SerialError` Class
|
|
365
|
+
|
|
366
|
+
Custom error class for serial port operations.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
class SerialError extends Error {
|
|
370
|
+
readonly code: SerialErrorCode;
|
|
371
|
+
readonly originalError?: Error;
|
|
372
|
+
|
|
373
|
+
is(code: SerialErrorCode): boolean;
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Properties:**
|
|
378
|
+
|
|
379
|
+
- `code`: `SerialErrorCode` - The error code
|
|
380
|
+
- `originalError?`: `Error` - The original error that caused this error (if any)
|
|
381
|
+
|
|
382
|
+
**Methods:**
|
|
383
|
+
|
|
384
|
+
- `is(code: SerialErrorCode): boolean` - Check if the error matches a specific error code
|
|
385
|
+
|
|
386
|
+
#### `SerialErrorCode` Enum
|
|
387
|
+
|
|
388
|
+
Error codes for different types of serial port errors:
|
|
389
|
+
|
|
390
|
+
- `BROWSER_NOT_SUPPORTED` - Browser does not support Web Serial API
|
|
391
|
+
- `PORT_NOT_AVAILABLE` - Serial port is not available
|
|
392
|
+
- `PORT_OPEN_FAILED` - Failed to open serial port
|
|
393
|
+
- `PORT_ALREADY_OPEN` - Serial port is already open
|
|
394
|
+
- `PORT_NOT_OPEN` - Serial port is not open
|
|
395
|
+
- `READ_FAILED` - Failed to read from serial port
|
|
396
|
+
- `WRITE_FAILED` - Failed to write to serial port
|
|
397
|
+
- `CONNECTION_LOST` - Serial port connection lost
|
|
398
|
+
- `INVALID_FILTER_OPTIONS` - Invalid filter options
|
|
399
|
+
- `OPERATION_CANCELLED` - Operation was cancelled
|
|
400
|
+
- `UNKNOWN` - Unknown error
|
|
401
|
+
|
|
402
|
+
### Browser Detection Utilities
|
|
403
|
+
|
|
404
|
+
#### `isBrowserSupported(): boolean`
|
|
405
|
+
|
|
406
|
+
Checks if the browser supports the Web Serial API (non-throwing version).
|
|
407
|
+
|
|
408
|
+
**Returns:** `boolean` - `true` if supported, `false` otherwise
|
|
409
|
+
|
|
410
|
+
#### `checkBrowserSupport(): void`
|
|
411
|
+
|
|
412
|
+
Checks if the browser supports the Web Serial API. Throws a `SerialError` if not supported.
|
|
413
|
+
|
|
414
|
+
**Throws:** `SerialError` with code `BROWSER_NOT_SUPPORTED` if the browser doesn't support Web Serial API
|
|
415
|
+
|
|
416
|
+
#### `detectBrowserType(): BrowserType`
|
|
417
|
+
|
|
418
|
+
Detects the browser type from the user agent.
|
|
419
|
+
|
|
420
|
+
**Returns:** `BrowserType` - One of `CHROME`, `EDGE`, `OPERA`, or `UNKNOWN`
|
|
421
|
+
|
|
422
|
+
#### `hasWebSerialSupport(): boolean`
|
|
423
|
+
|
|
424
|
+
Checks if the browser has Web Serial API support using feature detection.
|
|
425
|
+
|
|
426
|
+
**Returns:** `boolean` - `true` if Web Serial API is available, `false` otherwise
|
|
427
|
+
|
|
428
|
+
### I/O Utilities
|
|
429
|
+
|
|
430
|
+
#### `readableToObservable(stream: ReadableStream<Uint8Array>): Observable<Uint8Array>`
|
|
431
|
+
|
|
432
|
+
Converts a `ReadableStream` to an RxJS `Observable`.
|
|
433
|
+
|
|
434
|
+
**Parameters:**
|
|
435
|
+
|
|
436
|
+
- `stream`: `ReadableStream<Uint8Array>` - The stream to convert
|
|
437
|
+
|
|
438
|
+
**Returns:** `Observable<Uint8Array>` - Observable that emits data chunks
|
|
439
|
+
|
|
440
|
+
#### `observableToWritable(observable: Observable<Uint8Array>): WritableStream<Uint8Array>`
|
|
441
|
+
|
|
442
|
+
Converts an RxJS `Observable` to a `WritableStream`.
|
|
443
|
+
|
|
444
|
+
**Parameters:**
|
|
445
|
+
|
|
446
|
+
- `observable`: `Observable<Uint8Array>` - The observable to convert
|
|
447
|
+
|
|
448
|
+
**Returns:** `WritableStream<Uint8Array>` - Writable stream that writes data from the observable
|
|
449
|
+
|
|
450
|
+
#### `subscribeToWritable(observable: Observable<Uint8Array>, stream: WritableStream<Uint8Array>): { unsubscribe: () => void }`
|
|
451
|
+
|
|
452
|
+
Subscribes to an Observable and writes its values to a WritableStream.
|
|
453
|
+
|
|
454
|
+
**Parameters:**
|
|
455
|
+
|
|
456
|
+
- `observable`: `Observable<Uint8Array>` - The observable to subscribe to
|
|
457
|
+
- `stream`: `WritableStream<Uint8Array>` - The stream to write to
|
|
458
|
+
|
|
459
|
+
**Returns:** Subscription object with `unsubscribe()` method
|
|
460
|
+
|
|
461
|
+
## Framework Examples
|
|
462
|
+
|
|
463
|
+
This repository includes example applications demonstrating how to use web-serial-rxjs with different frameworks:
|
|
464
|
+
|
|
465
|
+
- **[Vanilla JavaScript](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vanilla-js)** - Basic usage with vanilla JavaScript
|
|
466
|
+
- **[Vanilla TypeScript](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vanilla-ts)** - TypeScript example with RxJS
|
|
467
|
+
- **[React](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-react)** - React example with custom hook (`useSerialClient`)
|
|
468
|
+
- **[Vue](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-vue)** - Vue 3 example using Composition API
|
|
469
|
+
- **[Svelte](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-svelte)** - Svelte example using Svelte Store
|
|
470
|
+
- **[Angular](https://github.com/gurezo/web-serial-rxjs/tree/main/apps/example-angular)** - Angular example using a Service
|
|
471
|
+
|
|
472
|
+
Each example includes a README with setup and usage instructions.
|
|
473
|
+
|
|
474
|
+
## Advanced Usage
|
|
475
|
+
|
|
476
|
+
### Observable Patterns
|
|
477
|
+
|
|
478
|
+
You can use RxJS operators to process serial data:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { map, filter, bufferTime } from 'rxjs/operators';
|
|
482
|
+
|
|
483
|
+
client
|
|
484
|
+
.getReadStream()
|
|
485
|
+
.pipe(
|
|
486
|
+
map((data: Uint8Array) => {
|
|
487
|
+
const decoder = new TextDecoder('utf-8');
|
|
488
|
+
return decoder.decode(data);
|
|
489
|
+
}),
|
|
490
|
+
filter((text) => text.trim().length > 0),
|
|
491
|
+
bufferTime(1000), // Buffer messages for 1 second
|
|
492
|
+
)
|
|
493
|
+
.subscribe({
|
|
494
|
+
next: (messages) => {
|
|
495
|
+
console.log('Buffered messages:', messages);
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Stream Processing
|
|
501
|
+
|
|
502
|
+
Process data streams with RxJS operators:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { map, scan, debounceTime } from 'rxjs/operators';
|
|
506
|
+
|
|
507
|
+
// Accumulate received data
|
|
508
|
+
client
|
|
509
|
+
.getReadStream()
|
|
510
|
+
.pipe(
|
|
511
|
+
map((data: Uint8Array) => {
|
|
512
|
+
const decoder = new TextDecoder('utf-8');
|
|
513
|
+
return decoder.decode(data);
|
|
514
|
+
}),
|
|
515
|
+
scan((acc, current) => acc + current, ''),
|
|
516
|
+
debounceTime(500),
|
|
517
|
+
)
|
|
518
|
+
.subscribe({
|
|
519
|
+
next: (accumulated) => {
|
|
520
|
+
console.log('Accumulated data:', accumulated);
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Custom Filters
|
|
526
|
+
|
|
527
|
+
Use port filters to limit available ports:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
const client = createSerialClient({
|
|
531
|
+
baudRate: 9600,
|
|
532
|
+
filters: [
|
|
533
|
+
{ usbVendorId: 0x1234, usbProductId: 0x5678 },
|
|
534
|
+
{ usbVendorId: 0xabcd },
|
|
535
|
+
],
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Error Recovery
|
|
540
|
+
|
|
541
|
+
Implement error recovery patterns:
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
import { retry, catchError } from 'rxjs/operators';
|
|
545
|
+
import { of } from 'rxjs';
|
|
546
|
+
|
|
547
|
+
client
|
|
548
|
+
.getReadStream()
|
|
549
|
+
.pipe(
|
|
550
|
+
retry({
|
|
551
|
+
count: 3,
|
|
552
|
+
delay: 1000,
|
|
553
|
+
}),
|
|
554
|
+
catchError((error) => {
|
|
555
|
+
console.error('Failed after retries:', error);
|
|
556
|
+
return of(null); // Return empty observable
|
|
557
|
+
}),
|
|
558
|
+
)
|
|
559
|
+
.subscribe({
|
|
560
|
+
next: (data) => {
|
|
561
|
+
if (data) {
|
|
562
|
+
console.log('Received:', data);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Development and Release Strategy
|
|
569
|
+
|
|
570
|
+
This project follows a **trunk-based development** approach:
|
|
571
|
+
|
|
572
|
+
- **`main` branch**: Always in a release-ready state
|
|
573
|
+
- **Short-lived branches**: `feature/*`, `fix/*`, `docs/*` for pull requests
|
|
574
|
+
- **Releases**: Managed via Git tags (e.g., `v1.0.0`), not branches
|
|
575
|
+
- **Version maintenance**: `release/v*` branches are added only when needed for maintaining multiple major versions
|
|
576
|
+
|
|
577
|
+
For detailed contribution guidelines, see [CONTRIBUTING.md](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.md).
|
|
578
|
+
|
|
579
|
+
## Contributing
|
|
580
|
+
|
|
581
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.md) for details on:
|
|
582
|
+
|
|
583
|
+
- Development setup
|
|
584
|
+
- Code style guidelines
|
|
585
|
+
- Commit message conventions
|
|
586
|
+
- Pull request process
|
|
587
|
+
|
|
588
|
+
For Japanese contributors, please see [CONTRIBUTING.ja.md](https://github.com/gurezo/web-serial-rxjs/blob/main/CONTRIBUTING.ja.md).
|
|
589
|
+
|
|
590
|
+
## License
|
|
591
|
+
|
|
592
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/gurezo/web-serial-rxjs/blob/main/LICENSE) file for details.
|
|
593
|
+
|
|
594
|
+
## Links
|
|
595
|
+
|
|
596
|
+
- **GitHub Repository**: [https://github.com/gurezo/web-serial-rxjs](https://github.com/gurezo/web-serial-rxjs)
|
|
597
|
+
- **Issues**: [https://github.com/gurezo/web-serial-rxjs/issues](https://github.com/gurezo/web-serial-rxjs/issues)
|
|
598
|
+
- **Web Serial API Specification**: [https://wicg.github.io/serial/](https://wicg.github.io/serial/)
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gurezo/web-serial-rxjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A TypeScript library that provides a reactive RxJS-based wrapper for the Web Serial API, enabling easy serial port communication in web applications.",
|
|
5
|
+
"author": "Akihiko Kigure <akihiko.kigure@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/**",
|
|
18
|
+
"README.md",
|
|
19
|
+
"README.ja.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"rxjs": "^7.8.0"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/gurezo/web-serial-rxjs.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/gurezo/web-serial-rxjs/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/gurezo/web-serial-rxjs#readme",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"rxjs",
|
|
36
|
+
"serial",
|
|
37
|
+
"web-serial",
|
|
38
|
+
"serial-port",
|
|
39
|
+
"reactive",
|
|
40
|
+
"observable",
|
|
41
|
+
"typescript",
|
|
42
|
+
"web-serial-api"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "pnpm run build:types && pnpm run build:bundle && pnpm run build:copy-files",
|
|
49
|
+
"build:types": "tsc --project tsconfig.lib.json",
|
|
50
|
+
"build:bundle": "node esbuild.config.mjs",
|
|
51
|
+
"build:copy-files": "cp ../../LICENSE . 2>/dev/null || true",
|
|
52
|
+
"clean": "rm -rf dist"
|
|
53
|
+
}
|
|
54
|
+
}
|