index.js 10.8 KB
/**
 * Modul modem-commands
 *
 * @module
 */


const MUTEX_COMMAND = 'COMMAND';
exports.MUTEX_COMMAND = MUTEX_COMMAND;

const MUTEX_SUBCOMMAND = 'SUBCOMMAND';
exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND;

/**
 * CTRL-Z string
 * @constant
 */
const CTRLZ = '\u001a';
exports.CTRLZ = CTRLZ;

const pdu = require('node-pdu');
const uuidv1 = require('uuid/v1');

const ParserReadline = require('@serialport/parser-readline');
const ParserRegex = require('@serialport/parser-regex');
const ParserReady = require('@serialport/parser-ready');

const logger = require('komodo-sdk/logger');
const mutex = require('../mutex-common');
const parsers = require('../serialport-parsers');
const modemInfo = require('../modem-info');

let port;

function writeToPort(data) {
    return new Promise((resolve) => {
        modemInfo.lastWriteTs = new Date();
        port.write(data, (err, bytesWritten) => {
            if (err) logger.warn(`ERROR: ${err.toString()}`);

            logger.verbose('OUTGOING', { data: data.toString(), bytesWritten, err });
            resolve(bytesWritten);
        });
    });
}
exports.writeToPort = writeToPort;

function writeToPortAndWaitForReadline(cmd, lockName) {
    let resolved = false;

    return new Promise(async (resolve) => {
        const parser = new ParserReadline({ delimiter: parsers.PARSER_READLINE_DELIMITER });
        parser.on('data', (data) => {
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
            if (!resolved) {
                resolved = true;
                resolve(data);
            }
        });

        await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
        port.pipe(parser);
        await writeToPort(cmd);
    });
}
exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline;

function writeToPortAndWaitForOkOrError(cmd, lockName) {
    return new Promise(async (resolve) => {
        const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ });
        parser.on('data', (data) => {
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
            resolve(data);
        });

        await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
        port.pipe(parser);
        await writeToPort(cmd);
    });
}
exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError;

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, ms || 0);
    });
}
exports.sleep = sleep;

exports.setPort = function setPort(val) {
    port = val;
};

function querySignalQuality() {
    return new Promise(async (resolve) => {
        if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) {
            resolve(false);
            return;
        }

        await writeToPort('AT+CSQ\r');
        mutex.unlock(MUTEX_COMMAND, 'querySignalQuality');
        resolve(true);
    });
}
exports.querySignalQuality = querySignalQuality;

function queryCOPS(lockName) {
    return new Promise(async (resolve) => {
        await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS');
        await writeToPort('AT+COPS?\r');
        mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS');
        resolve(true);
    });
}
exports.queryCOPS = queryCOPS;

function queryCOPSAndSignalQuality(skipOnLocked) {
    return new Promise(async (resolve) => {
        if (!skipOnLocked) {
            await mutex.lock(MUTEX_COMMAND);
        } else if (!mutex.tryLock(MUTEX_COMMAND, 'queryCOPSAndSignalQuality')) {
            resolve(false);
            return;
        }

        await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND);
        await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND);

        mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality');
        resolve(true);
    });
}
exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality;

function queryIMEI(lockName) {
    return new Promise(async (resolve) => {
        const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
        parser.on('data', (data) => {
            logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMEI' });
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMEI');
            modemInfo.imei = data.toString().trim() || null;
            logger.info('IMEI extracted', { imei: modemInfo.imei });
            resolve(modemInfo.imei);
        });

        await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMEI');

        port.pipe(parser);
        await writeToPort('AT+CGSN\r');
    });
}
exports.queryIMEI = queryIMEI;

function queryIMSI(lockName) {
    return new Promise(async (resolve) => {
        const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
        parser.on('data', (data) => {
            logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMSI' });
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMSI');
            modemInfo.imsi = data.toString().trim() || null;
            logger.info('IMSI extracted', { imsi: modemInfo.imsi });
            resolve(modemInfo.imsi);
        });

        await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMSI');

        port.pipe(parser);
        await writeToPort('AT+CIMI\r');
    });
}
exports.queryIMSI = queryIMSI;

exports.queryIMEIAndIMSI = async function queryIMEIAndIMSI() {
    await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI');

    const imei = await queryIMEI(MUTEX_SUBCOMMAND);
    const imsi = await queryIMSI(MUTEX_SUBCOMMAND);

    await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
    return { imei, imsi };
};

exports.queryManufacturer = function queryManufacturer(lockName) {
    return new Promise(async (resolve) => {
        const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
        parser.on('data', (data) => {
            logger.verbose('INCOMING', { data: data.toString(), parser: 'parserManufacturer' });
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, 'parserManufacturer');
            modemInfo.manufacturer = data.toString().trim() || null;
            logger.info('Manufacturer extracted', { manufacturer: modemInfo.manufacturer });
            resolve(modemInfo.manufacturer);
        });

        await mutex.lock(lockName || MUTEX_COMMAND, 'queryManufacturer');

        port.pipe(parser);
        await writeToPort('AT+CGMI\r');
    });
};

exports.queryModel = function queryModel(lockName) {
    return new Promise(async (resolve) => {
        const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
        parser.on('data', (data) => {
            logger.verbose('INCOMING', { data: data.toString(), parser: 'parserModel' });
            port.unpipe(parser);
            mutex.unlock(lockName || MUTEX_COMMAND, 'parserModel');
            modemInfo.model = data.toString().trim() || null;
            logger.info('Model extracted', { model: modemInfo.model });
            resolve(modemInfo.model);
        });

        await mutex.lock(lockName || MUTEX_COMMAND, 'queryModel');

        port.pipe(parser);
        await writeToPort('AT+CGMM\r');
    });
};

async function sendCtrlZ() {
    await writeToPort(CTRLZ);
}
exports.sendCtrlZ = sendCtrlZ;

async function initATCommands() {
    await mutex.lock(MUTEX_COMMAND, 'INIT MODEM');
    await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND);
    await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
    await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND);
    mutex.unlock(MUTEX_COMMAND, 'INIT MODEM');
}
exports.initATCommands = initATCommands;

function sendCMGSPdu(pduLength) {
    return new Promise((resolve) => {
        const parser = new ParserReady({ delimiter: '>' });
        parser.on('data', () => {
            logger.verbose('Got ">" message prompt, gonna to write PDU message');
            port.unpipe(parser);
            mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
            resolve(true);
        });

        mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
        port.pipe(parser);
        writeToPort(`AT+CMGS=${pduLength}\r`);
    });
}

exports.sendSMS = function sendSMS(destination, msg) {
    return new Promise(async (resolve) => {
        async function responseHandler(data) {
            logger.verbose('SMS sent callback called', { data });

            if (data.indexOf('ERROR') >= 0 || data.indexOf('+CMS ERROR') >= 0 || data.indexOf('+CMGS') >= 0) {
                logger.verbose('SMS sent');
                parsers.setSmsSentCallback(null);
                mutex.unlock(MUTEX_COMMAND, 'sendSMS');
                resolve(data.indexOf('ERROR') >= 0 ? null : data.toString().trim());
            }
        }

        await mutex.lock(MUTEX_COMMAND, 'sendSMS');

        if (!destination || !destination.trim()) {
            resolve(false);
            return;
        }

        if (!msg || !msg.trim()) {
            resolve(false);
            return;
        }

        const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+');
        logger.verbose(`Sending sms to ${correctedDestination}`, { msg });

        await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);

        const submit = pdu.Submit();
        submit.setAddress(correctedDestination);
        submit.setData(msg.trim());
        submit.getType().setSrr(0);

        await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1);
        // await writeToPortAndWaitForOkOrError(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);

        parsers.setSmsSentCallback(responseHandler);
        await writeToPort(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);
    });
};

exports.executeUSSD = function executeUSSD(code, _includeCUSD2, _sessionId) {
    return new Promise(async (resolve) => {
        const includeCUSD2 = _includeCUSD2 || 0;
        const sessionId = _sessionId || uuidv1();

        async function responseHandler(data) {
            logger.verbose('Processing USSD response', { data });
            parsers.setUssdCallback(null);

            if (includeCUSD2 === 1 || includeCUSD2 === 2) {
                await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
            }

            mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
            resolve(data);
        }

        mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
        parsers.setUssdCallback(responseHandler);

        await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND);

        if (includeCUSD2 === -1 || includeCUSD2 === 2) {
            await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
        }

        await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND);
    });
};