Skip to content

Commit

Permalink
Add enable, delete, list commands for Tachyon
Browse files Browse the repository at this point in the history
  • Loading branch information
keeramis committed Jan 17, 2025
1 parent 32763fc commit 129dd3c
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 51 deletions.
31 changes: 26 additions & 5 deletions src/cli/esim.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,42 @@ module.exports = ({ commandProcessor, root }) => {
`)
});

commandProcessor.createCommand(esim, 'enable', 'Enables a downloaded eSIM profile', {
handler: () => {
commandProcessor.createCommand(esim, 'enable', '(Only for Tachyon) Enables a downloaded eSIM profile', {
params: '<iccid>',
handler: (args) => {
const ESimCommands = require('../cmd/esim');
return new ESimCommands().enableCommand();
return new ESimCommands().enableCommand(args.params.iccid);
},
examples: {
'$0 $command': 'TBD'
}
});

commandProcessor.createCommand(esim, 'delete', 'Deletes an ICCID profile on the eSIM', {
commandProcessor.createCommand(esim, 'delete', '(Only for Tachyon) Deletes an ICCID profile on the eSIM', {
options: Object.assign({
'lpa': {
description: 'Provide the LPA tool path'
},
}),
params: '<iccid>',
handler: (args) => {
const ESimCommands = require('../cmd/esim');
return new ESimCommands().deleteCommand(args.params.iccid);
return new ESimCommands().deleteCommand(args, args.params.iccid);
},
examples: {
'$0 $command': 'TBD'
}
});

commandProcessor.createCommand(esim, 'list', '(Only for Tachyon) Lists all the eSIM profiles on the device', {
options: Object.assign({
'lpa': {
description: 'Provide the LPA tool path'
},
}),
handler: (args) => {
const ESimCommands = require('../cmd/esim');
return new ESimCommands().listCommand(args);
},
examples: {
'$0 $command': 'TBD'
Expand Down
164 changes: 122 additions & 42 deletions src/cmd/esim.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const FlashCommand = require('./flash');
const path = require('path');
const _ = require('lodash');
const chalk = require('chalk');
const { clear } = require('console');

// TODO: Get these from exports
const PROVISIONING_PROGRESS = 1;
Expand Down Expand Up @@ -78,22 +79,38 @@ module.exports = class ESimCommands extends CLICommandBase {
console.log('Ready to bulk provision. Connect devices to start. Press Ctrl-C to exit.');
}

async enableCommand() {
async enableCommand(iccid) {
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
this.verbose = true;
const device = await this.serial.whatSerialPortDidYouMean();
if (device.type === 'Tachyon') {
this.isTachyon = true;
await this.doEnable(device);
if (device.type !== 'Tachyon') {
throw new Error('Enable command is only for Tachyon devices');
}
this.isTachyon = true;
await this.doEnable(iccid);
}

async deleteCommand(iccid) {
async deleteCommand(args, iccid) {
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
this.verbose = true;
const device = await this.serial.whatSerialPortDidYouMean();
if (device.type === 'Tachyon') {
this.isTachyon = true;
await this.doDelete(device, iccid);
if (device.type !== 'Tachyon') {
throw new Error('Delete command is only for Tachyon devices');
}
this.isTachyon = true;
this._validateArgs(args);
await this.doDelete(device, iccid);
}

async listCommand() {
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
this.verbose = true;
const device = await this.serial.whatSerialPortDidYouMean();
if (device.type !== 'Tachyon') {
throw new Error('List command is only for Tachyon devices');
}
this.isTachyon = true;
await this.doList();
}

// Populate the availableProvisioningData set with the indices of the input JSON data
Expand Down Expand Up @@ -264,24 +281,41 @@ module.exports = class ESimCommands extends CLICommandBase {
}
}

async doEnable(device) {
async doEnable(iccid) {
const TACHYON_QLRIL_WAIT_TIMEOUT = 20000;
let output = '';

try {
const port = device.port;
const iccidsOnDevice = await this._getIccidOnDevice(port);
const iccidToEnable = this._getIccidToEnable(iccidsOnDevice);

this.adbProcess = execa('adb', ['shell', 'qlril-app', 'enable', iccidToEnable]);
this.adbProcess = execa('adb', ['shell', 'qlril-app', 'enable', iccid]);

await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for qlril app to start'));
}, TACHYON_QLRIL_WAIT_TIMEOUT);

await new Promise((resolve) => setTimeout(resolve, TACHYON_QLRIL_WAIT_TIMEOUT));
this.adbProcess.stdout.on('data', (data) => {
output += data.toString();
if (output.includes(`ICCID currently active: ${iccid}`)) {
console.log(`ICCID ${iccid} enabled successfully`);
clearTimeout(timeout);
resolve();
}
});

this.adbProcess.catch((error) => {
clearTimeout(timeout);
reject(new Error(`ADB process error: ${error.message}`));
});

await this._verifyIccidEnaled(port, iccidToEnable);
this.adbProcess.then(() => {
clearTimeout(timeout);
reject(new Error('ADB process ended early without valid output'));
});
});

console.log('Profile enabled successfully');
console.log(os.EOL);
} catch (error) {
console.error(`Failed to enable profile: ${error.message}`);
console.error(`Failed to enable profiles: ${error.message}`);
} finally {
this._exitQlril();
}
Expand All @@ -291,13 +325,14 @@ module.exports = class ESimCommands extends CLICommandBase {
try {
const port = device.port;

await this._initializeQlril();

const iccidsOnDevice = await this._getIccidOnDevice(port);
console.log('Profiles on device:', iccidsOnDevice);
if (!iccidsOnDevice.includes(iccid)) {
console.log(`ICCID ${iccid} not found on the device`);
console.log(`ICCID ${iccid} not found on the device or is a test ICCID`);
return;
}

await this._initializeQlril();

await execa(this.lpa, ['disable', iccid, `--serial=${port}`]);
await execa(this.lpa, ['delete', iccid, `--serial=${port}`]);
Expand All @@ -310,33 +345,77 @@ module.exports = class ESimCommands extends CLICommandBase {
}
}

_validateArgs(args) {
if (!args) {
throw new Error('Missing args');
async doList() {
const TACHYON_QLRIL_WAIT_TIMEOUT = 10000;
let output = '';

try {
this.adbProcess = execa('adb', ['shell', 'qlril-app', 'listProfiles']);

await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for qlril app to start'));
}, TACHYON_QLRIL_WAIT_TIMEOUT);

this.adbProcess.stdout.on('data', (data) => {
output += data.toString();

const iccids = output
.trim()
.replace(/^\[/, '')
.replace(/\]$/, '')
.split(',')
.map(iccid => iccid.trim())
.filter(Boolean);

if (iccids.length > 0) {
console.log(`Profiles found:${os.EOL}`);
iccids.forEach(iccid => console.log(`\t- ${iccid}`));
clearTimeout(timeout);
resolve();
}
});

this.adbProcess.catch((error) => {
clearTimeout(timeout);
reject(new Error(`ADB process error: ${error.message}`));
});

this.adbProcess.then(() => {
clearTimeout(timeout);
reject(new Error('ADB process ended early without valid output'));
});
});

console.log(os.EOL);
} catch (error) {
console.error(`Failed to list profiles: ${error.message}`);
} finally {
this._exitQlril();
}
}


const requiredArgs = {
input: 'Missing input JSON file',
lpa: 'Missing LPA tool path',
...(this.isTachyon ? {} : { binary: 'Missing folder path to binaries' })
};

for (const [key, errorMessage] of Object.entries(requiredArgs)) {
if (!args[key]) {
throw new Error(errorMessage);
_validateArgs(args) {
if (!args?.lpa) {
throw new Error('Missing LPA tool path');
}

this.inputJson = args?.input;
if (this.inputJson) {
try {
this.inputJsonData = JSON.parse(fs.readFileSync(this.inputJson));
} catch (error) {
throw new Error(`Invalid JSON in input file: ${error.message}`);
}
}

this.inputJson = args.input;
this.inputJsonData = JSON.parse(fs.readFileSync(this.inputJson));

this.outputFolder = args.output || 'esim_loading_logs';

this.outputFolder = args?.output || 'esim_loading_logs';
if (!fs.existsSync(this.outputFolder)) {
fs.mkdirSync(this.outputFolder);
}

this.lpa = args.lpa;
this.binaries = args.binary;
this.binaries = args?.binary;
}


Expand All @@ -356,7 +435,7 @@ module.exports = class ESimCommands extends CLICommandBase {

const equal = _.isEqual(_.sortBy(expectedIccids), _.sortBy(iccidsOnDeviceAfterDownloadFiltered));

res.details.iccidsOnDevice = iccidsOnDeviceAfterDownload;
res.details.iccidsOnDevice = iccidsOnDeviceAfterDownloadFiltered;
res.details.rawLogs.push(equal ? ['Profiles on device match the expected profiles'] :
['Profiles on device do not match the expected profiles']);
res.status = equal ? 'success' : 'failed';
Expand Down Expand Up @@ -803,6 +882,7 @@ module.exports = class ESimCommands extends CLICommandBase {
};

const profilesOnDeviceAfterEnable = await this._listProfiles(port);
console.log('Profiles on device after enable:', profilesOnDeviceAfterEnable);
const iccidString = profilesOnDeviceAfterEnable.find((line) => line.includes(iccid));
if (iccidString) {
// check that you see the string 'enabled'
Expand Down
9 changes: 5 additions & 4 deletions src/lib/qdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class QdlFlasher {
}

processFlashingLogs(line) {
if (!this.preparingDownload) {
this.preparingDownload = true;
this.ui.stdout.write('Preparing for download...');
}

if (line.includes('status=getProgramInfo')) {
this.handleProgramInfo(line);
} else if (line.includes('status=Start flashing module')) {
Expand All @@ -110,10 +115,6 @@ class QdlFlasher {
}

handleProgramInfo(line) {
if (!this.preparingDownload) {
this.preparingDownload = true;
this.ui.stdout.write('Preparing to download files...');
}
const match = line.match(/sectors_total=(\d+)/);
if (match) {
this.totalSectorsInAllFiles += parseInt(match[1], 10);
Expand Down

0 comments on commit 129dd3c

Please sign in to comment.