Skip to content

Commit 9ce0cf0

Browse files
authored
Implement support for RP2040 on Chromebook PWA (#546)
General improvements in the Web Serial API protocol. Final goal is to provide a better support for a broader range of boards, e.g. the Arduino RP2040 Connect, on Chromebooks. * Improved communication with the Web Serial APIs via asynchronous messaging between modules * Implemented closeAllPorts in Web Serial API Daemon * Some minor refactoring. * Updated version in package.json, updated change log in readme file.
1 parent 1a9a3cb commit 9ce0cf0

File tree

6 files changed

+224
-57
lines changed

6 files changed

+224
-57
lines changed

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v14.16.0

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ JS module providing discovery of the [Arduino Create Agent](https://github.com/a
66

77

88
## Changelog
9-
[2.8.0] - 2022-03-21
9+
[2.9.0-beta.1] - 2022-05-17
10+
11+
### Added
12+
- Improved support (still in Beta) for Chrome's Web Serial API on ChromeOS. Other operating systems should not be affected.
13+
- Added support for "Arduino RP2040 Connect" board
14+
- Simplified the communication with the Web Serial API via a messaging system which simulates
15+
the [postMessage](https://developer.chrome.com/docs/extensions/reference/runtime/#method-Port-postMessage) function available in the Chrome App Daemon (see `chrome-app-daemon.js`).
1016

17+
[2.8.0] - 2022-03-21
1118
### Added
1219
- Added support (still in Beta) for Chrome's Web Serial API on ChromeOS.
1320
Other operating systems should not be affected.

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "arduino-create-agent-js-client",
3-
"version": "2.8.0",
3+
"version": "2.9.0-beta.1",
44
"description": "JS module providing discovery of the Arduino Create Plugin and communication with it",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/daemon.js

-16
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export default class Daemon {
3232
this.BOARDS_URL = boardsUrl;
3333
this.UPLOAD_NOPE = 'UPLOAD_NOPE';
3434
this.UPLOAD_DONE = 'UPLOAD_DONE';
35-
this.CDC_RESET_DONE = 'CDC_RESET_DONE';
3635
this.UPLOAD_ERROR = 'UPLOAD_ERROR';
3736
this.UPLOAD_IN_PROGRESS = 'UPLOAD_IN_PROGRESS';
3837

@@ -56,15 +55,6 @@ export default class Daemon {
5655
this.uploadingDone = this.uploading.pipe(filter(upload => upload.status === this.UPLOAD_DONE))
5756
.pipe(first())
5857
.pipe(takeUntil(this.uploading.pipe(filter(upload => upload.status === this.UPLOAD_ERROR))));
59-
this.cdcResetDone = this.uploading.pipe(
60-
filter(upload => upload.status === this.CDC_RESET_DONE),
61-
first(),
62-
takeUntil(
63-
this.uploading.pipe(
64-
filter(upload => upload.status === this.UPLOAD_ERROR || upload.status === this.UPLOAD_DONE))
65-
)
66-
);
67-
6858
this.uploadingError = this.uploading.pipe(filter(upload => upload.status === this.UPLOAD_ERROR))
6959
.pipe(first())
7060
.pipe(takeUntil(this.uploadingDone));
@@ -118,12 +108,6 @@ export default class Daemon {
118108
});
119109
}
120110

121-
// eslint-disable-next-line class-methods-use-this
122-
cdcReset() {
123-
// It's a no-op for daemons different from web serial deamon
124-
return Promise.resolve(true);
125-
}
126-
127111
/**
128112
* Upload a sketch to serial target
129113
* Fetch commandline from boards API for serial upload

src/web-serial-daemon.js

+213-38
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,184 @@
11
import {
2-
filter, takeUntil
2+
distinctUntilChanged, filter, takeUntil
33
} from 'rxjs/operators';
4+
45
import Daemon from './daemon';
56

67
/**
78
* WARNING: the WebSerialDaemon with support for the Web Serial API is still in an alpha version.
89
* At the moment it doesn't implement all the features available in the Chrome App Deamon
910
* Use at your own risk.
1011
*
11-
* The `uploader` parameter in the constructor is the component which is
12+
* The `channel` parameter in the constructor is the component which is
1213
* 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.
1417
*/
1518
export default class WebSerialDaemon extends Daemon {
16-
constructor(boardsUrl, uploader) {
19+
constructor(boardsUrl, channel) {
1720
super(boardsUrl);
21+
1822
this.port = null;
19-
this.agentFound.next(true);
2023
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+
}
2239

23-
this._populateSupportedBoards();
40+
this.connectToChannel();
41+
}
42+
else {
43+
this.openChannel(() => this.channel.postMessage({
44+
command: 'listPorts'
45+
}));
46+
}
47+
});
2448
}
2549

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+
});
2965
}
3066

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+
});
3485
}
3586

3687
handleAppMessage(message) {
3788
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)) {
38142
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+
})),
40151
network: []
41152
});
42-
// this.handleListMessage(message);
43-
}
44-
45-
if (message.supportedBoards) {
46-
this.supportedBoards.next(message.supportedBoards);
47153
}
48154
}
49155

50156
/**
51157
* Send 'close' command to all the available serial ports
52158
*/
53-
// eslint-disable-next-line class-methods-use-this
54159
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+
}
56171
}
57172

58173
/**
59174
* Request serial port open
60175
* @param {string} port the port name
61176
*/
62-
openSerialMonitor(port) {
177+
openSerialMonitor(port, baudrate) {
63178
if (this.serialMonitorOpened.getValue()) {
64179
return;
65180
}
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);
67182
if (!serialPort) {
68183
return this.serialMonitorError.next(`Can't find port ${port}`);
69184
}
@@ -77,30 +192,90 @@ export default class WebSerialDaemon extends Daemon {
77192
this.serialMonitorError.next(`Failed to open serial ${port}`);
78193
}
79194
});
80-
195+
this.channel.postMessage({
196+
command: 'openPort',
197+
data: {
198+
name: port,
199+
baudrate
200+
}
201+
});
81202
}
82203

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+
}
90221
});
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+
});
91249
}
92250

93251
/**
94252
* @param {object} uploadPayload
95253
* TODO: document param's shape
96254
*/
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+
});
104275
});
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+
}
105280
}
106281
}

0 commit comments

Comments
 (0)