/** * 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); }); };