Skip to content

Commit eef1944

Browse files
author
Alberto Iannaccone
committed
configure board
1 parent f7cce8c commit eef1944

File tree

3 files changed

+177
-150
lines changed

3 files changed

+177
-150
lines changed

src/boardConfiguration.js

Lines changed: 175 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -27,148 +27,189 @@
2727
* the GNU General Public License.
2828
*/
2929

30+
import { BehaviorSubject } from 'rxjs';
31+
import { takeUntil, filter, first } from 'rxjs/operators';
3032
import { provisioningSketch } from './sketches/provisioning.ino';
31-
import { perform, upload, addSerialCallback } from './readMessages';
3233

33-
/**
34-
* Returns the correct Provisioning sketch after adding fqbn
35-
* @param {string} fqbn
36-
* @return {Object} the Object containing the provisioning sketch, ready to be compiled
37-
*/
38-
const getProvisioningSketch = (fqbn) => {
39-
provisioningSketch.fqbn = fqbn;
40-
provisioningSketch.sketch.ino.data = window.btoa(window.unescape(encodeURIComponent(provisioningSketch.sketch.ino.content)));
41-
return provisioningSketch;
42-
};
43-
44-
const getCsr = (board) => {
45-
let partialCsr = '';
46-
let gettingCsr = new Promise((resolve, reject) => {
47-
const parseCsrQuestions = message => {
48-
// TODO: store partial messages
49-
50-
if (message.indexOf('No ECCX08 present') !== -1) {
51-
return reject(new Error('We couldn\'t find the Crypto Chip'));
52-
}
53-
if (message.indexOf('Locking ECCX08 configuration failed!') !== -1 || message.indexOf('Writing ECCX08 configuration failed') !== -1) {
54-
return reject(new Error('already configured'));
55-
}
56-
if (message.indexOf('Error generating CSR!') !== -1) {
57-
return reject(new Error('We were not able to generate the CSR.'));
58-
}
59-
if (message.indexOf('Error') !== -1) {
60-
return reject(new Error(message));
61-
}
62-
if (message.indexOf('Would you like to generate a new private key and CSR (y/N):') !== -1) {
63-
const serialData = {
64-
com_name: board.port,
65-
data: 'y\n'
66-
};
67-
perform('req_serial_monitor_write', serialData);
68-
}
69-
if (message.indexOf('Your ECCX08 is unlocked, would you like to lock it (y/N):') !== -1) {
70-
const serialData = {
71-
com_name: board.port,
72-
data: 'y\n'
73-
};
74-
perform('req_serial_monitor_write', serialData);
75-
}
76-
partialCsr += message;
77-
const begin = partialCsr.indexOf('-----BEGIN CERTIFICATE REQUEST-----');
78-
const end = partialCsr.indexOf('-----END CERTIFICATE REQUEST-----');
79-
if (begin !== -1 && end !== -1) {
80-
const csr = partialCsr.slice(begin, end + 33); // Add 33 to end to include '-----END CERTIFICATE REQUEST-----'
81-
return resolve(csr);
82-
}
83-
};
34+
const BAUDRATE = 9600;
8435

85-
addSerialCallback(parseCsrQuestions);
86-
})
87-
.finally(() => {
88-
gettingCsr = false;
36+
export default class BoardConfiguration {
37+
constructor(daemon) {
38+
this.CONFIGURE_IN_PROGRESS = 'CONFIGURE_IN_PROGRESS';
39+
this.CONFIGURE_NOPE = 'CONFIGURE_NOPE';
40+
this.CONFIGURE_DONE = 'CONFIGURE_DONE';
41+
this.CONFIGURE_ERROR = 'CONFIGURE_ERROR';
42+
43+
this.daemon = daemon;
44+
this.serialMonitorContent = '';
45+
this.configuring = new BehaviorSubject({ status: this.CONFIGURE_NOPE });
46+
47+
this.daemon.serialMonitorMessages.subscribe(message => {
48+
this.serialMonitorContent += message;
8949
});
90-
return gettingCsr;
91-
};
50+
}
9251

93-
const storeCertificate = (compressedCert, board) => {
94-
const storing = new Promise((resolve, reject) => {
52+
/**
53+
* Returns the correct Provisioning sketch after adding fqbn
54+
* @param {string} fqbn
55+
* @return {Object} the Object containing the provisioning sketch, ready to be compiled
56+
*/
57+
static getProvisioningSketch(fqbn) {
58+
provisioningSketch.fqbn = fqbn;
59+
provisioningSketch.sketch.ino.data = window.btoa(window.unescape(encodeURIComponent(provisioningSketch.sketch.ino.content)));
60+
return provisioningSketch;
61+
}
9562

96-
const parseCsr = (message) => {
97-
if (message.indexOf('Compressed cert') !== -1) {
98-
return resolve();
99-
}
100-
if (message.indexOf('Error') !== -1) {
101-
return reject(new Error(message));
102-
}
63+
getCsr(board) {
64+
let partialMessage = '';
65+
const gettingCsr = new Promise((resolve, reject) => {
66+
const parseCsrQuestions = message => {
67+
// TODO: store partial messages
68+
partialMessage += message;
69+
70+
if (partialMessage.indexOf('No ECCX08 present') !== -1) {
71+
return reject(new Error('We couldn\'t find the Crypto Chip'));
72+
}
73+
if (partialMessage.indexOf('Locking ECCX08 configuration failed!') !== -1 || partialMessage.indexOf('Writing ECCX08 configuration failed') !== -1) {
74+
return reject(new Error('already configured'));
75+
}
76+
if (partialMessage.indexOf('Error generating CSR!') !== -1) {
77+
return reject(new Error('We were not able to generate the CSR.'));
78+
}
79+
if (partialMessage.indexOf('Error') !== -1) {
80+
return reject(new Error(message));
81+
}
82+
if (partialMessage.indexOf('Would you like to generate a new private key and CSR (y/N):') !== -1) {
83+
partialMessage = '';
84+
const serialData = {
85+
com_name: board.port,
86+
data: 'y\n'
87+
};
88+
this.daemon.writeSerial(board.port, serialData);
89+
}
90+
if (partialMessage.indexOf('Your ECCX08 is unlocked, would you like to lock it (y/N):') !== -1) {
91+
partialMessage = '';
92+
const serialData = {
93+
com_name: board.port,
94+
data: 'y\n'
95+
};
96+
this.daemon.writeSerial(board.port, serialData);
97+
}
98+
99+
const begin = partialMessage.indexOf('-----BEGIN CERTIFICATE REQUEST-----');
100+
const end = partialMessage.indexOf('-----END CERTIFICATE REQUEST-----');
101+
if (begin !== -1 && end !== -1) {
102+
const csr = partialMessage.slice(begin, end + 33); // Add 33 to end to include '-----END CERTIFICATE REQUEST-----'
103+
return resolve(csr);
104+
}
105+
};
106+
107+
this.serialMessagesSubscription = this.daemon.serialMonitorMessages.subscribe(parseCsrQuestions);
108+
});
109+
return gettingCsr.finally(() => this.serialMessagesSubscription.unsubscribe());
110+
}
111+
112+
storeCertificate(compressedCert, board) {
113+
let partialMessage = '';
114+
const storing = new Promise((resolve, reject) => {
115+
const parseCsr = (message) => {
116+
partialMessage += message;
117+
if (partialMessage.indexOf('Compressed cert') !== -1) {
118+
return resolve();
119+
}
120+
if (partialMessage.indexOf('Error') !== -1) {
121+
return reject(new Error(message));
122+
}
123+
};
124+
this.serialMessagesSubscription = this.daemon.serialMonitorMessages.subscribe(parseCsr);
125+
126+
const notBefore = new Date(compressedCert.not_before);
127+
const notAfter = new Date(compressedCert.not_after);
128+
// eslint-disable-next-line prefer-template
129+
const answers = board.id + '\n' +
130+
notBefore.getUTCFullYear() + '\n' +
131+
(notBefore.getUTCMonth() + 1) + '\n' +
132+
notBefore.getUTCDate() + '\n' +
133+
notBefore.getUTCHours() + '\n' +
134+
(notAfter.getUTCFullYear() - notBefore.getUTCFullYear()) + '\n' +
135+
compressedCert.serial + '\n' +
136+
compressedCert.signature + '\n';
137+
138+
const serialData = {
139+
com_name: board.port,
140+
data: answers
141+
};
142+
this.daemon.writeSerial(board.port, serialData);
143+
});
144+
145+
return storing.finally(() => this.serialMessagesSubscription.unsubscribe());
146+
}
147+
148+
/**
149+
* Uploads the sketch and performs action in order to configure the board for Arduino Cloud
150+
* @param {Object} compiledSketch the Object containing the provisioning sketch, ready to be compiled
151+
* @param {Object} board contains the board data
152+
* @param {function} createDeviceCb used to create the device associated to the user
153+
*/
154+
configure(compiledSketch, board, createDeviceCb) {
155+
this.configuring.next({ status: this.CONFIGURE_IN_PROGRESS, msg: 'Starting board configuration' });
156+
if (!this.daemon.channelOpen.getValue()) {
157+
const errorMessage = `Couldn't configure board at port ${board.port} because we there is no open channel to the Arduino Create Plugin.`;
158+
this.configuring.next({ status: this.CONFIGURE_ERROR, err: errorMessage });
159+
return;
160+
}
161+
this.serialMonitorContent = '';
162+
163+
const uploadTarget = {
164+
board: board.fqbn,
165+
port: board.port,
166+
network: false
103167
};
104-
addSerialCallback(parseCsr);
105-
const notBefore = new Date(compressedCert.not_before);
106-
const notAfter = new Date(compressedCert.not_after);
107-
// eslint-disable-next-line prefer-template
108-
const answers = board.id + '\n' +
109-
notBefore.getUTCFullYear() + '\n' +
110-
(notBefore.getUTCMonth() + 1) + '\n' +
111-
notBefore.getUTCDate() + '\n' +
112-
notBefore.getUTCHours() + '\n' +
113-
(notAfter.getUTCFullYear() - notBefore.getUTCFullYear()) + '\n' +
114-
compressedCert.serial + '\n' +
115-
compressedCert.signature + '\n';
116-
117-
const serialData = {
118-
com_name: board.port,
119-
data: answers
168+
169+
const file = {
170+
name: compiledSketch.name + board.upload[0].ext,
171+
data: compiledSketch.hex
120172
};
121-
perform('req_serial_monitor_write', serialData);
122-
});
123173

124-
return storing;
125-
};
174+
const uploadData = {
175+
files: [file],
176+
commandline: board.upload[0].commandline,
177+
signature: board.upload[0].options.signature,
178+
extrafiles: [],
179+
options: {
180+
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
181+
use_1200bps_touch: (board.upload[0].options.use_1200bps_touch === true || board.upload[0].options.use_1200bps_touch === 'true'), // eslint-disable-line camelcase
182+
params_verbose: '-v' // eslint-disable-line camelcase
183+
}
184+
};
126185

127-
/**
128-
* Uploads the sketch and performs action in order to configure the board for Arduino Cloud
129-
* @param {Object} compiledSketch the Object containing the provisioning sketch, ready to be compiled
130-
* @param {Object} board contains the board data
131-
* @param {function} createDeviceCb used to create the device associated to the user
132-
*/
133-
const configure = (compiledSketch, board, createDeviceCb) => {
134-
const serialData = {
135-
com_name: board.port,
136-
baudrate: 9600
137-
};
138-
139-
const uploadTarget = {
140-
board: board.fqbn,
141-
port: board.port,
142-
network: false
143-
};
144-
145-
const file = {
146-
name: compiledSketch.name + board.upload[0].ext,
147-
data: compiledSketch.hex
148-
};
149-
150-
const uploadData = {
151-
files: [file],
152-
commandline: board.upload[0].commandline,
153-
signature: board.upload[0].options.signature,
154-
extrafiles: [],
155-
options: {
156-
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
157-
use_1200bps_touch: (board.upload[0].options.use_1200bps_touch === true || board.upload[0].options.use_1200bps_touch === 'true'), // eslint-disable-line camelcase
158-
params_verbose: '-v' // eslint-disable-line camelcase
186+
// check the uploading status:
187+
if (this.daemon.uploading.getValue().status === this.daemon.UPLOAD_IN_PROGRESS) {
188+
// if there is an upload in course, notify observers;
189+
this.configuring.next({ status: this.CONFIGURE_ERROR, err: `Couldn't configure board at port ${board.port}. There is already an upload in progress.` });
190+
return;
159191
}
160-
};
161-
162-
upload(uploadTarget, uploadData)
163-
.then(() => perform('req_serial_monitor_open', serialData))
164-
.then(() => getCsr(board))
165-
.then(csr => createDeviceCb(csr))
166-
.then(data => storeCertificate(data.compressed))
167-
.catch(reason => new Error(`Couldn't configure board: ${reason}`))
168-
.finally(() => perform('req_serial_monitor_close', serialData));
169-
};
170-
171-
export {
172-
getProvisioningSketch,
173-
configure
174-
};
192+
193+
this.daemon.uploadingDone.pipe(first()).subscribe(() => {
194+
this.daemon.serialMonitorOpened.pipe(takeUntil(this.daemon.serialMonitorOpened.pipe(filter(open => open))))
195+
.subscribe(() => {
196+
this.getCsr(board)
197+
.then(csr => createDeviceCb(csr))
198+
.then(data => this.storeCertificate(data.compressed))
199+
.then(() => this.configuring.next({ status: this.CONFIGURE_DONE }))
200+
.catch(reason => this.configuring.next({
201+
status: this.CONFIGURE_ERROR,
202+
err: `Couldn't configure board at port ${board.port}. Configuration failed with error: ${reason}`
203+
}))
204+
.finally(() => this.daemon.closeSerialMonitor(board.port, BAUDRATE));
205+
});
206+
this.daemon.openSerialMonitor(board.port, BAUDRATE);
207+
});
208+
209+
this.daemon.uploadingError.pipe(first()).subscribe(upload => {
210+
this.configuring.next({ status: this.CONFIGURE_ERROR, err: `Couldn't configure board at port ${board.port}. Upload failed with error: ${upload.err}` });
211+
});
212+
213+
this.daemon.upload(uploadTarget, uploadData);
214+
}
215+
}

src/socket-daemon.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -243,19 +243,6 @@ export default class SocketDaemon extends Daemon {
243243
}).then(() => Promise.reject()); // We reject the promise because the daemon will be restarted, we need to continue looking for the port
244244
}
245245

246-
/**
247-
* Uploads the sketch and performs action in order to configure the board for Arduino Cloud
248-
* @param {Object} compiledSketch the Object containing the provisioning sketch, already compiled
249-
* @param {Object} board contains the board data
250-
* @param {function} createDeviceCb used to create the device associated to the user
251-
*/
252-
configureBoard(compiledSketch, board, createDeviceCb) {
253-
if (!this.wsConnect.getValue()) {
254-
return Promise.reject(new Error('We were not able to generate the CSR.'));
255-
}
256-
return this.configure(compiledSketch, board, createDeviceCb);
257-
}
258-
259246
/**
260247
* Pauses the plugin
261248
* @return {Promise}

test/app.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class App extends React.Component {
108108
});
109109

110110
daemon.uploading.subscribe(upload => {
111-
this.setState({ uploadStatus: upload.status });
111+
this.setState({ uploadStatus: upload.status, uploadError: upload.err });
112112
console.log(upload);
113113
});
114114

@@ -259,8 +259,7 @@ class App extends React.Component {
259259
<div className="section">
260260
<h2>Upload a sample sketch on a MKR1000 at /dev/ttyACM0</h2>
261261
<button onClick={ handleUpload } disabled={ this.state.uploadStatus === daemon.UPLOAD_IN_PROGRESS }>Upload Sketch</button><br/>
262-
<div>Upload status: <span className={ uploadClass }> { this.state.uploadStatus }</span></div>
263-
<div>{ this.state.uploadError }</div>
262+
<div>Upload status: <span className={ uploadClass }> { this.state.uploadStatus }</span> <span>{ this.state.uploadError }</span></div>
264263
</div>
265264

266265
{ daemon.downloading ? <div className="section">

0 commit comments

Comments
 (0)