diff --git a/src/boardConfiguration.js b/src/boardConfiguration.js index 1317ca34..0967f7da 100644 --- a/src/boardConfiguration.js +++ b/src/boardConfiguration.js @@ -18,12 +18,11 @@ * */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, timer } from 'rxjs'; import { takeUntil, filter, first } from 'rxjs/operators'; import { provisioningSketch } from './sketches/provisioning.ino'; const BAUDRATE = 9600; - export default class BoardConfiguration { constructor(daemon) { this.CONFIGURE_IN_PROGRESS = 'CONFIGURE_IN_PROGRESS'; @@ -34,12 +33,22 @@ export default class BoardConfiguration { this.daemon = daemon; this.serialMonitorContent = ''; this.configuring = new BehaviorSubject({ status: this.CONFIGURE_NOPE }); - + this.configureDone = this.configuring.pipe(filter(configure => configure.status === this.CONFIGURE_DONE)); + this.configureInProgress = this.configuring.pipe(filter(configure => configure.status === this.CONFIGURE_IN_PROGRESS)); + this.configureError = this.configuring.pipe(filter(configure => configure.status === this.CONFIGURE_ERROR)); this.daemon.serialMonitorMessages.subscribe(message => { this.serialMonitorContent += message; }); } + initConfig() { + this.configuring.next({ status: this.CONFIGURE_IN_PROGRESS, msg: 'Starting board configuration...' }); + } + + notifyError(msg) { + this.configuring.next({ status: this.CONFIGURE_ERROR, msg: msg, err: msg}); + } + /** * Returns the correct Provisioning sketch after adding fqbn * @param {string} fqbn @@ -55,7 +64,6 @@ export default class BoardConfiguration { let partialMessage = ''; const gettingCsr = new Promise((resolve, reject) => { const parseCsrQuestions = message => { - // TODO: store partial messages partialMessage += message; if (partialMessage.indexOf('No ECCX08 present') !== -1) { @@ -72,19 +80,11 @@ export default class BoardConfiguration { } if (partialMessage.indexOf('Would you like to generate a new private key and CSR (y/N):') !== -1) { partialMessage = ''; - const serialData = { - com_name: board.port, - data: 'y\n' - }; - this.daemon.writeSerial(board.port, serialData); + this.daemon.writeSerial(board.port, 'y\n'); } if (partialMessage.indexOf('Your ECCX08 is unlocked, would you like to lock it (y/N):') !== -1) { partialMessage = ''; - const serialData = { - com_name: board.port, - data: 'y\n' - }; - this.daemon.writeSerial(board.port, serialData); + this.daemon.writeSerial(board.port, 'y\n'); } const begin = partialMessage.indexOf('-----BEGIN CERTIFICATE REQUEST-----'); @@ -125,36 +125,13 @@ export default class BoardConfiguration { (notAfter.getUTCFullYear() - notBefore.getUTCFullYear()) + '\n' + compressedCert.serial + '\n' + compressedCert.signature + '\n'; - - const serialData = { - com_name: board.port, - data: answers - }; - this.daemon.writeSerial(board.port, serialData); + this.daemon.writeSerial(board.port, answers); }); return storing.finally(() => this.serialMessagesSubscription.unsubscribe()); } - /** - * Uploads the sketch and performs action in order to configure the board for Arduino Cloud - * @param {Object} compiledSketch the Object containing the provisioning sketch, ready to be compiled - * @param {Object} board contains the board data - * @param {function} createDeviceCb used to create the device associated to the user - */ - configure(compiledSketch, board, createDeviceCb) { - this.configuring.next({ status: this.CONFIGURE_IN_PROGRESS, msg: 'Starting board configuration' }); - if (!this.daemon.channelOpen.getValue()) { - const errorMessage = `Couldn't configure board at port ${board.port} because we there is no open channel to the Arduino Create Plugin.`; - this.configuring.next({ - status: this.CONFIGURE_ERROR, - msg: errorMessage, - err: 'cannot find plugin' - }); - return; - } - this.serialMonitorContent = ''; - + uploadSketch(compiledSketch, board) { const uploadTarget = { board: board.fqbn, port: board.port, @@ -172,12 +149,34 @@ export default class BoardConfiguration { signature: board.upload[0].options.signature, extrafiles: [], options: { - wait_for_upload_port: (board.upload[0].options.wait_for_upload_port === true || board.upload[0].options.wait_for_upload_port === 'true'), // eslint-disable-line camelcase - use_1200bps_touch: (board.upload[0].options.use_1200bps_touch === true || board.upload[0].options.use_1200bps_touch === 'true'), // eslint-disable-line camelcase - params_verbose: '-v' // eslint-disable-line camelcase + wait_for_upload_port: (board.upload[0].options.wait_for_upload_port === true || board.upload[0].options.wait_for_upload_port === 'true'), + use_1200bps_touch: (board.upload[0].options.use_1200bps_touch === true || board.upload[0].options.use_1200bps_touch === 'true'), + params_verbose: '-v' } }; + this.daemon.upload(uploadTarget, uploadData); + } + + /** + * Uploads the sketch and performs action in order to configure the board for Arduino Cloud + * @param {Object} compiledSketch the Object containing the provisioning sketch, ready to be compiled + * @param {Object} board contains the board data + * @param {function} createDeviceCb used to create the device associated to the user + */ + configure(compiledSketch, board, createDeviceCb) { + this.configuring.next({ status: this.CONFIGURE_IN_PROGRESS, msg: 'Uploading provisioning sketch...' }); + if (!this.daemon.channelOpen.getValue()) { + const errorMessage = `Couldn't configure board at port ${board.port} because we there is no open channel to the Arduino Create Plugin.`; + this.configuring.next({ + status: this.CONFIGURE_ERROR, + msg: errorMessage, + err: 'cannot find plugin' + }); + return; + } + this.serialMonitorContent = ''; + // check the uploading status: if (this.daemon.uploading.getValue().status === this.daemon.UPLOAD_IN_PROGRESS) { // if there is an upload in course, notify observers; @@ -190,32 +189,53 @@ export default class BoardConfiguration { } this.daemon.uploadingDone.pipe(first()).subscribe(() => { + this.configuring.next({ + status: this.CONFIGURE_IN_PROGRESS, + msg: 'Provisioning sketch uploaded successfully. Opening serial monitor...' + }); this.daemon.serialMonitorOpened.pipe(takeUntil(this.daemon.serialMonitorOpened.pipe(filter(open => open)))) .subscribe(() => { + this.configuring.next({ + status: this.CONFIGURE_IN_PROGRESS, + msg: 'Serial monitor opened. Generating CSR...' + }); this.getCsr(board) - .then(csr => createDeviceCb(csr)) - .then(data => this.storeCertificate(data.compressed)) + .then(csr => { + this.configuring.next({ + status: this.CONFIGURE_IN_PROGRESS, + msg: 'CSR generated. Creating device...' + }); + return createDeviceCb(csr) + }) + .then(data => { + this.configuring.next({ + status: this.CONFIGURE_IN_PROGRESS, + msg: 'Device created. Storing certificate...' + }); + return this.storeCertificate(data.compressed, board); + }) .then(() => this.configuring.next({ status: this.CONFIGURE_DONE })) .catch(reason => this.configuring.next({ status: this.CONFIGURE_ERROR, - msg: `Couldn't configure board at port ${board.port}. Configuration failed with error: ${reason}`, + msg: `Couldn't configure board at port ${board.port}. Configuration failed with error: ${reason.message}`, err: reason.toString() })) .finally(() => this.daemon.closeSerialMonitor(board.port, BAUDRATE)); }, error => { this.configuring.next({ status: this.CONFIGURE_ERROR, - msg: `Couldn't configure board at port ${board.port}. Configuration failed with error: ${error}`, + msg: `Couldn't configure board at port ${board.port}. Configuration failed with error: ${error.message}`, err: error.toString() }); }); - this.daemon.openSerialMonitor(board.port, BAUDRATE); + this.daemon.openSerialMonitor(board.port, BAUDRATE); }); this.daemon.uploadingError.pipe(first()).subscribe(upload => { this.configuring.next({ status: this.CONFIGURE_ERROR, err: `Couldn't configure board at port ${board.port}. Upload failed with error: ${upload.err}` }); }); - this.daemon.upload(uploadTarget, uploadData); + this.daemon.initUpload(); + this.uploadSketch(compiledSketch, board); } } diff --git a/src/daemon.js b/src/daemon.js index c5452e90..3ca42392 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -113,4 +113,8 @@ export default class Daemon { } throw new Error('Stop Upload not supported on Chrome OS'); } + + initUpload() { + this.uploading.next({ status: this.UPLOAD_NOPE }); + } } diff --git a/src/index.js b/src/index.js index a5790ff6..1b95a951 100644 --- a/src/index.js +++ b/src/index.js @@ -25,5 +25,5 @@ import BoardConfiguration from './boardConfiguration'; const Daemon = window.navigator.userAgent.indexOf(' CrOS ') !== -1 ? ChromeOsDaemon : SocketDaemon; -export { BoardConfiguration }; +export { Daemon, BoardConfiguration }; export default Daemon; diff --git a/src/socket-daemon.js b/src/socket-daemon.js index d8fd8c20..977342b9 100644 --- a/src/socket-daemon.js +++ b/src/socket-daemon.js @@ -23,7 +23,7 @@ import semVerCompare from 'semver-compare'; import { detect } from 'detect-browser'; import { BehaviorSubject, timer } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter, takeUntil, first } from 'rxjs/operators'; import Daemon from './daemon'; @@ -31,6 +31,7 @@ import Daemon from './daemon'; const MIN_VERSION = '1.1.71'; const browser = detect(); const POLLING_INTERVAL = 2500; +const UPLOAD_DONE_TIMER = 5000; const PROTOCOL = { HTTP: 'http', @@ -312,7 +313,7 @@ export default class SocketDaemon extends Daemon { } const serialPort = this.devicesList.getValue().serial.find(p => p.Name === port); if (!serialPort) { - return this.serialMonitorOpened.error(new Error(`Can't find port ${port}`)); + return this.serialMonitorOpened.error(new Error(`Can't find board at ${port}`)); } this.appMessages .pipe(takeUntil(this.serialMonitorOpened.pipe(filter(open => !open)))) @@ -321,7 +322,7 @@ export default class SocketDaemon extends Daemon { this.serialMonitorOpened.next(false); } if (message.Cmd === 'CloseFail') { - this.serialMonitorOpened.error(new Error(`Failed to close serial ${port}`)); + this.serialMonitorOpened.error(new Error(`Failed to close serial monitor at ${port}`)); } }); this.socket.emit('command', `close ${port}`); @@ -332,8 +333,8 @@ export default class SocketDaemon extends Daemon { return; } if (message.Flash === 'Ok' && message.ProgrammerStatus === 'Done') { - this.uploading.next({ status: this.UPLOAD_DONE, msg: message.Flash }); - return; + // After the upload is completed the port goes down for a while, so we have to wait a few seconds + return timer(UPLOAD_DONE_TIMER).subscribe(() => this.uploading.next({ status: this.UPLOAD_DONE, msg: message.Flash })); } switch (message.ProgrammerStatus) { case 'Starting': @@ -439,17 +440,22 @@ export default class SocketDaemon extends Daemon { payload.extrafiles.push({ filename: data.files[i].name, hex: data.files[i].data }); } - fetch(`${this.pluginURL}/upload`, { - method: 'POST', - headers: { - 'Content-Type': 'text/plain; charset=utf-8' - }, - body: JSON.stringify(payload) - }) - .catch(error => { - this.uploading.next({ status: this.UPLOAD_ERROR, err: error }); - }); - } + this.serialMonitorOpened.pipe(filter(open => !open)) + .pipe(first()) + .subscribe(() => { + + fetch(`${this.pluginURL}/upload`, { + method: 'POST', + headers: { + 'Content-Type': 'text/plain; charset=utf-8' + }, + body: JSON.stringify(payload) + }) + .catch(error => { + this.uploading.next({ status: this.UPLOAD_ERROR, err: error }); + }); + }) + } /** * Download tool