Skip to content

Commit 08de643

Browse files
committed
Add enable, delete, list commands for Tachyon
1 parent b61ee75 commit 08de643

File tree

3 files changed

+183
-81
lines changed

3 files changed

+183
-81
lines changed

src/cli/esim.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,42 @@ module.exports = ({ commandProcessor, root }) => {
4242
`)
4343
});
4444

45-
commandProcessor.createCommand(esim, 'enable', 'Enables a downloaded eSIM profile', {
45+
commandProcessor.createCommand(esim, 'enable', '(Only for Tachyon) Enables a downloaded eSIM profile', {
46+
params: '<iccid>',
4647
handler: (args) => {
4748
const ESimCommands = require('../cmd/esim');
48-
return new ESimCommands().enableCommand();
49+
return new ESimCommands().enableCommand(args.params.iccid);
50+
},
51+
examples: {
52+
'$0 $command': 'TBD'
53+
}
54+
});
55+
56+
commandProcessor.createCommand(esim, 'delete', '(Only for Tachyon) Deletes an ICCID profile on the eSIM', {
57+
options: Object.assign({
58+
'lpa': {
59+
description: 'Provide the LPA tool path'
60+
},
61+
}),
62+
params: '<iccid>',
63+
handler: (args) => {
64+
const ESimCommands = require('../cmd/esim');
65+
return new ESimCommands().deleteCommand(args, args.params.iccid);
66+
},
67+
examples: {
68+
'$0 $command': 'TBD'
69+
}
70+
});
71+
72+
commandProcessor.createCommand(esim, 'list', '(Only for Tachyon) Lists all the eSIM profiles on the device', {
73+
options: Object.assign({
74+
'lpa': {
75+
description: 'Provide the LPA tool path'
76+
},
77+
}),
78+
handler: (args) => {
79+
const ESimCommands = require('../cmd/esim');
80+
return new ESimCommands().listCommand(args);
4981
},
5082
examples: {
5183
'$0 $command': 'TBD'

src/cmd/esim.js

Lines changed: 144 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const FlashCommand = require('./flash');
1111
const path = require('path');
1212
const _ = require('lodash');
1313
const chalk = require('chalk');
14+
const { clear } = require('console');
1415

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

81-
async enableCommand() {
82+
async enableCommand(iccid) {
83+
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
8284
this.verbose = true;
8385
const device = await this.serial.whatSerialPortDidYouMean();
84-
if (device.type === 'Tachyon') {
85-
this.isTachyon = true;
86+
if (device.type !== 'Tachyon') {
87+
throw new Error('Enable command is only for Tachyon devices');
88+
}
89+
this.isTachyon = true;
90+
await this.doEnable(iccid);
91+
}
92+
93+
async deleteCommand(args, iccid) {
94+
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
95+
this.verbose = true;
96+
const device = await this.serial.whatSerialPortDidYouMean();
97+
if (device.type !== 'Tachyon') {
98+
throw new Error('Delete command is only for Tachyon devices');
8699
}
100+
this.isTachyon = true;
101+
this._validateArgs(args);
102+
await this.doDelete(device, iccid);
103+
}
87104

88-
await this.doEnable(device);
105+
async listCommand() {
106+
console.log(chalk.bold(`Ensure only one device is connected${os.EOL}`));
107+
this.verbose = true;
108+
const device = await this.serial.whatSerialPortDidYouMean();
109+
if (device.type !== 'Tachyon') {
110+
throw new Error('List command is only for Tachyon devices');
111+
}
112+
this.isTachyon = true;
113+
await this.doList();
89114
}
90115

91116
// Populate the availableProvisioningData set with the indices of the input JSON data
@@ -256,98 +281,141 @@ module.exports = class ESimCommands extends CLICommandBase {
256281
}
257282
}
258283

259-
async doEnable(device) {
260-
let provisionOutputLogs = [];
261-
let timestamp = new Date().toISOString().replace(/:/g, '-');
262-
let success = false;
263-
264-
const outputJsonFile = path.join(this.outputFolder, `${this.isTachyon ? 'tachyon' : device.deviceId}_${timestamp}.json`);
265-
266-
const processOutput = async (failedLogs = []) => {
267-
const logs = Array.isArray(failedLogs) ? failedLogs : [failedLogs];
268-
provisionOutputLogs.push({
269-
step: 'final_step',
270-
timestamp: new Date().toISOString().replace(/:/g, '-'),
271-
success: success ? 'success' : 'failed',
272-
details: {
273-
rawLogs: success ? ['Profile enable successful'] : ['Profile enable failed', ...logs],
274-
}
284+
async doEnable(iccid) {
285+
const TACHYON_QLRIL_WAIT_TIMEOUT = 20000;
286+
let output = '';
287+
288+
try {
289+
this.adbProcess = execa('adb', ['shell', 'qlril-app', 'enable', iccid]);
290+
291+
await new Promise((resolve, reject) => {
292+
const timeout = setTimeout(() => {
293+
reject(new Error('Timeout waiting for qlril app to start'));
294+
}, TACHYON_QLRIL_WAIT_TIMEOUT);
295+
296+
this.adbProcess.stdout.on('data', (data) => {
297+
output += data.toString();
298+
if (output.includes(`ICCID currently active: ${iccid}`)) {
299+
console.log(`ICCID ${iccid} enabled successfully`);
300+
clearTimeout(timeout);
301+
resolve();
302+
}
303+
});
304+
305+
this.adbProcess.catch((error) => {
306+
clearTimeout(timeout);
307+
reject(new Error(`ADB process error: ${error.message}`));
308+
});
309+
310+
this.adbProcess.then(() => {
311+
clearTimeout(timeout);
312+
reject(new Error('ADB process ended early without valid output'));
313+
});
275314
});
276-
await this._changeLed(device, success ? PROVISIONING_SUCCESS : PROVISIONING_FAILURE);
277-
this._addToJson(outputJsonFile, provisionOutputLogs.filter(Boolean));
278-
};
315+
316+
console.log(os.EOL);
317+
} catch (error) {
318+
console.error(`Failed to enable profiles: ${error.message}`);
319+
} finally {
320+
this._exitQlril();
321+
}
322+
}
279323

324+
async doDelete(device, iccid) {
280325
try {
281326
const port = device.port;
282-
283-
// Start qlril-app through ADB for Tachyon
284-
const qlrilStep = await this._initializeQlril();
285-
provisionOutputLogs.push(qlrilStep);
286-
if (qlrilStep?.status === 'failed') {
287-
await processOutput();
288-
return;
289-
}
290327

291-
const iccidsOnDevice = await this._getIccidOnDevice(port);
292-
293-
const iccidToEnable = this._getIccidToEnable(iccidsOnDevice);
294-
provisionOutputLogs.push(`ICCID to enable: ${iccidToEnable}`);
295-
if (iccidToEnable === null) {
296-
await processOutput('No profile found on the device to enable');
297-
return;
298-
}
299-
300-
const enableResp = await this._enableProfile(port, iccidToEnable);
301-
provisionOutputLogs.push(enableResp);
302-
if (enableResp.status === 'failed') {
303-
await processOutput();
304-
return;
305-
}
328+
await this._initializeQlril();
306329

307-
const verifyIccidEnabledResp = await this._verifyIccidEnaled(port, iccidToEnable);
308-
provisionOutputLogs.push(verifyIccidEnabledResp);
309-
if (verifyIccidEnabledResp.status === 'failed') {
310-
await processOutput();
330+
const iccidsOnDevice = await this._getIccidOnDevice(port);
331+
console.log('Profiles on device:', iccidsOnDevice);
332+
if (!iccidsOnDevice.includes(iccid)) {
333+
console.log(`ICCID ${iccid} not found on the device or is a test ICCID`);
311334
return;
312335
}
313-
314-
success = true;
315-
console.log('Profile enabled successfully');
316-
await processOutput();
336+
337+
await execa(this.lpa, ['disable', iccid, `--serial=${port}`]);
338+
await execa(this.lpa, ['delete', iccid, `--serial=${port}`]);
339+
340+
console.log('Profile deleted successfully');
317341
} catch (error) {
318-
await processOutput(error.message);
342+
console.error(`Failed to delete profile: ${error.message}`);
319343
} finally {
320344
this._exitQlril();
321345
}
322346
}
323347

324-
_validateArgs(args) {
325-
if (!args) {
326-
throw new Error('Missing args');
348+
async doList() {
349+
const TACHYON_QLRIL_WAIT_TIMEOUT = 10000;
350+
let output = '';
351+
352+
try {
353+
this.adbProcess = execa('adb', ['shell', 'qlril-app', 'listProfiles']);
354+
355+
await new Promise((resolve, reject) => {
356+
const timeout = setTimeout(() => {
357+
reject(new Error('Timeout waiting for qlril app to start'));
358+
}, TACHYON_QLRIL_WAIT_TIMEOUT);
359+
360+
this.adbProcess.stdout.on('data', (data) => {
361+
output += data.toString();
362+
363+
const iccids = output
364+
.trim()
365+
.replace(/^\[/, '')
366+
.replace(/\]$/, '')
367+
.split(',')
368+
.map(iccid => iccid.trim())
369+
.filter(Boolean);
370+
371+
if (iccids.length > 0) {
372+
console.log(`Profiles found:${os.EOL}`);
373+
iccids.forEach(iccid => console.log(`\t- ${iccid}`));
374+
clearTimeout(timeout);
375+
resolve();
376+
}
377+
});
378+
379+
this.adbProcess.catch((error) => {
380+
clearTimeout(timeout);
381+
reject(new Error(`ADB process error: ${error.message}`));
382+
});
383+
384+
this.adbProcess.then(() => {
385+
clearTimeout(timeout);
386+
reject(new Error('ADB process ended early without valid output'));
387+
});
388+
});
389+
390+
console.log(os.EOL);
391+
} catch (error) {
392+
console.error(`Failed to list profiles: ${error.message}`);
393+
} finally {
394+
this._exitQlril();
327395
}
396+
}
397+
328398

329-
const requiredArgs = {
330-
input: 'Missing input JSON file',
331-
lpa: 'Missing LPA tool path',
332-
...(this.isTachyon ? {} : { binary: 'Missing folder path to binaries' })
333-
};
334-
335-
for (const [key, errorMessage] of Object.entries(requiredArgs)) {
336-
if (!args[key]) {
337-
throw new Error(errorMessage);
399+
_validateArgs(args) {
400+
if (!args?.lpa) {
401+
throw new Error('Missing LPA tool path');
402+
}
403+
404+
this.inputJson = args?.input;
405+
if (this.inputJson) {
406+
try {
407+
this.inputJsonData = JSON.parse(fs.readFileSync(this.inputJson));
408+
} catch (error) {
409+
throw new Error(`Invalid JSON in input file: ${error.message}`);
338410
}
339411
}
340-
341-
this.inputJson = args.input;
342-
this.inputJsonData = JSON.parse(fs.readFileSync(this.inputJson));
343-
344-
this.outputFolder = args.output || 'esim_loading_logs';
412+
413+
this.outputFolder = args?.output || 'esim_loading_logs';
345414
if (!fs.existsSync(this.outputFolder)) {
346415
fs.mkdirSync(this.outputFolder);
347416
}
348-
349417
this.lpa = args.lpa;
350-
this.binaries = args.binary;
418+
this.binaries = args?.binary;
351419
}
352420

353421

@@ -367,7 +435,7 @@ module.exports = class ESimCommands extends CLICommandBase {
367435

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

370-
res.details.iccidsOnDevice = iccidsOnDeviceAfterDownload;
438+
res.details.iccidsOnDevice = iccidsOnDeviceAfterDownloadFiltered;
371439
res.details.rawLogs.push(equal ? ['Profiles on device match the expected profiles'] :
372440
['Profiles on device do not match the expected profiles']);
373441
res.status = equal ? 'success' : 'failed';
@@ -814,6 +882,7 @@ module.exports = class ESimCommands extends CLICommandBase {
814882
};
815883

816884
const profilesOnDeviceAfterEnable = await this._listProfiles(port);
885+
console.log('Profiles on device after enable:', profilesOnDeviceAfterEnable);
817886
const iccidString = profilesOnDeviceAfterEnable.find((line) => line.includes(iccid));
818887
if (iccidString) {
819888
// check that you see the string 'enabled'

src/lib/qdl.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ class QdlFlasher {
100100
}
101101

102102
processFlashingLogs(line) {
103+
if (!this.preparingDownload) {
104+
this.preparingDownload = true;
105+
this.ui.stdout.write('Preparing for download...');
106+
}
107+
103108
if (line.includes('status=getProgramInfo')) {
104109
this.handleProgramInfo(line);
105110
} else if (line.includes('status=Start flashing module')) {
@@ -110,10 +115,6 @@ class QdlFlasher {
110115
}
111116

112117
handleProgramInfo(line) {
113-
if (!this.preparingDownload) {
114-
this.preparingDownload = true;
115-
this.ui.stdout.write('Preparing to download files...');
116-
}
117118
const match = line.match(/sectors_total=(\d+)/);
118119
if (match) {
119120
this.totalSectorsInAllFiles += parseInt(match[1], 10);

0 commit comments

Comments
 (0)