@alexgyver/serial 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,20 +1,26 @@
1
1
  {
2
2
  "name": "@alexgyver/serial",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Web Serial API wrapper",
5
5
  "main": "./serial.js",
6
6
  "module": "./serial.js",
7
7
  "types": "./serial.js",
8
8
  "scripts": {
9
- "build": "webpack --config ./webpack.config.js"
9
+ "build": "webpack --config ./webpack.config.js",
10
+ "dev": "webpack serve --config ./webpack.dev.config.js & webpack --config ./webpack.dev.config.js"
10
11
  },
11
12
  "repository": {
12
13
  "type": "git",
13
14
  "url": "git+https://github.com/GyverLibs/Serial.js.git"
14
15
  },
15
16
  "devDependencies": {
16
- "webpack": "^5.98.0",
17
- "webpack-cli": "^6.0.1"
17
+ "webpack": "^5.103.0",
18
+ "webpack-cli": "^6.0.1",
19
+ "webpack-dev-server": "^5.2.2",
20
+ "html-webpack-plugin": "^5.6.5"
21
+ },
22
+ "dependencies": {
23
+ "@alexgyver/utils": "^1.2.2"
18
24
  },
19
25
  "author": "AlexGyver <alex@alexgyver.ru>",
20
26
  "license": "MIT"
package/serial.js CHANGED
@@ -1,7 +1,11 @@
1
+ import { StreamSplitter } from "@alexgyver/utils";
2
+
1
3
  export default class SerialJS {
2
4
  //#region handlers
3
5
  onbin = null;
4
6
  ontext = null;
7
+ online = null;
8
+
5
9
  async onopen() { }
6
10
  async onclose() { }
7
11
  async onerror(e) { }
@@ -9,6 +13,9 @@ export default class SerialJS {
9
13
 
10
14
  //#region constructor
11
15
  constructor() {
16
+ this.splitter = new StreamSplitter();
17
+ this.splitter.ontext = (t) => this.online(t);
18
+
12
19
  this._ok = 'serial' in navigator;
13
20
  if (this._ok) this._update().then(() => this.onportchange(this.selected()));
14
21
  else this._error('Browser is not supported');
@@ -18,9 +25,11 @@ export default class SerialJS {
18
25
  opened() {
19
26
  return this._open;
20
27
  }
28
+
21
29
  selected() {
22
30
  return !!this._port;
23
31
  }
32
+
24
33
  getName() {
25
34
  if (!this._port) return 'None';
26
35
 
@@ -42,6 +51,7 @@ export default class SerialJS {
42
51
  await this.close();
43
52
  let ports = await navigator.serial.getPorts();
44
53
  for (let p of ports) await p.forget();
54
+ await new Promise(r => setTimeout(r, 50));
45
55
  await navigator.serial.requestPort();
46
56
  await this._update();
47
57
  } catch (e) {
@@ -50,6 +60,7 @@ export default class SerialJS {
50
60
  }
51
61
  this.onportchange(this.selected());
52
62
  }
63
+
53
64
  async open(baud = 115200) {
54
65
  if (!this._ok) return;
55
66
 
@@ -61,7 +72,7 @@ export default class SerialJS {
61
72
  await this._port.open({ baudRate: baud });
62
73
  this._open = true;
63
74
  this.onopen();
64
- this.reader.reset();
75
+ // this.reader.reset();
65
76
  await this._readLoop();
66
77
  } finally {
67
78
  await this._port.close();
@@ -72,6 +83,7 @@ export default class SerialJS {
72
83
  this._error(e);
73
84
  }
74
85
  }
86
+
75
87
  async close() {
76
88
  if (!this._ok) return;
77
89
  if (!this._open) return;
@@ -79,7 +91,11 @@ export default class SerialJS {
79
91
  this._close = true;
80
92
  if (this._reader) await this._reader.cancel();
81
93
 
82
- while (this._open) await new Promise(r => setTimeout(r, 10));
94
+ const t0 = performance.now();
95
+ while (this._open) {
96
+ if (performance.now() - t0 > 2000) this._error("Close timeout");
97
+ await new Promise(r => setTimeout(r, 10));
98
+ }
83
99
  }
84
100
 
85
101
  async sendBin(data) {
@@ -94,12 +110,11 @@ export default class SerialJS {
94
110
  this._error(e);
95
111
  }
96
112
  }
113
+
97
114
  async sendText(text) {
98
115
  await this.sendBin((new TextEncoder()).encode(text));
99
116
  }
100
117
 
101
- reader = new SplitReader();
102
-
103
118
  //#region private
104
119
  _port = null;
105
120
  _open = false;
@@ -115,15 +130,21 @@ export default class SerialJS {
115
130
  }
116
131
  async _readLoop() {
117
132
  this._close = false;
118
- if (this._port.readable) this._reader = this._port.readable.getReader();
133
+ const decoder = new TextDecoder();
134
+
119
135
  while (this._port.readable && !this._close) {
136
+ this._reader = this._port.readable.getReader();
120
137
  try {
121
138
  while (true) {
122
- let read = await this._reader.read();
123
- if (read.done) break;
124
- if (this.onbin) this.onbin(read.value);
125
- if (this.ontext) this.ontext(new TextDecoder().decode(read.value));
126
- if (this.reader.ontext) this.reader._write(new TextDecoder().decode(read.value));
139
+ const { done, value } = await this._reader.read();
140
+ if (done) return;
141
+
142
+ if (this.onbin) this.onbin(value);
143
+ if (this.ontext || this.online) {
144
+ const text = decoder.decode(value);
145
+ if (this.ontext) this.ontext(text);
146
+ if (this.online) this.splitter.write(text);
147
+ }
127
148
  }
128
149
  } finally {
129
150
  this._reader.releaseLock();
@@ -131,34 +152,4 @@ export default class SerialJS {
131
152
  }
132
153
  }
133
154
  }
134
- }
135
-
136
- class SplitReader {
137
- ontext = null;
138
-
139
- setEOL(eol) {
140
- this._eol = eol;
141
- this.reset();
142
- }
143
-
144
- reset() {
145
- this._buf = "";
146
- this._skip = true;
147
- }
148
-
149
- _write(str) {
150
- this._buf += str;
151
- let t = this._buf.split(this._eol);
152
- if (t.length == 1) return;
153
-
154
- if (t[t.length - 1].length) this._buf = t.pop();
155
- else this._buf = "", t.pop();
156
-
157
- if (this._skip) t.shift(), this._skip = false;
158
- t.map(this.ontext);
159
- }
160
-
161
- _buf = "";
162
- _skip = true;
163
- _eol = /\r?\n/;
164
155
  }
package/serial.min.js CHANGED
@@ -1 +1 @@
1
- var e={d:(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{A:()=>s});class s{onbin=null;ontext=null;async onopen(){}async onclose(){}async onerror(e){}async onportchange(e){}constructor(){this._ok="serial"in navigator,this._ok?this._update().then((()=>this.onportchange(this.selected()))):this._error("Browser is not supported")}opened(){return this._open}selected(){return!!this._port}getName(){if(!this._port)return"None";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(){if(this._ok){try{await this.close();let e=await navigator.serial.getPorts();for(let t of e)await t.forget();await navigator.serial.requestPort(),await this._update()}catch(e){this._port=null,this._error(e)}this.onportchange(this.selected())}}async open(e=115200){if(this._ok)try{if(await this.close(),await this._update(),!this.selected())throw"No port";try{await this._port.open({baudRate:e}),this._open=!0,this.onopen(),this.reader.reset(),await this._readLoop()}finally{await this._port.close(),this._open=!1,this.onclose()}}catch(e){this._error(e)}}async close(){if(this._ok&&this._open)for(this._close=!0,this._reader&&await this._reader.cancel();this._open;)await new Promise((e=>setTimeout(e,10)))}async sendBin(e){if(this._ok&&this.opened())try{let t=this._port.writable.getWriter();await t.write(e),t.releaseLock()}catch(e){this._error(e)}}async sendText(e){await this.sendBin((new TextEncoder).encode(e))}reader=new r;_port=null;_open=!1;_close=!1;_reader=null;_error(e){this.onerror("[SerialJS] "+e)}async _update(){let e=await navigator.serial.getPorts();this._port=e.length?e[0]:null}async _readLoop(){for(this._close=!1,this._port.readable&&(this._reader=this._port.readable.getReader());this._port.readable&&!this._close;)try{for(;;){let e=await this._reader.read();if(e.done)break;this.onbin&&this.onbin(e.value),this.ontext&&this.ontext((new TextDecoder).decode(e.value)),this.reader.ontext&&this.reader._write((new TextDecoder).decode(e.value))}}finally{this._reader.releaseLock(),this._reader=null}}}class r{ontext=null;setEOL(e){this._eol=e,this.reset()}reset(){this._buf="",this._skip=!0}_write(e){this._buf+=e;let t=this._buf.split(this._eol);1!=t.length&&(t[t.length-1].length?this._buf=t.pop():(this._buf="",t.pop()),this._skip&&(t.shift(),this._skip=!1),t.map(this.ontext))}_buf="";_skip=!0;_eol=/\r?\n/}var a=t.A;export{a as default};
1
+ class t{ontext=null;constructor(t=/\r?\n/,e=!1){this._eol=t,this._skip=e}reset(){this._buf=""}write(t){if(!this.ontext)return;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 e{onbin=null;ontext=null;online=null;async onopen(){}async onclose(){}async onerror(t){}async onportchange(t){}constructor(){this.splitter=new t,this.splitter.ontext=t=>this.online(t),this._ok="serial"in navigator,this._ok?this._update().then(()=>this.onportchange(this.selected())):this._error("Browser is not supported")}opened(){return this._open}selected(){return!!this._port}getName(){if(!this._port)return"None";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(){if(this._ok){try{await this.close();let t=await navigator.serial.getPorts();for(let e of t)await e.forget();await new Promise(t=>setTimeout(t,50)),await navigator.serial.requestPort(),await this._update()}catch(t){this._port=null,this._error(t)}this.onportchange(this.selected())}}async open(t=115200){if(this._ok)try{if(await this.close(),await this._update(),!this.selected())throw"No port";try{await this._port.open({baudRate:t}),this._open=!0,this.onopen(),await this._readLoop()}finally{await this._port.close(),this._open=!1,this.onclose()}}catch(t){this._error(t)}}async close(){if(!this._ok)return;if(!this._open)return;this._close=!0,this._reader&&await this._reader.cancel();const t=performance.now();for(;this._open;)performance.now()-t>2e3&&this._error("Close timeout"),await new Promise(t=>setTimeout(t,10))}async sendBin(t){if(this._ok&&this.opened())try{let e=this._port.writable.getWriter();await e.write(t),e.releaseLock()}catch(t){this._error(t)}}async sendText(t){await this.sendBin((new TextEncoder).encode(t))}_port=null;_open=!1;_close=!1;_reader=null;_error(t){this.onerror("[SerialJS] "+t)}async _update(){let t=await navigator.serial.getPorts();this._port=t.length?t[0]:null}async _readLoop(){this._close=!1;const t=new TextDecoder;for(;this._port.readable&&!this._close;){this._reader=this._port.readable.getReader();try{for(;;){const{done:e,value:s}=await this._reader.read();if(e)return;if(this.onbin&&this.onbin(s),this.ontext||this.online){const e=t.decode(s);this.ontext&&this.ontext(e),this.online&&this.splitter.write(e)}}}finally{this._reader.releaseLock(),this._reader=null}}}}export{e as default};
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
7
+ <script src="script.js" type="module" defer="defer"></script>
8
+ </head>
9
+
10
+ <body>
11
+ <button id="select_b">select</button>
12
+ <button id="open_b">open</button>
13
+ <button id="close_b">close</button>
14
+ <button id="send_b">send</button>
15
+ </body>
16
+
17
+ </html>
@@ -0,0 +1,32 @@
1
+ import SerialJS from "../serial";
2
+
3
+ let i = 0;
4
+ let ser = new SerialJS();
5
+
6
+ select_b.onclick = () => ser.select();
7
+ open_b.onclick = () => ser.open();
8
+ close_b.onclick = () => ser.close();
9
+ send_b.onclick = () => ser.sendText('Hello ' + i++);
10
+
11
+ // split reader
12
+ // ser.reader.setEOL(/\r?\n/); // default
13
+ // ser.reader.ontext = t => console.log(t);
14
+
15
+ // read raw
16
+ // ser.onbin = b => console.log(b);
17
+ // ser.ontext = t => console.log(t);
18
+ ser.online = t => console.log(t);
19
+
20
+ // state
21
+ ser.onopen = () => {
22
+ console.log('Opened', ser.getName());
23
+ }
24
+ ser.onclose = () => {
25
+ console.log('Closed');
26
+ }
27
+ ser.onerror = e => {
28
+ console.log(e);
29
+ }
30
+ ser.onportchange = () => {
31
+ console.log('port change', ser.selected(), ser.getName());
32
+ }
@@ -0,0 +1,39 @@
1
+ const path = require('path');
2
+ const PACKAGE = require('./package.json');
3
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
4
+
5
+ module.exports = {
6
+ entry: {
7
+ index: './test_dev/script.js',
8
+ },
9
+
10
+ output: {
11
+ filename: 'script.js',
12
+ path: path.resolve(__dirname, 'dev'),
13
+ clean: true,
14
+ },
15
+
16
+ plugins: [
17
+ new HtmlWebpackPlugin({
18
+ template: `./test_dev/index.html`,
19
+ filename: `index.html`,
20
+ inject: true,
21
+ minify: false,
22
+ version: PACKAGE.version,
23
+ title: PACKAGE.title,
24
+ }),
25
+ ],
26
+
27
+ devServer: {
28
+ watchFiles: ['test_dev/*.html'],
29
+ static: path.resolve(__dirname, './dev'),
30
+ hot: true,
31
+ open: true,
32
+ },
33
+
34
+ watchOptions: {
35
+ poll: 1000,
36
+ },
37
+
38
+ mode: 'development',
39
+ };