ircbloq-link
Version:
Porvide local hardware function to ircbloq
203 lines (173 loc) • 7.95 kB
JavaScript
const fs = require('fs');
const {spawn, spawnSync} = require('child_process');
const path = require('path');
const ansi = require('ansi-string');
const yaml = require('js-yaml');
const AVRDUDE_STDOUT_GREEN_START = /Reading \||Writing \|/g;
const AVRDUDE_STDOUT_GREEN_END = /%/g;
const AVRDUDE_STDOUT_WHITE = /avrdude done/g;
const AVRDUDE_STDOUT_RED_START = /can't open device|programmer is not responding/g;
const AVRDUDE_STDERR_RED_IGNORE = /Executable segment sizes/g;
class Arduino {
constructor (peripheralPath, config, userDataPath, toolsPath, sendstd) {
this._peripheralPath = peripheralPath;
this._config = config;
this._userDataPath = userDataPath;
this._projectfilePath = path.join(userDataPath, 'arduino/project');
this._arduinoPath = path.join(toolsPath, 'Arduino');
this._sendstd = sendstd;
this._arduinoCliPath = path.join(this._arduinoPath, 'arduino-cli');
this._codefilePath = path.join(this._projectfilePath, 'project.ino');
this._buildPath = path.join(this._projectfilePath, 'build');
this.initArduinoCli();
}
initArduinoCli () {
// try to init the arduino cli config.
spawnSync(this._arduinoCliPath, ['config', 'init']);
// if arduino cli config haven be init, set it to link arduino path.
const buf = spawnSync(this._arduinoCliPath, ['config', 'dump']);
const stdout = yaml.load(buf.stdout.toString());
if (stdout.directories.data !== this._arduinoPath) {
this._sendstd(`${ansi.yellow_dark}arduino cli config has not been initialized yet.\n`);
this._sendstd(`${ansi.green_dark}set the path to ${this._arduinoPath}.\n`);
spawnSync(this._arduinoCliPath, ['config', 'set', 'directories.data', this._arduinoPath]);
spawnSync(this._arduinoCliPath, ['config', 'set', 'directories.downloads',
path.join(this._arduinoPath, 'staging')]);
spawnSync(this._arduinoCliPath, ['config', 'set', 'directories.user', this._arduinoPath]);
}
}
build (code, library = []) {
return new Promise((resolve, reject) => {
if (!fs.existsSync(this._projectfilePath)) {
fs.mkdirSync(this._projectfilePath, {recursive: true});
}
try {
fs.writeFileSync(this._codefilePath, code);
} catch (err) {
return reject(err);
}
const args = [
'compile',
'--upload',
'--fqbn', this._config.fqbn,
`-p${this._peripheralPath}`,
'--libraries', path.join(this._arduinoPath, 'libraries'),
'--warnings=none',
'--verbose',
this._projectfilePath
];
// if extensions library to not empty
library.forEach(lib => {
if (fs.existsSync(lib)) {
args.splice(7, 0, '--libraries', lib);
}
});
const arduinoBuilder = spawn(this._arduinoCliPath, args);
arduinoBuilder.stderr.on('data', buf => {
const data = buf.toString();
if (data.search(AVRDUDE_STDERR_RED_IGNORE) !== -1) { // eslint-disable-line no-negated-condition
this._sendstd(data);
} else {
this._sendstd(ansi.red + data);
}
});
arduinoBuilder.stdout.on('data', buf => {
const data = buf.toString();
let ansiColor = null;
if (data.search(/Sketch uses|Global variables/g) === -1) {
ansiColor = ansi.clear;
} else {
ansiColor = ansi.green_dark;
}
this._sendstd(ansiColor + data);
});
arduinoBuilder.on('exit', outCode => {
this._sendstd(`${ansi.clear}\r\n`); // End ansi color setting
switch (outCode) {
case 0:
if (this._config.fqbn === 'arduino:avr:leonardo' || this._config.fqbn === 'SparkFun:avr:makeymakey') {
// Waiting for leonardo usb rerecognize.
const wait = ms => new Promise(relv => setTimeout(relv, ms));
wait(1000).then(() => resolve('Success'));
} else {
return resolve('Success');
}
break;
case 1:
return reject('Build failed');
case 2:
return reject('Sketch not found');
case 3:
return reject('Invalid (argument for) commandline optiond');
case 4:
return reject('Preference passed to --get-pref does not exist');
default:
return reject('Unknown error');
}
});
});
}
_insertStr (soure, start, newStr) {
return soure.slice(0, start) + newStr + soure.slice(start);
}
async flash (firmwarePath = null) {
const args = [
'upload',
'--fqbn', this._config.fqbn,
'--verbose',
'--verify',
`-p${this._peripheralPath}`
];
if (firmwarePath) {
args.push('--input-file', firmwarePath, firmwarePath);
} else {
args.push(this._projectfilePath);
}
return new Promise((resolve, reject) => {
const avrdude = spawn(this._arduinoCliPath, args);
avrdude.stderr.on('data', buf => {
let data = buf.toString();
// todo: Because the feacture of avrdude sends STD information intermittently.
// There should be a better way to handle these mesaage.
if (data.search(AVRDUDE_STDOUT_GREEN_START) !== -1) { // eslint-disable-line eqeqeq
data = this._insertStr(data, data.search(AVRDUDE_STDOUT_GREEN_START), ansi.green_dark);
}
if (data.search(AVRDUDE_STDOUT_GREEN_END) !== -1) { // eslint-disable-line eqeqeq
data = this._insertStr(data, data.search(AVRDUDE_STDOUT_GREEN_END) + 1, ansi.clear);
}
if (data.search(AVRDUDE_STDOUT_WHITE) !== -1) { // eslint-disable-line eqeqeq
data = this._insertStr(data, data.search(AVRDUDE_STDOUT_WHITE), ansi.clear);
}
if (data.search(AVRDUDE_STDOUT_RED_START) !== -1) { // eslint-disable-line eqeqeq
data = this._insertStr(data, data.search(AVRDUDE_STDOUT_RED_START), ansi.red);
}
this._sendstd(data);
});
avrdude.stdout.on('data', buf => {
// It seems that avrdude didn't use stdout.
const data = buf.toString();
this._sendstd(data);
});
avrdude.on('exit', code => {
switch (code) {
case 0:
if (this._config.fqbn === 'arduino:avr:leonardo' || this._config.fqbn === 'SparkFun:avr:makeymakey') {
// Waiting for leonardo usb rerecognize.
const wait = ms => new Promise(relv => setTimeout(relv, ms));
wait(1000).then(() => resolve('Success'));
} else {
return resolve('Success');
}
break;
case 1:
return reject('avrdude failed to flash');
}
});
});
}
flashRealtimeFirmware () {
const firmwarePath = path.join(this._arduinoPath, '../../firmwares/arduino', this._config.firmware);
return this.flash(firmwarePath);
}
}
module.exports = Arduino;