1
1
import {
2
- filter , takeUntil
2
+ distinctUntilChanged , filter , takeUntil
3
3
} from 'rxjs/operators' ;
4
+
4
5
import Daemon from './daemon' ;
5
6
6
7
/**
7
8
* WARNING: the WebSerialDaemon with support for the Web Serial API is still in an alpha version.
8
9
* At the moment it doesn't implement all the features available in the Chrome App Deamon
9
10
* Use at your own risk.
10
11
*
11
- * The `uploader ` parameter in the constructor is the component which is
12
+ * The `channel ` parameter in the constructor is the component which is
12
13
* used to interact with the Web Serial API.
13
- * It must provide a method `upload`.
14
+ *
15
+ * It must provide a `postMessage` method, similarly to the object created with `chrome.runtime.connect` in
16
+ * the `chrome-app-daemon.js` module, which is used to send messages to interact with the Web Serial API.
14
17
*/
15
18
export default class WebSerialDaemon extends Daemon {
16
- constructor ( boardsUrl , uploader ) {
19
+ constructor ( boardsUrl , channel ) {
17
20
super ( boardsUrl ) ;
21
+
18
22
this . port = null ;
19
- this . agentFound . next ( true ) ;
20
23
this . channelOpenStatus . next ( true ) ;
21
- this . uploader = uploader ;
24
+ this . channel = channel ; // channel is injected from the client app
25
+ this . connectedPorts = [ ] ;
26
+
27
+ this . init ( ) ;
28
+ }
29
+
30
+ init ( ) {
31
+ this . agentFound
32
+ . pipe ( distinctUntilChanged ( ) )
33
+ . subscribe ( found => {
34
+ if ( ! found ) {
35
+ // Set channelOpen false for the first time
36
+ if ( this . channelOpen . getValue ( ) === null ) {
37
+ this . channelOpen . next ( false ) ;
38
+ }
22
39
23
- this . _populateSupportedBoards ( ) ;
40
+ this . connectToChannel ( ) ;
41
+ }
42
+ else {
43
+ this . openChannel ( ( ) => this . channel . postMessage ( {
44
+ command : 'listPorts'
45
+ } ) ) ;
46
+ }
47
+ } ) ;
24
48
}
25
49
26
- _populateSupportedBoards ( ) {
27
- const supportedBoards = this . uploader . getSupportedBoards ( ) ;
28
- this . appMessages . next ( { supportedBoards } ) ;
50
+ connectToChannel ( ) {
51
+ this . channel . onMessage ( message => {
52
+ if ( message . version ) {
53
+ this . agentInfo = message . version ;
54
+ this . agentFound . next ( true ) ;
55
+ this . channelOpen . next ( true ) ;
56
+ }
57
+ else {
58
+ this . appMessages . next ( message ) ;
59
+ }
60
+ } ) ;
61
+ this . channel . onDisconnect ( ( ) => {
62
+ this . channelOpen . next ( false ) ;
63
+ this . agentFound . next ( false ) ;
64
+ } ) ;
29
65
}
30
66
31
- // eslint-disable-next-line class-methods-use-this
32
- closeSerialMonitor ( ) {
33
- // TODO: it's a NO OP at the moment
67
+ _appConnect ( ) {
68
+ this . channel . onMessage ( message => {
69
+ if ( message . version ) {
70
+ this . agentInfo = {
71
+ version : message . version ,
72
+ os : 'ChromeOS'
73
+ } ;
74
+ this . agentFound . next ( true ) ;
75
+ this . channelOpen . next ( true ) ;
76
+ }
77
+ else {
78
+ this . appMessages . next ( message ) ;
79
+ }
80
+ } ) ;
81
+ this . channel . onDisconnect ( ( ) => {
82
+ this . channelOpen . next ( false ) ;
83
+ this . agentFound . next ( false ) ;
84
+ } ) ;
34
85
}
35
86
36
87
handleAppMessage ( message ) {
37
88
if ( message . ports ) {
89
+ this . handleListMessage ( message ) ;
90
+ }
91
+ else if ( message . supportedBoards ) {
92
+ this . supportedBoards . next ( message . supportedBoards ) ;
93
+ }
94
+ if ( message . serialData ) {
95
+ this . serialMonitorMessages . next ( message . serialData ) ;
96
+ }
97
+
98
+ if ( message . uploadStatus ) {
99
+ this . handleUploadMessage ( message ) ;
100
+ }
101
+
102
+ if ( message . err ) {
103
+ this . uploading . next ( { status : this . UPLOAD_ERROR , err : message . Err } ) ;
104
+ }
105
+ }
106
+
107
+ handleUploadMessage ( message ) {
108
+ if ( this . uploading . getValue ( ) . status !== this . UPLOAD_IN_PROGRESS ) {
109
+ return ;
110
+ }
111
+ switch ( message . uploadStatus ) {
112
+ case 'message' :
113
+ this . uploading . next ( {
114
+ status : this . UPLOAD_IN_PROGRESS ,
115
+ msg : message . message ,
116
+ operation : message . operation ,
117
+ port : message . port
118
+ } ) ;
119
+ break ;
120
+ case 'error' :
121
+ this . uploading . next ( { status : this . UPLOAD_ERROR , err : message . message } ) ;
122
+ break ;
123
+ case 'success' :
124
+ this . uploading . next (
125
+ {
126
+ status : this . UPLOAD_DONE ,
127
+ msg : message . message ,
128
+ operation : message . operation ,
129
+ port : message . port
130
+ }
131
+ ) ;
132
+ break ;
133
+
134
+ default :
135
+ this . uploading . next ( { status : this . UPLOAD_IN_PROGRESS } ) ;
136
+ }
137
+ }
138
+
139
+ handleListMessage ( message ) {
140
+ const lastDevices = this . devicesList . getValue ( ) ;
141
+ if ( ! Daemon . devicesListAreEquals ( lastDevices . serial , message . ports ) ) {
38
142
this . devicesList . next ( {
39
- serial : message . ports ,
143
+ serial : message . ports
144
+ . map ( port => ( {
145
+ Name : port . name ,
146
+ SerialNumber : port . serialNumber ,
147
+ IsOpen : port . isOpen ,
148
+ VendorID : port . vendorId ,
149
+ ProductID : port . productId
150
+ } ) ) ,
40
151
network : [ ]
41
152
} ) ;
42
- // this.handleListMessage(message);
43
- }
44
-
45
- if ( message . supportedBoards ) {
46
- this . supportedBoards . next ( message . supportedBoards ) ;
47
153
}
48
154
}
49
155
50
156
/**
51
157
* Send 'close' command to all the available serial ports
52
158
*/
53
- // eslint-disable-next-line class-methods-use-this
54
159
closeAllPorts ( ) {
55
- console . log ( 'should be closing serial ports here' ) ;
160
+ const devices = this . devicesList . getValue ( ) . serial ;
161
+ if ( Array . isArray ( devices ) ) {
162
+ devices . forEach ( device => {
163
+ this . channel . postMessage ( {
164
+ command : 'closePort' ,
165
+ data : {
166
+ name : device . Name
167
+ }
168
+ } ) ;
169
+ } ) ;
170
+ }
56
171
}
57
172
58
173
/**
59
174
* Request serial port open
60
175
* @param {string } port the port name
61
176
*/
62
- openSerialMonitor ( port ) {
177
+ openSerialMonitor ( port , baudrate ) {
63
178
if ( this . serialMonitorOpened . getValue ( ) ) {
64
179
return ;
65
180
}
66
- const serialPort = this . devicesList . getValue ( ) . serial [ 0 ] ; // .find(p => p.Name === port);
181
+ const serialPort = this . devicesList . getValue ( ) . serial . find ( p => p . Name === port ) ;
67
182
if ( ! serialPort ) {
68
183
return this . serialMonitorError . next ( `Can't find port ${ port } ` ) ;
69
184
}
@@ -77,30 +192,90 @@ export default class WebSerialDaemon extends Daemon {
77
192
this . serialMonitorError . next ( `Failed to open serial ${ port } ` ) ;
78
193
}
79
194
} ) ;
80
-
195
+ this . channel . postMessage ( {
196
+ command : 'openPort' ,
197
+ data : {
198
+ name : port ,
199
+ baudrate
200
+ }
201
+ } ) ;
81
202
}
82
203
83
- cdcReset ( { fqbn } ) {
84
- return this . uploader . cdcReset ( { fqbn } )
85
- . then ( ( ) => {
86
- this . uploading . next ( { status : this . CDC_RESET_DONE , msg : 'Touch operation succeeded' } ) ;
87
- } )
88
- . catch ( error => {
89
- this . notifyUploadError ( error . message ) ;
204
+ closeSerialMonitor ( port ) {
205
+ if ( ! this . serialMonitorOpened . getValue ( ) ) {
206
+ return ;
207
+ }
208
+ const serialPort = this . devicesList . getValue ( ) . serial . find ( p => p . Name === port ) ;
209
+ if ( ! serialPort ) {
210
+ return this . serialMonitorError . next ( `Can't find port ${ port } ` ) ;
211
+ }
212
+ this . appMessages
213
+ . pipe ( takeUntil ( this . serialMonitorOpened . pipe ( filter ( open => ! open ) ) ) )
214
+ . subscribe ( message => {
215
+ if ( message . portCloseStatus === 'success' ) {
216
+ this . serialMonitorOpened . next ( false ) ;
217
+ }
218
+ if ( message . portCloseStatus === 'error' ) {
219
+ this . serialMonitorError . next ( `Failed to close serial ${ port } ` ) ;
220
+ }
90
221
} ) ;
222
+ this . channel . postMessage ( {
223
+ command : 'closePort' ,
224
+ data : {
225
+ name : port
226
+ }
227
+ } ) ;
228
+ }
229
+
230
+ cdcReset ( { fqbn, port } ) {
231
+ this . uploading . next ( { status : this . UPLOAD_IN_PROGRESS , msg : 'CDC reset started' } ) ;
232
+ this . channel . postMessage ( {
233
+ command : 'cdcReset' ,
234
+ data : {
235
+ fqbn,
236
+ port
237
+ }
238
+ } ) ;
239
+ }
240
+
241
+ connectToSerialDevice ( { fqbn } ) {
242
+ this . uploading . next ( { status : this . UPLOAD_IN_PROGRESS , msg : 'Board selection started' } ) ;
243
+ this . channel . postMessage ( {
244
+ command : 'connectToSerial' ,
245
+ data : {
246
+ fqbn
247
+ }
248
+ } ) ;
91
249
}
92
250
93
251
/**
94
252
* @param {object } uploadPayload
95
253
* TODO: document param's shape
96
254
*/
97
- _upload ( uploadPayload ) {
98
- return this . uploader . upload ( uploadPayload )
99
- . then ( ( ) => {
100
- this . uploading . next ( { status : this . UPLOAD_DONE , msg : 'Sketch uploaded' } ) ;
101
- } )
102
- . catch ( error => {
103
- this . notifyUploadError ( error . message ) ;
255
+ _upload ( uploadPayload , uploadCommandInfo ) {
256
+ const {
257
+ board, port, commandline, data, pid, vid
258
+ } = uploadPayload ;
259
+ const extrafiles = uploadCommandInfo && uploadCommandInfo . files && Array . isArray ( uploadCommandInfo . files ) ? uploadCommandInfo . files : [ ] ;
260
+ try {
261
+ window . oauth . getAccessToken ( ) . then ( token => {
262
+ this . channel . postMessage ( {
263
+ command : 'upload' ,
264
+ data : {
265
+ board,
266
+ port,
267
+ commandline,
268
+ data,
269
+ token : token . token ,
270
+ extrafiles,
271
+ pid,
272
+ vid
273
+ }
274
+ } ) ;
104
275
} ) ;
276
+ }
277
+ catch ( err ) {
278
+ this . uploading . next ( { status : this . UPLOAD_ERROR , err : 'you need to be logged in on a Create site to upload by Chrome App' } ) ;
279
+ }
105
280
}
106
281
}
0 commit comments