@alexgyver/serial 1.0.1 → 1.0.2
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/README.md +36 -2
- package/package.json +2 -2
- package/serial.js +117 -85
- package/serial.min.js +1 -1
- package/test/index.html +1 -0
- package/test/script.js +6 -8
- package/test_arduino/test_arduino.ino +12 -0
- package/test_dev/index.html +1 -0
- package/test_dev/script.js +6 -9
package/README.md
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
# Serial.js
|
|
2
|
-
Web Serial API
|
|
2
|
+
Обёртка на Web Serial API
|
|
3
|
+
- Автоматическое переподключение
|
|
4
|
+
- Сохранение последнего выбранного порта
|
|
5
|
+
- Буферизация отправки
|
|
6
|
+
- Буферизация приёма, разделение текста по разделителю
|
|
3
7
|
|
|
4
|
-
> npm i @alexgyver/serial
|
|
8
|
+
> npm i @alexgyver/serial
|
|
9
|
+
|
|
10
|
+
## Дока
|
|
11
|
+
```js
|
|
12
|
+
constructor(params = {});
|
|
13
|
+
config(params = {});
|
|
14
|
+
// eol: /\r?\n/
|
|
15
|
+
// baud: 115200
|
|
16
|
+
// reconnect: 1000
|
|
17
|
+
|
|
18
|
+
onbin(b);
|
|
19
|
+
ontext(t);
|
|
20
|
+
|
|
21
|
+
onopen():
|
|
22
|
+
onclose():
|
|
23
|
+
onchange(s):
|
|
24
|
+
onselect(name);
|
|
25
|
+
onerror(e);
|
|
26
|
+
|
|
27
|
+
static supported();
|
|
28
|
+
opened();
|
|
29
|
+
selected();
|
|
30
|
+
getName();
|
|
31
|
+
|
|
32
|
+
select();
|
|
33
|
+
open();
|
|
34
|
+
close();
|
|
35
|
+
|
|
36
|
+
sendBin(data);
|
|
37
|
+
sendText(text);
|
|
38
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexgyver/serial",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Web Serial API wrapper",
|
|
5
5
|
"main": "./serial.js",
|
|
6
6
|
"module": "./serial.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"html-webpack-plugin": "^5.6.5"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@alexgyver/utils": "^1.2.
|
|
23
|
+
"@alexgyver/utils": "^1.2.3"
|
|
24
24
|
},
|
|
25
25
|
"author": "AlexGyver <alex@alexgyver.ru>",
|
|
26
26
|
"license": "MIT"
|
package/serial.js
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
|
-
import { StreamSplitter } from "@alexgyver/utils";
|
|
1
|
+
import { sleep, ShiftBuffer, StreamSplitter } from "@alexgyver/utils";
|
|
2
|
+
|
|
3
|
+
const States = {
|
|
4
|
+
Open: 1,
|
|
5
|
+
Closing: 2,
|
|
6
|
+
Closed: 3,
|
|
7
|
+
};
|
|
2
8
|
|
|
3
9
|
export default class SerialJS {
|
|
4
10
|
//#region handlers
|
|
5
11
|
onbin = null;
|
|
6
12
|
ontext = null;
|
|
7
|
-
online = null;
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
onopen() { }
|
|
15
|
+
onclose() { }
|
|
16
|
+
onchange(s) { }
|
|
17
|
+
onselect(name) { }
|
|
18
|
+
onerror(e) { }
|
|
13
19
|
|
|
14
20
|
//#region constructor
|
|
15
|
-
constructor() {
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
constructor(params = {}) {
|
|
22
|
+
const def = {
|
|
23
|
+
eol: /\r?\n/,
|
|
24
|
+
baud: 115200,
|
|
25
|
+
reconnect: 1000,
|
|
26
|
+
};
|
|
27
|
+
this.cfg = { ...def, ...params };
|
|
28
|
+
|
|
29
|
+
this._setLastPort().then(() => this.onselect(this.getName()));
|
|
30
|
+
this.splitter = new StreamSplitter(this.cfg.eol);
|
|
31
|
+
this.splitter.ontext = (t) => this.ontext(t);
|
|
32
|
+
}
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
config(params = {}) {
|
|
35
|
+
this.cfg = { ...this.cfg, ...params };
|
|
36
|
+
this.splitter.eol = this.cfg.eol;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
//#region methods
|
|
40
|
+
static supported() {
|
|
41
|
+
return 'serial' in navigator;
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
opened() {
|
|
26
|
-
return this.
|
|
45
|
+
return this._state == States.Open;
|
|
27
46
|
}
|
|
28
47
|
|
|
29
48
|
selected() {
|
|
@@ -31,7 +50,7 @@ export default class SerialJS {
|
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
getName() {
|
|
34
|
-
if (!this._port) return
|
|
53
|
+
if (!this._port) return null;
|
|
35
54
|
|
|
36
55
|
switch (this._port.getInfo().usbProductId) {
|
|
37
56
|
case 0x55d3: return 'CH343';
|
|
@@ -45,111 +64,124 @@ export default class SerialJS {
|
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
async select() {
|
|
48
|
-
if (!this._ok) return;
|
|
49
|
-
|
|
50
67
|
try {
|
|
51
68
|
await this.close();
|
|
69
|
+
this._port = null;
|
|
52
70
|
let ports = await navigator.serial.getPorts();
|
|
53
71
|
for (let p of ports) await p.forget();
|
|
54
|
-
await
|
|
72
|
+
await sleep(50);
|
|
55
73
|
await navigator.serial.requestPort();
|
|
56
|
-
await this.
|
|
74
|
+
await this._setLastPort();
|
|
57
75
|
} catch (e) {
|
|
58
|
-
this._port = null;
|
|
59
76
|
this._error(e);
|
|
60
77
|
}
|
|
61
|
-
this.
|
|
78
|
+
this.onselect(this.getName());
|
|
79
|
+
return this.selected();
|
|
62
80
|
}
|
|
63
81
|
|
|
64
|
-
async open(
|
|
65
|
-
if (
|
|
82
|
+
async open() {
|
|
83
|
+
if (this.cfg.reconnect) this.retry = true;
|
|
84
|
+
await this._open();
|
|
85
|
+
}
|
|
86
|
+
async _open() {
|
|
87
|
+
if (this.opened()) return;
|
|
66
88
|
|
|
67
89
|
try {
|
|
68
|
-
await this.
|
|
69
|
-
await this.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await this.
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
await this._close();
|
|
91
|
+
await this._setLastPort();
|
|
92
|
+
await this._port.open({ baudRate: this.cfg.baud });
|
|
93
|
+
this.writer = this._port.writable.getWriter();
|
|
94
|
+
this.reader = this._port.readable.getReader();
|
|
95
|
+
this._buffer.clear();
|
|
96
|
+
this._state = States.Open;
|
|
97
|
+
this._change(true);
|
|
98
|
+
|
|
99
|
+
while (this._state == States.Open) {
|
|
100
|
+
const { value, done } = await this.reader.read();
|
|
101
|
+
if (done) break;
|
|
102
|
+
if (value) {
|
|
103
|
+
if (this.onbin) this.onbin(value);
|
|
104
|
+
if (this.ontext) this.splitter.write(this._decoder.decode(value, { stream: true }));
|
|
105
|
+
}
|
|
81
106
|
}
|
|
82
107
|
} catch (e) {
|
|
83
108
|
this._error(e);
|
|
109
|
+
if (this.retry) setTimeout(() => this._open(), this.cfg.reconnect);
|
|
84
110
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async close() {
|
|
88
|
-
if (!this._ok) return;
|
|
89
|
-
if (!this._open) return;
|
|
90
111
|
|
|
91
|
-
this.
|
|
92
|
-
if (this.
|
|
112
|
+
if (this.reader) this.reader.releaseLock();
|
|
113
|
+
if (this.writer) this.writer.releaseLock();
|
|
114
|
+
this.reader = null;
|
|
115
|
+
this.writer = null;
|
|
116
|
+
this._state = States.Closed;
|
|
93
117
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
118
|
+
try {
|
|
119
|
+
await this._port.close();
|
|
120
|
+
this._change(false);
|
|
121
|
+
} catch (e) { }
|
|
99
122
|
}
|
|
100
123
|
|
|
101
|
-
async
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
async close() {
|
|
125
|
+
this.retry = false;
|
|
126
|
+
await this._close();
|
|
127
|
+
}
|
|
128
|
+
async _close() {
|
|
129
|
+
switch (this._state) {
|
|
130
|
+
case States.Closed: return;
|
|
131
|
+
case States.Open:
|
|
132
|
+
if (this.reader) await this.reader.cancel();
|
|
133
|
+
this._state = States.Closing;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
let i = 0;
|
|
138
|
+
while (this._state == States.Closing) {
|
|
139
|
+
await sleep(10);
|
|
140
|
+
if (++i > 200) {
|
|
141
|
+
this._error('Close timeout');
|
|
142
|
+
this._state = States.Closed;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
111
145
|
}
|
|
146
|
+
|
|
112
147
|
}
|
|
113
148
|
|
|
114
149
|
async sendText(text) {
|
|
115
150
|
await this.sendBin((new TextEncoder()).encode(text));
|
|
116
151
|
}
|
|
117
152
|
|
|
153
|
+
async sendBin(data) {
|
|
154
|
+
this._buffer.push(data);
|
|
155
|
+
this._send();
|
|
156
|
+
}
|
|
157
|
+
|
|
118
158
|
//#region private
|
|
119
159
|
_port = null;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this.
|
|
160
|
+
_state = States.Closed;
|
|
161
|
+
_buffer = new ShiftBuffer();
|
|
162
|
+
_decoder = new TextDecoder();
|
|
163
|
+
|
|
164
|
+
async _send() {
|
|
165
|
+
if (this._busy) return;
|
|
166
|
+
this._busy = true;
|
|
167
|
+
while (this._buffer.length) {
|
|
168
|
+
let d = this._buffer.shiftAll();
|
|
169
|
+
try {
|
|
170
|
+
if (this.writer) await this.writer.write(d);
|
|
171
|
+
} catch (e) { }
|
|
172
|
+
}
|
|
173
|
+
this._busy = false;
|
|
126
174
|
}
|
|
127
|
-
async
|
|
175
|
+
async _setLastPort() {
|
|
128
176
|
let ports = await navigator.serial.getPorts();
|
|
129
177
|
this._port = ports.length ? ports[0] : null;
|
|
130
178
|
}
|
|
131
|
-
async _readLoop() {
|
|
132
|
-
this._close = false;
|
|
133
|
-
const decoder = new TextDecoder();
|
|
134
|
-
|
|
135
|
-
while (this._port.readable && !this._close) {
|
|
136
|
-
this._reader = this._port.readable.getReader();
|
|
137
|
-
try {
|
|
138
|
-
while (true) {
|
|
139
|
-
const { done, value } = await this._reader.read();
|
|
140
|
-
if (done) return;
|
|
141
179
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
} finally {
|
|
150
|
-
this._reader.releaseLock();
|
|
151
|
-
this._reader = null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
180
|
+
_error(e) {
|
|
181
|
+
this.onerror('[SerialJS] ' + e);
|
|
182
|
+
}
|
|
183
|
+
_change(s) {
|
|
184
|
+
this.onchange(s);
|
|
185
|
+
s ? this.onopen() : this.onclose();
|
|
154
186
|
}
|
|
155
187
|
}
|
package/serial.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class
|
|
1
|
+
const t=t=>new Promise(e=>setTimeout(e,t));class e{ontext=null;constructor(t=/\r?\n/,e=!1){this.eol=t,this.skip=e}reset(){this._buf=""}write(t){if(!this.ontext)return;if(!this.eol)return void this.ontext(t);this._buf+=t;let e=this._buf.split(this.eol);1!=e.length&&(e[e.length-1].length?this._buf=e.pop():(this._buf="",e.pop()),this.skip&&(e.shift(),this.skip=!1),e.forEach(t=>this.ontext(t)))}_buf=""}class s{constructor(){this.chunks=[],this.length=0}clear(){this.chunks=[]}push(t){return this.chunks.push(t),this.length+=t.length,!0}shift(t){t>this.length&&(t=this.length);const e=new Uint8Array(t);let s=0;for(;t>0;){const i=this.chunks[0];t>=i.length?(e.set(i,s),s+=i.length,t-=i.length,this.chunks.shift(),this.length-=i.length):(e.set(i.subarray(0,t),s),this.chunks[0]=i.subarray(t),this.length-=t,t=0)}return e}shiftAll(){return this.shift(this.length)}}class i{onbin=null;ontext=null;onopen(){}onclose(){}onchange(t){}onselect(t){}onerror(t){}constructor(t={}){this.cfg={eol:/\r?\n/,baud:115200,reconnect:1e3,...t},this._setLastPort().then(()=>this.onselect(this.getName())),this.splitter=new e(this.cfg.eol),this.splitter.ontext=t=>this.ontext(t)}config(t={}){this.cfg={...this.cfg,...t},this.splitter.eol=this.cfg.eol}static supported(){return"serial"in navigator}opened(){return 1==this._state}selected(){return!!this._port}getName(){if(!this._port)return null;switch(this._port.getInfo().usbProductId){case 21971:return"CH343";case 30084:return"CH340S";case 29986:case 29987:return"CH340";case 21778:case 21795:case 21892:return"CH341";case 1026:case 1027:case 1028:case 1029:case 24577:case 1538:case 24592:return"FT232";case 38144:case 258:case 1281:case 32937:case 6e4:case 60001:case 60003:return"CP210x"}return"Unknown"}async select(){try{await this.close(),this._port=null;let e=await navigator.serial.getPorts();for(let t of e)await t.forget();await t(50),await navigator.serial.requestPort(),await this._setLastPort()}catch(t){this._error(t)}return this.onselect(this.getName()),this.selected()}async open(){this.cfg.reconnect&&(this.retry=!0),await this._open()}async _open(){if(!this.opened()){try{for(await this._close(),await this._setLastPort(),await this._port.open({baudRate:this.cfg.baud}),this.writer=this._port.writable.getWriter(),this.reader=this._port.readable.getReader(),this._buffer.clear(),this._state=1,this._change(!0);1==this._state;){const{value:t,done:e}=await this.reader.read();if(e)break;t&&(this.onbin&&this.onbin(t),this.ontext&&this.splitter.write(this._decoder.decode(t,{stream:!0})))}}catch(t){this._error(t),this.retry&&setTimeout(()=>this._open(),this.cfg.reconnect)}this.reader&&this.reader.releaseLock(),this.writer&&this.writer.releaseLock(),this.reader=null,this.writer=null,this._state=3;try{await this._port.close(),this._change(!1)}catch(t){}}}async close(){this.retry=!1,await this._close()}async _close(){switch(this._state){case 3:return;case 1:this.reader&&await this.reader.cancel(),this._state=2}let e=0;for(;2==this._state;)if(await t(10),++e>200){this._error("Close timeout"),this._state=3;break}}async sendText(t){await this.sendBin((new TextEncoder).encode(t))}async sendBin(t){this._buffer.push(t),this._send()}_port=null;_state=3;_buffer=new s;_decoder=new TextDecoder;async _send(){if(!this._busy){for(this._busy=!0;this._buffer.length;){let t=this._buffer.shiftAll();try{this.writer&&await this.writer.write(t)}catch(t){}}this._busy=!1}}async _setLastPort(){let t=await navigator.serial.getPorts();this._port=t.length?t[0]:null}_error(t){this.onerror("[SerialJS] "+t)}_change(t){this.onchange(t),t?this.onopen():this.onclose()}}export{i as default};
|
package/test/index.html
CHANGED
package/test/script.js
CHANGED
|
@@ -3,18 +3,16 @@ import SerialJS from "https://gyverlibs.github.io/Serial.js/serial.min.js";
|
|
|
3
3
|
let i = 0;
|
|
4
4
|
let ser = new SerialJS();
|
|
5
5
|
|
|
6
|
+
// control
|
|
6
7
|
select_b.onclick = () => ser.select();
|
|
7
8
|
open_b.onclick = () => ser.open();
|
|
8
9
|
close_b.onclick = () => ser.close();
|
|
9
10
|
send_b.onclick = () => ser.sendText('Hello ' + i++);
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
-
// ser.reader.setEOL(/\r?\n/); // default
|
|
13
|
-
ser.reader.ontext = t => console.log(t);
|
|
14
|
-
|
|
15
|
-
// read raw
|
|
12
|
+
// read
|
|
16
13
|
// ser.onbin = b => console.log(b);
|
|
17
|
-
|
|
14
|
+
ser.ontext = t => console.log(t);
|
|
15
|
+
// ser.online = t => console.log(t);
|
|
18
16
|
|
|
19
17
|
// state
|
|
20
18
|
ser.onopen = () => {
|
|
@@ -26,6 +24,6 @@ ser.onclose = () => {
|
|
|
26
24
|
ser.onerror = e => {
|
|
27
25
|
console.log(e);
|
|
28
26
|
}
|
|
29
|
-
ser.
|
|
30
|
-
console.log('port change',
|
|
27
|
+
ser.onselect = (port) => {
|
|
28
|
+
console.log('port change', port);
|
|
31
29
|
}
|
package/test_dev/index.html
CHANGED
package/test_dev/script.js
CHANGED
|
@@ -3,19 +3,16 @@ import SerialJS from "../serial";
|
|
|
3
3
|
let i = 0;
|
|
4
4
|
let ser = new SerialJS();
|
|
5
5
|
|
|
6
|
+
// control
|
|
6
7
|
select_b.onclick = () => ser.select();
|
|
7
8
|
open_b.onclick = () => ser.open();
|
|
8
9
|
close_b.onclick = () => ser.close();
|
|
9
10
|
send_b.onclick = () => ser.sendText('Hello ' + i++);
|
|
10
11
|
|
|
11
|
-
//
|
|
12
|
-
// ser.reader.setEOL(/\r?\n/); // default
|
|
13
|
-
// ser.reader.ontext = t => console.log(t);
|
|
14
|
-
|
|
15
|
-
// read raw
|
|
12
|
+
// read
|
|
16
13
|
// ser.onbin = b => console.log(b);
|
|
17
|
-
|
|
18
|
-
ser.online = t => console.log(t);
|
|
14
|
+
ser.ontext = t => console.log(t);
|
|
15
|
+
// ser.online = t => console.log(t);
|
|
19
16
|
|
|
20
17
|
// state
|
|
21
18
|
ser.onopen = () => {
|
|
@@ -27,6 +24,6 @@ ser.onclose = () => {
|
|
|
27
24
|
ser.onerror = e => {
|
|
28
25
|
console.log(e);
|
|
29
26
|
}
|
|
30
|
-
ser.
|
|
31
|
-
console.log('port change',
|
|
27
|
+
ser.onselect = (port) => {
|
|
28
|
+
console.log('port change', port);
|
|
32
29
|
}
|