Skip to content

Commit afeef7d

Browse files
authored
Merge pull request #791 from particle-iot/feature/tachyon-flash-config-blob
Feature/tachyon flash config blob
2 parents f9e469f + 9034d4f commit afeef7d

File tree

10 files changed

+126
-31
lines changed

10 files changed

+126
-31
lines changed

assets/qdl/darwin/arm64/qdl

-112 Bytes
Binary file not shown.

assets/qdl/darwin/x64/liblzma.5.dylib

0 Bytes
Binary file not shown.

assets/qdl/darwin/x64/qdl

-120 Bytes
Binary file not shown.

assets/qdl/linux/arm64/qdl

0 Bytes
Binary file not shown.

assets/qdl/linux/x64/qdl

0 Bytes
Binary file not shown.

assets/qdl/win32/x64/qdl.exe

-544 Bytes
Binary file not shown.

src/cmd/esim.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ module.exports = class ESimCommands extends CLICommandBase {
169169
return;
170170
}
171171

172-
// Start qlril-app through ADB for Tachyon
172+
// Start particle-ril through ADB for Tachyon
173173
const qlrilStep = await this._initializeQlril();
174174
provisionOutputLogs.push(qlrilStep);
175175
if (qlrilStep?.status === 'failed') {
@@ -275,7 +275,7 @@ module.exports = class ESimCommands extends CLICommandBase {
275275

276276
async doEnable(iccid) {
277277
try {
278-
const { stdout } = await execa('adb', ['shell', 'qlril-app', 'enable', iccid]);
278+
const { stdout } = await execa('adb', ['shell', 'particle-ril', 'enable', iccid]);
279279
if (stdout.includes(`ICCID currently active: ${iccid}`)) {
280280
console.log(`ICCID ${iccid} enabled successfully`);
281281
}
@@ -312,7 +312,7 @@ module.exports = class ESimCommands extends CLICommandBase {
312312

313313
async doList() {
314314
try {
315-
const { stdout } = await execa('adb', ['shell', 'qlril-app', 'listProfiles']);
315+
const { stdout } = await execa('adb', ['shell', 'particle-ril', 'listProfiles']);
316316

317317
const iccids = stdout
318318
.trim()
@@ -521,7 +521,7 @@ module.exports = class ESimCommands extends CLICommandBase {
521521
}
522522

523523
logAndPush('Initalizing qlril app on Tachyon through adb');
524-
this.adbProcess = execa('adb', ['shell', 'qlril-app', '--port', '/dev/ttyGS2']);
524+
this.adbProcess = execa('adb', ['shell', 'particle-ril', '--port', '/dev/ttyGS2']);
525525

526526
try {
527527
await new Promise((resolve, reject) => {

src/cmd/flash.js

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ module.exports = class FlashCommand extends CLICommandBase {
7171
}
7272

7373
//returns true if successful or false if failed
74-
async flashTachyon({ files, output }) {
74+
async flashTachyon({ files, skipReset, output, verbose=true }) {
7575
let zipFile;
7676
let includeDir = '';
7777
let updateFolder = '';
@@ -101,28 +101,65 @@ module.exports = class FlashCommand extends CLICommandBase {
101101
filesToProgram = files;
102102
}
103103

104-
if (output && !fs.existsSync(output)) {
105-
fs.mkdirSync(output);
106-
}
107-
const outputLog = path.join(output ? output : process.cwd(), `tachyon_flash_${Date.now()}.log`);
104+
const outputLog = await this._getOutputLogPath(output);
105+
108106
try {
109-
this.ui.write(`Starting download. This may take several minutes. See logs at: ${outputLog}${os.EOL}`);
107+
if (verbose) {
108+
this.ui.write(`${os.EOL}Starting download. See logs at: ${outputLog}${os.EOL}`);
109+
}
110110
const qdl = new QdlFlasher({
111111
files: filesToProgram,
112112
includeDir,
113113
updateFolder,
114114
zip: zipFile,
115115
ui: this.ui,
116-
outputLogFile: outputLog
116+
outputLogFile: outputLog,
117+
skipReset,
118+
currTask: 'OS'
117119
});
118120
await qdl.run();
119-
fs.appendFileSync(outputLog, 'Download complete.');
120-
return true;
121+
fs.appendFileSync(outputLog, `OS Download complete.${os.EOL}`);
121122
} catch (error) {
122-
this.ui.write('Download failed');
123123
fs.appendFileSync(outputLog, 'Download failed with error: ' + error.message);
124-
return false;
124+
throw new Error('Download failed with error: ' + error.message);
125+
}
126+
}
127+
128+
async flashTachyonXml({ files, output }) {
129+
try {
130+
const zipFile = files.find(f => f.endsWith('.zip'));
131+
const xmlFile = files.find(f => f.endsWith('.xml'));
132+
133+
const firehoseFile = await this._getFirehoseFileFromZip(zipFile);
134+
const qdl = new QdlFlasher({
135+
files: [firehoseFile, xmlFile],
136+
ui: this.ui,
137+
outputLogFile: output,
138+
currTask: 'Configuration file'
139+
});
140+
141+
await qdl.run();
142+
fs.appendFileSync(output, `Config file download complete.${os.EOL}`);
143+
} catch (error) {
144+
fs.appendFileSync(output, 'Download failed with error: ' + error.message);
145+
throw new Error('Download failed with error: ' + error.message);
146+
}
147+
}
148+
149+
async _getOutputLogPath(output) {
150+
if (output) {
151+
const stats = await fs.stat(output);
152+
if (stats.isDirectory()) {
153+
const logFile = path.join(output, `tachyon_flash_${Date.now()}.log`);
154+
await fs.ensureFile(logFile);
155+
return logFile;
156+
}
157+
return output;
125158
}
159+
160+
const defaultLogFile = path.join(process.cwd(), `tachyon_flash_${Date.now()}.log`);
161+
await fs.ensureFile(defaultLogFile);
162+
return defaultLogFile;
126163
}
127164

128165
async _extractFlashFilesFromDir(dirPath) {
@@ -176,6 +213,21 @@ module.exports = class FlashCommand extends CLICommandBase {
176213
return JSON.parse(manifest.toString());
177214
}
178215

216+
async _getFirehoseFileFromZip(zipPath) {
217+
const dir = await unzip.Open.file(zipPath);
218+
const { filesToProgram } = await this._extractFlashFilesFromZip(zipPath);
219+
const firehoseFile = dir.files.find(file => file.path.endsWith(filesToProgram[0]));
220+
if (!firehoseFile) {
221+
throw new Error('Unable to find firehose file');
222+
}
223+
224+
const buffer = await firehoseFile.buffer();
225+
const tempFile = temp.openSync({ prefix: 'firehose_', suffix: '.elf' });
226+
fs.writeSync(tempFile.fd, buffer);
227+
fs.closeSync(tempFile.fd);
228+
return tempFile.path;
229+
}
230+
179231
_parseManfiestData(data) {
180232
return {
181233
base: data?.targets[0]?.qcm6490?.edl?.base,

src/cmd/setup-tachyon.js

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const CloudCommand = require('./cloud');
1313
const { sha512crypt } = require('sha512crypt-node');
1414
const DownloadManager = require('../lib/download-manager');
1515
const { platformForId } = require('../lib/platform');
16+
const path = require('path');
1617

1718
module.exports = class SetupTachyonCommands extends CLICommandBase {
1819
constructor({ ui } = {}) {
@@ -93,27 +94,41 @@ module.exports = class SetupTachyonCommands extends CLICommandBase {
9394
);
9495

9596
let configBlobPath = loadConfig;
97+
let configBlob = null;
9698
if (configBlobPath) {
99+
try {
100+
const data = fs.readFileSync(configBlobPath, 'utf8');
101+
configBlob = JSON.parse(data);
102+
const res = await this._createConfigBlob({
103+
registrationCode,
104+
systemPassword: configBlob.system_password,
105+
wifi: configBlob.wifi,
106+
sshKey: configBlob.ssh_key
107+
});
108+
configBlobPath = res.path;
109+
} catch (error) {
110+
throw new Error(`The configuration file is not a valid JSON file: ${error.message}`);
111+
}
97112
this.ui.write(
98113
`${os.EOL}${os.EOL}Skipping Step 6 - Using configuration file: ` + loadConfig + `${os.EOL}`
99114
);
100115
} else {
101-
configBlobPath = await this._runStepWithTiming(
116+
const res = await this._runStepWithTiming(
102117
'Creating the configuration file to write to the Tachyon device...',
103118
6,
104119
() => this._createConfigBlob({ registrationCode, ...config })
105120
);
121+
configBlobPath = res.path;
122+
configBlob = res.configBlob;
106123
}
124+
107125
const xmlPath = await this._createXmlFile(configBlobPath);
108126

109127
if (saveConfig) {
110-
this.ui.write(`${os.EOL}${os.EOL}Configuration file written here: ${saveConfig}${os.EOL}`);
111-
fs.copyFileSync(configBlobPath, saveConfig);
128+
fs.writeFileSync(saveConfig, JSON.stringify(configBlob, null, 2));
129+
this.ui.write(`${os.EOL}Configuration file written here: ${saveConfig}${os.EOL}`);
112130
}
113131

114-
//what files to flash?
115-
const filesToFlash = skipFlashingOs ? [xmlPath] : [packagePath, xmlPath];
116-
117132
const flashSuccessful = await this._runStepWithTiming(
118133
`Okay—last step! We're now flashing the device with the configuration, including the password, Wi-Fi settings, and operating system.${os.EOL}` +
119134
`Heads up: this is a large image and will take around 10 minutes to complete. Don't worry—we'll show a progress bar as we go!${os.EOL}${os.EOL}` +
@@ -125,7 +140,10 @@ module.exports = class SetupTachyonCommands extends CLICommandBase {
125140
` - When the light starts flashing yellow, release the button.${os.EOL}` +
126141
' Your device is now in flashing mode!',
127142
7,
128-
() => this._flash(filesToFlash)
143+
() => this._flash({
144+
files: [packagePath, xmlPath],
145+
skipFlashingOs
146+
})
129147
);
130148

131149
if (flashSuccessful) {
@@ -455,6 +473,9 @@ Welcome to the Particle Tachyon setup! This interactive command:
455473
return 'You need to provide a path to your SSH public key';
456474
}
457475
return true;
476+
},
477+
filter: (value) => {
478+
return value.startsWith('~') ? value.replace('~', os.homedir()) : value;
458479
}
459480
},
460481
];
@@ -485,7 +506,7 @@ Welcome to the Particle Tachyon setup! This interactive command:
485506

486507
// Write config JSON to a temporary file (generate a filename with the temp npm module)
487508
// prefixed by the JSON string length as a 32 bit integer
488-
let jsonString = JSON.stringify(config);
509+
let jsonString = JSON.stringify(config, null, 2);
489510
const buffer = Buffer.alloc(4 + Buffer.byteLength(jsonString));
490511
buffer.writeUInt32BE(Buffer.byteLength(jsonString), 0);
491512
buffer.write(jsonString, 4);
@@ -494,7 +515,7 @@ Welcome to the Particle Tachyon setup! This interactive command:
494515
fs.writeSync(tempFile.fd, buffer);
495516
fs.closeSync(tempFile.fd);
496517

497-
return tempFile.path;
518+
return { path: tempFile.path, configBlob: config };
498519
}
499520

500521
_generateShadowCompatibleHash(password) {
@@ -524,13 +545,16 @@ Welcome to the Particle Tachyon setup! This interactive command:
524545
].join(os.EOL);
525546

526547
// Create a temporary file for the XML content
527-
const tempFile = temp.openSync();
548+
const tempFile = temp.openSync({ prefix: 'config', suffix: '.xml' });
528549
fs.writeSync(tempFile.fd, xmlContent, 0, xmlContent.length, 0);
529550
fs.closeSync(tempFile.fd);
530551
return tempFile.path;
531552
}
532553

533-
async _flash(files) {
554+
async _flash({ files, skipFlashingOs, output }) {
555+
556+
const packagePath = files[0];
557+
534558
const question = {
535559
type: 'confirm',
536560
name: 'flash',
@@ -540,7 +564,19 @@ Welcome to the Particle Tachyon setup! This interactive command:
540564
await this.ui.prompt(question);
541565

542566
const flashCommand = new FlashCommand();
543-
return await flashCommand.flashTachyon({ files });
567+
568+
if (output && !fs.existsSync(output)) {
569+
fs.mkdirSync(output);
570+
}
571+
const outputLog = path.join(process.cwd(), `tachyon_flash_${Date.now()}.log`);
572+
fs.ensureFileSync(outputLog);
573+
574+
this.ui.write(`${os.EOL}Starting download. See logs at: ${outputLog}${os.EOL}`);
575+
if (!skipFlashingOs) {
576+
await flashCommand.flashTachyon({ files: [packagePath], skipReset: true, output: outputLog, verbose: false });
577+
}
578+
await flashCommand.flashTachyonXml({ files, output: outputLog });
579+
return true;
544580
}
545581

546582
_particleApi() {

src/lib/qdl.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const mkdirTemp = util.promisify(temp.mkdir);
1010
const TACHYON_STORAGE_TYPE = 'ufs';
1111

1212
class QdlFlasher {
13-
constructor({ files, includeDir, updateFolder, zip, ui, outputLogFile }) {
13+
constructor({ files, includeDir, updateFolder, zip, ui, outputLogFile, skipReset=false, currTask=null }) {
1414
this.files = files;
1515
this.includeDir = includeDir;
1616
this.updateFolder = updateFolder;
@@ -24,17 +24,20 @@ class QdlFlasher {
2424
this.currentModuleSectors = 0;
2525
this.progressBarInitialized = false;
2626
this.preparingDownload = false;
27+
this.skipReset = skipReset;
28+
this.currTask = currTask;
2729
}
2830

2931
async run() {
32+
let qdlProcess;
3033
try {
3134
const qdlPath = await this.getExecutable();
3235
const qdlArguments = this.buildArgs({ files: this.files, includeDir: this.includeDir, zip: this.zip });
3336
this.progressBar = this.ui.createProgressBar();
3437
const command = `${qdlPath} ${qdlArguments.join(' ')}`;
3538
fs.appendFileSync(this.outputLogFile, `Command: ${command}\n`);
3639

37-
const qdlProcess = execa(qdlPath, qdlArguments, {
40+
qdlProcess = execa(qdlPath, qdlArguments, {
3841
cwd: this.updateFolder || process.cwd(),
3942
stdio: 'pipe'
4043
});
@@ -55,6 +58,9 @@ class QdlFlasher {
5558
if (this.progressBarInitialized) {
5659
this.progressBar.stop();
5760
}
61+
if (qdlProcess && qdlProcess.kill) {
62+
qdlProcess.kill();
63+
}
5864
}
5965
}
6066

@@ -78,7 +84,8 @@ class QdlFlasher {
7884
'--storage', TACHYON_STORAGE_TYPE,
7985
...(zip ? ['--zip', zip] : []),
8086
...(includeDir ? ['--include', includeDir] : []),
81-
...files
87+
...files,
88+
...(this.skipReset ? ['--skip-reset'] : [])
8289
];
8390
}
8491

@@ -154,7 +161,7 @@ class QdlFlasher {
154161
}
155162

156163
if (this.totalSectorsFlashed === this.totalSectorsInAllFiles) {
157-
this.progressBar.update({ description: 'Flashing complete' });
164+
this.progressBar.update({ description: `Flashing complete ${this.currTask ? this.currTask : ''}` });
158165
}
159166
}
160167
}

0 commit comments

Comments
 (0)