/** * Modul modem-commands * * @module modem-commands */ /** * Label mutex command * @static */ const MUTEX_COMMAND = 'COMMAND'; /** * Label mutex subcommand * @static */ const MUTEX_SUBCOMMAND = 'SUBCOMMAND'; /** * CTRL-Z string * @static */ const CTRLZ = '\u001a'; 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) => { const port = global.MODEM_PORT; 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); }); }); } function writeToPortAndWaitForReadline(cmd, lockName) { const port = global.MODEM_PORT; 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); }); } function writeToPortAndWaitForOkOrError(cmd, lockName) { return new Promise(async (resolve) => { const port = global.MODEM_PORT; 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); }); } /** * Sleep async * @static * @param {number} ms - Milliseconds to sleep * @return {Promise} */ function sleep(ms) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, ms || 0); }); } /** * Set port * @static * @param {SerialPort} val */ /* function setPort(val) { // port = val || global.MODEM_PORT; } */ 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); }); } 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); }); } 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); }); } function queryIMEI(lockName) { return new Promise(async (resolve) => { const port = global.MODEM_PORT; 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'); }); } function queryIMSI(lockName) { return new Promise(async (resolve) => { const port = global.MODEM_PORT; 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'); }); } 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 }; } function queryManufacturer(lockName) { return new Promise(async (resolve) => { const port = global.MODEM_PORT; 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'); }); } function queryModel(lockName) { return new Promise(async (resolve) => { const port = global.MODEM_PORT; 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'); }); } /** * Menulis CTRL-Z ke port. * @static */ async function sendCtrlZ() { await writeToPort(CTRLZ); } 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'); } /** * Menulis awal pesan PDU. * * @param {number} pduLength */ function sendCMGSPdu(pduLength) { return new Promise((resolve) => { const port = global.MODEM_PORT; 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`); }); } /** * Mengirim sms * @param {string} destination - nomor tujuan * @param {string} msg - isi pesan * @return {Promise} * @static */ 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); }); } /** * Ekseksusi kode USSD. * <br> * <br>Pilihan includeCUSD2: * <br>-1: sebelum * <br>0: tidak (default) * <br>1: sesudah * <br>2: sebelum dan sesudah * * @static * @param {string} code - Kode USSD * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2 * @return {Promise} */ 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); }); } exports.MUTEX_COMMAND = MUTEX_COMMAND; exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND; exports.CTRLZ = CTRLZ; /** * Modem info. * @type {object} */ exports.modemInfo = modemInfo; // exports.setPort = setPort; exports.writeToPort = writeToPort; exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline; exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError; exports.sleep = sleep; exports.querySignalQuality = querySignalQuality; exports.queryCOPS = queryCOPS; exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality; exports.queryIMEI = queryIMEI; exports.queryIMSI = queryIMSI; exports.queryIMEIAndIMSI = queryIMEIAndIMSI; exports.queryManufacturer = queryManufacturer; exports.queryModel = queryModel; exports.sendCtrlZ = sendCtrlZ; exports.initATCommands = initATCommands; exports.sendSMS = sendSMS; exports.executeUSSD = executeUSSD;