Skip to content

Commit

Permalink
Merge pull request #791 from particle-iot/feature/tachyon-flash-confi…
Browse files Browse the repository at this point in the history
…g-blob

Feature/tachyon flash config blob
  • Loading branch information
keeramis authored Feb 4, 2025
2 parents f9e469f + 9034d4f commit afeef7d
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 31 deletions.
Binary file modified assets/qdl/darwin/arm64/qdl
Binary file not shown.
Binary file modified assets/qdl/darwin/x64/liblzma.5.dylib
Binary file not shown.
Binary file modified assets/qdl/darwin/x64/qdl
Binary file not shown.
Binary file modified assets/qdl/linux/arm64/qdl
Binary file not shown.
Binary file modified assets/qdl/linux/x64/qdl
Binary file not shown.
Binary file modified assets/qdl/win32/x64/qdl.exe
Binary file not shown.
8 changes: 4 additions & 4 deletions src/cmd/esim.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ module.exports = class ESimCommands extends CLICommandBase {
return;
}

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

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

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

const iccids = stdout
.trim()
Expand Down Expand Up @@ -521,7 +521,7 @@ module.exports = class ESimCommands extends CLICommandBase {
}

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

try {
await new Promise((resolve, reject) => {
Expand Down
74 changes: 63 additions & 11 deletions src/cmd/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ module.exports = class FlashCommand extends CLICommandBase {
}

//returns true if successful or false if failed
async flashTachyon({ files, output }) {
async flashTachyon({ files, skipReset, output, verbose=true }) {
let zipFile;
let includeDir = '';
let updateFolder = '';
Expand Down Expand Up @@ -101,28 +101,65 @@ module.exports = class FlashCommand extends CLICommandBase {
filesToProgram = files;
}

if (output && !fs.existsSync(output)) {
fs.mkdirSync(output);
}
const outputLog = path.join(output ? output : process.cwd(), `tachyon_flash_${Date.now()}.log`);
const outputLog = await this._getOutputLogPath(output);

try {
this.ui.write(`Starting download. This may take several minutes. See logs at: ${outputLog}${os.EOL}`);
if (verbose) {
this.ui.write(`${os.EOL}Starting download. See logs at: ${outputLog}${os.EOL}`);
}
const qdl = new QdlFlasher({
files: filesToProgram,
includeDir,
updateFolder,
zip: zipFile,
ui: this.ui,
outputLogFile: outputLog
outputLogFile: outputLog,
skipReset,
currTask: 'OS'
});
await qdl.run();
fs.appendFileSync(outputLog, 'Download complete.');
return true;
fs.appendFileSync(outputLog, `OS Download complete.${os.EOL}`);
} catch (error) {
this.ui.write('Download failed');
fs.appendFileSync(outputLog, 'Download failed with error: ' + error.message);
return false;
throw new Error('Download failed with error: ' + error.message);
}
}

async flashTachyonXml({ files, output }) {
try {
const zipFile = files.find(f => f.endsWith('.zip'));
const xmlFile = files.find(f => f.endsWith('.xml'));

const firehoseFile = await this._getFirehoseFileFromZip(zipFile);
const qdl = new QdlFlasher({
files: [firehoseFile, xmlFile],
ui: this.ui,
outputLogFile: output,
currTask: 'Configuration file'
});

await qdl.run();
fs.appendFileSync(output, `Config file download complete.${os.EOL}`);
} catch (error) {
fs.appendFileSync(output, 'Download failed with error: ' + error.message);
throw new Error('Download failed with error: ' + error.message);
}
}

async _getOutputLogPath(output) {
if (output) {
const stats = await fs.stat(output);
if (stats.isDirectory()) {
const logFile = path.join(output, `tachyon_flash_${Date.now()}.log`);
await fs.ensureFile(logFile);
return logFile;
}
return output;
}

const defaultLogFile = path.join(process.cwd(), `tachyon_flash_${Date.now()}.log`);
await fs.ensureFile(defaultLogFile);
return defaultLogFile;
}

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

async _getFirehoseFileFromZip(zipPath) {
const dir = await unzip.Open.file(zipPath);
const { filesToProgram } = await this._extractFlashFilesFromZip(zipPath);
const firehoseFile = dir.files.find(file => file.path.endsWith(filesToProgram[0]));
if (!firehoseFile) {
throw new Error('Unable to find firehose file');
}

const buffer = await firehoseFile.buffer();
const tempFile = temp.openSync({ prefix: 'firehose_', suffix: '.elf' });
fs.writeSync(tempFile.fd, buffer);
fs.closeSync(tempFile.fd);
return tempFile.path;
}

_parseManfiestData(data) {
return {
base: data?.targets[0]?.qcm6490?.edl?.base,
Expand Down
60 changes: 48 additions & 12 deletions src/cmd/setup-tachyon.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const CloudCommand = require('./cloud');
const { sha512crypt } = require('sha512crypt-node');
const DownloadManager = require('../lib/download-manager');
const { platformForId } = require('../lib/platform');
const path = require('path');

module.exports = class SetupTachyonCommands extends CLICommandBase {
constructor({ ui } = {}) {
Expand Down Expand Up @@ -93,27 +94,41 @@ module.exports = class SetupTachyonCommands extends CLICommandBase {
);

let configBlobPath = loadConfig;
let configBlob = null;
if (configBlobPath) {
try {
const data = fs.readFileSync(configBlobPath, 'utf8');
configBlob = JSON.parse(data);
const res = await this._createConfigBlob({
registrationCode,
systemPassword: configBlob.system_password,
wifi: configBlob.wifi,
sshKey: configBlob.ssh_key
});
configBlobPath = res.path;
} catch (error) {
throw new Error(`The configuration file is not a valid JSON file: ${error.message}`);
}
this.ui.write(
`${os.EOL}${os.EOL}Skipping Step 6 - Using configuration file: ` + loadConfig + `${os.EOL}`
);
} else {
configBlobPath = await this._runStepWithTiming(
const res = await this._runStepWithTiming(
'Creating the configuration file to write to the Tachyon device...',
6,
() => this._createConfigBlob({ registrationCode, ...config })
);
configBlobPath = res.path;
configBlob = res.configBlob;
}

const xmlPath = await this._createXmlFile(configBlobPath);

if (saveConfig) {
this.ui.write(`${os.EOL}${os.EOL}Configuration file written here: ${saveConfig}${os.EOL}`);
fs.copyFileSync(configBlobPath, saveConfig);
fs.writeFileSync(saveConfig, JSON.stringify(configBlob, null, 2));
this.ui.write(`${os.EOL}Configuration file written here: ${saveConfig}${os.EOL}`);
}

//what files to flash?
const filesToFlash = skipFlashingOs ? [xmlPath] : [packagePath, xmlPath];

const flashSuccessful = await this._runStepWithTiming(
`Okay—last step! We're now flashing the device with the configuration, including the password, Wi-Fi settings, and operating system.${os.EOL}` +
`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}` +
Expand All @@ -125,7 +140,10 @@ module.exports = class SetupTachyonCommands extends CLICommandBase {
` - When the light starts flashing yellow, release the button.${os.EOL}` +
' Your device is now in flashing mode!',
7,
() => this._flash(filesToFlash)
() => this._flash({
files: [packagePath, xmlPath],
skipFlashingOs
})
);

if (flashSuccessful) {
Expand Down Expand Up @@ -455,6 +473,9 @@ Welcome to the Particle Tachyon setup! This interactive command:
return 'You need to provide a path to your SSH public key';
}
return true;
},
filter: (value) => {
return value.startsWith('~') ? value.replace('~', os.homedir()) : value;
}
},
];
Expand Down Expand Up @@ -485,7 +506,7 @@ Welcome to the Particle Tachyon setup! This interactive command:

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

return tempFile.path;
return { path: tempFile.path, configBlob: config };
}

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

// Create a temporary file for the XML content
const tempFile = temp.openSync();
const tempFile = temp.openSync({ prefix: 'config', suffix: '.xml' });
fs.writeSync(tempFile.fd, xmlContent, 0, xmlContent.length, 0);
fs.closeSync(tempFile.fd);
return tempFile.path;
}

async _flash(files) {
async _flash({ files, skipFlashingOs, output }) {

const packagePath = files[0];

const question = {
type: 'confirm',
name: 'flash',
Expand All @@ -540,7 +564,19 @@ Welcome to the Particle Tachyon setup! This interactive command:
await this.ui.prompt(question);

const flashCommand = new FlashCommand();
return await flashCommand.flashTachyon({ files });

if (output && !fs.existsSync(output)) {
fs.mkdirSync(output);
}
const outputLog = path.join(process.cwd(), `tachyon_flash_${Date.now()}.log`);
fs.ensureFileSync(outputLog);

this.ui.write(`${os.EOL}Starting download. See logs at: ${outputLog}${os.EOL}`);
if (!skipFlashingOs) {
await flashCommand.flashTachyon({ files: [packagePath], skipReset: true, output: outputLog, verbose: false });
}
await flashCommand.flashTachyonXml({ files, output: outputLog });
return true;
}

_particleApi() {
Expand Down
15 changes: 11 additions & 4 deletions src/lib/qdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const mkdirTemp = util.promisify(temp.mkdir);
const TACHYON_STORAGE_TYPE = 'ufs';

class QdlFlasher {
constructor({ files, includeDir, updateFolder, zip, ui, outputLogFile }) {
constructor({ files, includeDir, updateFolder, zip, ui, outputLogFile, skipReset=false, currTask=null }) {
this.files = files;
this.includeDir = includeDir;
this.updateFolder = updateFolder;
Expand All @@ -24,17 +24,20 @@ class QdlFlasher {
this.currentModuleSectors = 0;
this.progressBarInitialized = false;
this.preparingDownload = false;
this.skipReset = skipReset;
this.currTask = currTask;
}

async run() {
let qdlProcess;
try {
const qdlPath = await this.getExecutable();
const qdlArguments = this.buildArgs({ files: this.files, includeDir: this.includeDir, zip: this.zip });
this.progressBar = this.ui.createProgressBar();
const command = `${qdlPath} ${qdlArguments.join(' ')}`;
fs.appendFileSync(this.outputLogFile, `Command: ${command}\n`);

const qdlProcess = execa(qdlPath, qdlArguments, {
qdlProcess = execa(qdlPath, qdlArguments, {
cwd: this.updateFolder || process.cwd(),
stdio: 'pipe'
});
Expand All @@ -55,6 +58,9 @@ class QdlFlasher {
if (this.progressBarInitialized) {
this.progressBar.stop();
}
if (qdlProcess && qdlProcess.kill) {
qdlProcess.kill();
}
}
}

Expand All @@ -78,7 +84,8 @@ class QdlFlasher {
'--storage', TACHYON_STORAGE_TYPE,
...(zip ? ['--zip', zip] : []),
...(includeDir ? ['--include', includeDir] : []),
...files
...files,
...(this.skipReset ? ['--skip-reset'] : [])
];
}

Expand Down Expand Up @@ -154,7 +161,7 @@ class QdlFlasher {
}

if (this.totalSectorsFlashed === this.totalSectorsInAllFiles) {
this.progressBar.update({ description: 'Flashing complete' });
this.progressBar.update({ description: `Flashing complete ${this.currTask ? this.currTask : ''}` });
}
}
}
Expand Down

0 comments on commit afeef7d

Please sign in to comment.