modem.js 6.99 KB
'use strict';

const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 60000;
const DELIMITER_WAIT_FOR_OK = '\nOK\r\n';

const SerialPort = require('serialport');
const ParserReadline = require('@serialport/parser-readline');
const ParserDelimiter = require('@serialport/parser-delimiter');

const config = require('komodo-sdk/config');
const logger = require('komodo-sdk/logger');

const mutex = require('./mutex');
const common = require('./common');
const sms = require('./sms');
const dbCops = require('./db-cops');
const reportSender = require('./report-sender');
const msisdn = require('./msisdn');

const modemInfo = {
    manufacturer: null,
    model: null,
    imei: null,
    imsi: null,
    msisdn: null,
    cops: null,
    networkId: null,
    networkName: null,
    signalStrength: null,
    config: config.modem,
};

const port = new SerialPort(config.modem.device, { baudRate: 115200 });

const parserReadLine = new ParserReadline();
const parserWaitForOK = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK });
port.pipe(parserReadLine);

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

async function writeToPortAndWaitForOK(data) {
    await mutex.setLockWaitForOK();
    const result = await writeToPort(data);
    await mutex.setLockWaitForOK();
    mutex.releaseLockWaitForOK();
    return result;
}

async function readSMS(slot) {
    const parser = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK });
    parser.on('data', async (data) => {
        if (data) {
            reportSender.incomingSMS(sms.extract(data.toString().trim()));
        }
        mutex.releaseLockWaitForOK();
    });

    logger.info(`Reading SMS on slot ${slot}`);
    port.pipe(parser);
    await writeToPortAndWaitForOK(`AT+CMGR=${slot}\r`);
    port.unpipe(parser);
    logger.verbose(`Finished reading SMS on slot ${slot}`);

    logger.info(`Deleting message on slot ${slot}`);
    port.pipe(parserWaitForOK);
    await writeToPortAndWaitForOK(`AT+CMGD=${slot}\r`);
    port.unpipe(parserWaitForOK);

    logger.info('Message processing has completed');
}

function onIncomingSMS(data) {
    const value = common.extractValueFromReadLineData(data);
    if (!value) return;

    const chunks = value.split(',');
    if (!chunks && !chunks[1]) return;

    const slot = chunks[1];

    logger.info(`Incoming SMS on slot ${slot}`);
    readSMS(slot);
}

function onCOPS(data) {
    modemInfo.cops = common.extractValueFromReadLineData(data).trim();
    logger.info(`Connected Network: ${modemInfo.cops}`);

    if (!modemInfo.cops) return;

    [, , modemInfo.networkId] = modemInfo.cops.split(',');

    if (modemInfo.networkId) {
        modemInfo.networkName = dbCops[modemInfo.networkId];
    }
}

parserReadLine.on('data', (data) => {
    logger.verbose(`* IN: ${data}`);
    if (data) {
        if (data.indexOf('+CSQ: ') === 0) {
            modemInfo.signalStrength = common.extractValueFromReadLineData(data).trim();
            logger.info(`Signal strength: ${modemInfo.signalStrength}`);
        } else if (data.indexOf('+CMTI: ') === 0) {
            onIncomingSMS(data);
        } else if (data.indexOf('+COPS: ') === 0) {
            onCOPS(data);
        }
    }
});

parserWaitForOK.on('data', () => {
    mutex.releaseLockWaitForOK();
});

function simpleCommand(cmd, callback) {
    const parser = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK });
    parser.on('data', (data) => {
        port.unpipe(parser);
        mutex.releaseLockWaitForOK();

        if (data) {
            callback(null, data.toString().trim());
        }
    });

    port.pipe(parser);
    writeToPortAndWaitForOK(cmd);
}

function readManufacturer() {
    return new Promise((resolve) => {
        simpleCommand('AT+CGMI\r', (err, result) => {
            modemInfo.manufacturer = result;
            logger.info(`Manufacturer: ${result}`);
            resolve(result);
        });
    });
}

function readModel() {
    return new Promise((resolve) => {
        simpleCommand('AT+CGMM\r', (err, result) => {
            modemInfo.model = result;
            logger.info(`Model: ${result}`);
            resolve(result);
        });
    });
}

function readIMEI() {
    return new Promise((resolve) => {
        simpleCommand('AT+CGSN\r', (err, result) => {
            modemInfo.imei = result;
            logger.info(`IMEI: ${result}`);
            resolve(result);
        });
    });
}

function readIMSI() {
    return new Promise((resolve) => {
        simpleCommand('AT+CIMI\r', (err, result) => {
            modemInfo.imsi = result;
            logger.info(`IMSI: ${result}`);

            if (result) {
                modemInfo.msisdn = msisdn[result];
                if (modemInfo.msisdn) {
                    logger.info(`MSISDN: ${modemInfo.msisdn}`);
                }
            }
            resolve(result);
        });
    });
}

async function querySignalStrength() {
    port.pipe(parserWaitForOK);
    await writeToPortAndWaitForOK('AT+CSQ\r');
    port.unpipe(parserWaitForOK);
}

async function registerSignalStrengthBackgroundQuery() {
    logger.info('Registering background signal strength query');

    setInterval(() => {
        querySignalStrength();
    }, INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS);
}

async function sendSMS(destination, msg) {
    if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return;

    await mutex.setLockWaitForCommand();
    logger.info('Sending message', { destination, msg });

    const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+');

    port.pipe(parserWaitForOK);
    await writeToPortAndWaitForOK('AT+CMGF=1\r');
    await writeToPortAndWaitForOK(`AT+CMGS="${correctedDestination}"\n${msg}${Buffer.from([0x1A])}`);
    port.unpipe(parserWaitForOK);

    logger.info('Message has been sent');

    mutex.releaseLockWaitForCommand();
}

function init() {
    port.on('open', async () => {
        port.pipe(parserWaitForOK);

        logger.info('Modem opened');
        await writeToPortAndWaitForOK('AT\r');

        logger.info('Initializing modem to factory set');
        await writeToPortAndWaitForOK('AT&F\r');

        logger.info('Disabling echo');
        await writeToPortAndWaitForOK('ATE0\r');

        await writeToPortAndWaitForOK('AT+COPS?\r');

        logger.info('Querying signal strength');
        await writeToPortAndWaitForOK('AT+CSQ\r');

        await readManufacturer();
        await readModel();
        await readIMEI();
        await readIMSI();

        if (!config.disable_delete_inbox_on_startup) {
            logger.info('Deleting existing messages');
            await writeToPortAndWaitForOK('AT+CMGD=0,4\r');
        }

        port.unpipe(parserWaitForOK);

        registerSignalStrengthBackgroundQuery();
        logger.verbose('Init completed');
    });
}

init();

exports.modemInfo = modemInfo;
exports.sendSMS = sendSMS;