/** * Modul modem. * * Modul ini sedang proses pengalihan ke module:bootstrap * * @module * @deprecated going to move to module:bootstrap */ const DEFAULT_SLEEP_AFTER_SEND_SMS_MS = 2000; const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 30000; const MAX_LAST_DATA_AGE_MS = 3 * 60 * 1000; const REGEX_WAIT_FOR_OK_OR_ERROR = /\n(?:OK|ERROR)\r/; // const REGEX_WAIT_FOR_OK_OR_ERROR_USSD = /\n(?:OK|ERROR)\r/; const moment = require('moment'); const SerialPort = require('serialport'); const ParserReadline = require('@serialport/parser-readline'); // const ParserDelimiter = require('@serialport/parser-delimiter'); const ParserRegex = require('@serialport/parser-regex'); 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 registerModem = require('./register-modem'); const modemInfo = { device: config.modem.device, manufacturer: null, model: null, imei: null, imsi: null, msisdn: null, cops: null, networkId: null, networkName: null, signalStrength: null, signalStrengthTs: null, signalStrengthTsReadable: null, }; let lastTs = new Date(); let port; const parserReadLine = new ParserReadline(); const parserWaitForOK = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parserWaitForOK.on('data', () => { mutex.releaseLockWaitForCommand(); }); 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 readSMS(slot) { const parserCMGR = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parserCMGR.on('data', (data) => { if (data) { try { reportSender.incomingSMS(sms.extract(data.toString().trim()), modemInfo); } catch (e) { logger.warn(`Exception on reporting new message. ${e.toString()}`, { smsObj: e.smsObj, dataFromModem: data }); process.exit(0); } } port.unpipe(parserCMGR); mutex.releaseLockWaitForCommand(); }); // const parserCMGD = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK }); const parserCMGD = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parserCMGD.on('data', () => { port.unpipe(parserCMGD); mutex.releaseLockWaitForCommand(); }); logger.info(`Reading SMS on slot ${slot}`); await mutex.setLockWaitForCommand(); port.pipe(parserCMGR); await writeToPort(`AT+CMGR=${slot}\r`); logger.info(`Finished reading SMS on slot ${slot}`); logger.info(`Deleting message on slot ${slot}`); await mutex.setLockWaitForCommand(); port.pipe(parserCMGD); await writeToPort(`AT+CMGD=${slot}\r`); 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] || modemInfo.networkId; } } parserReadLine.on('data', (data) => { logger.verbose(`* IN: ${data}`); if (data) { lastTs = new Date(); if (data.indexOf('+CSQ: ') === 0) { const signalStrength = common.extractValueFromReadLineData(data).trim(); if (signalStrength) { modemInfo.signalStrength = signalStrength; modemInfo.signalStrengthTs = new Date(); modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss'); logger.info(`Signal strength: ${modemInfo.signalStrength}`); registerModem(modemInfo); } } else if (data.indexOf('+CMTI: ') === 0) { onIncomingSMS(data); } else if (data.indexOf('+COPS: ') === 0) { onCOPS(data); } } }); async function simpleSubCommand(cmd, callback) { const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parser.on('data', (data) => { port.unpipe(parser); mutex.releaseLockWaitForSubCommand(); if (data) { if (callback) callback(null, data.toString().trim()); } }); return new Promise(async (resolve) => { await mutex.setLockWaitForSubCommand(); port.pipe(parser); writeToPort(cmd); await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); resolve(); }); } function readManufacturer() { return new Promise((resolve) => { simpleSubCommand('AT+CGMI\r', (err, result) => { modemInfo.manufacturer = result; logger.info(`Manufacturer: ${result}`); resolve(result); }); }); } function readModel() { return new Promise((resolve) => { simpleSubCommand('AT+CGMM\r', (err, result) => { modemInfo.model = result; logger.info(`Model: ${result}`); resolve(result); }); }); } function readIMEI() { return new Promise((resolve) => { simpleSubCommand('AT+CGSN\r', (err, result) => { modemInfo.imei = result; logger.info(`IMEI: ${result}`); resolve(result); }); }); } function readIMSI() { return new Promise((resolve) => { simpleSubCommand('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}`); registerModem(modemInfo); } */ } else { logger.warn(`IMSI not detected. Please insert a sim card to your modem. Terminating ${config.modem.device}.`); process.exit(2); } resolve(result); }); }); } function readCOPS() { return new Promise((resolve) => { simpleSubCommand('AT+COPS?\r', (err, result) => { resolve(result); }); }); } function deleteInbox() { return new Promise((resolve) => { simpleSubCommand('AT+CMGD=0,4\r', (err, result) => { resolve(result); }); }); } async function querySignalStrength() { const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parser.on('data', () => { port.unpipe(parser); mutex.releaseLockWaitForCommand(); }); if (mutex.tryLockWaitForCommand()) { port.pipe(parser); await writeToPort('AT+CSQ\r'); } } function registerModemToCenterPeriodically() { registerModem(modemInfo); setInterval(() => { registerModem(modemInfo); }, 60 * 1000); } async function registerSignalStrengthBackgroundQuery() { logger.info('Registering background signal strength query'); querySignalStrength(); setInterval(() => { querySignalStrength(); }, config.interval_beetwen_signal_strength_ms || INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS); } async function sendSMS(destination, msg) { if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return; // const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); const parser = new ParserReadline({ delimiter: '\r\n' }); parser.on('data', () => { port.unpipe(parser); mutex.releaseLockWaitForSubCommand(); }); logger.verbose('Waiting for command lock to send message'); await mutex.setLockWaitForCommand(); logger.info('Preparing to send message', { destination, msg }); const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+'); logger.verbose('Waiting for lock before set SMS to text mode'); await mutex.setLockWaitForSubCommand(); port.pipe(parser); await writeToPort('AT+CMGF=1\r'); logger.verbose('Waiting for lock before writing message'); await mutex.setLockWaitForSubCommand(); port.pipe(parser); await writeToPort(`AT+CMGS="${correctedDestination}"\r`); await writeToPort(msg); await writeToPort(Buffer.from([0x1A])); await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); logger.info('Message has been sent'); setTimeout(() => { logger.verbose('Releasing command lock'); mutex.releaseLockWaitForCommand(); }, config.sleep_after_send_sms_ms || DEFAULT_SLEEP_AFTER_SEND_SMS_MS); } /** * Ekseksusi kode USSD. * * Pilihan includeCUSD2: * -1: sebelum * 0: tidak (default) * 1: sesudah * 2: sebelum dan sesudah * * @param {string} code - Kode USSD * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2 */ function executeUSSD(code, includeCUSD2) { return new Promise(async (resolve) => { const parserMain = new ParserReadline({ delimiter: '\r\n' }); // const parserMain = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parserMain.on('data', (data) => { if (!data || !data.toString().trim()) return; if (data.toString().trim() === 'OK') return; port.unpipe(parserMain); mutex.releaseLockWaitForSubCommand(); resolve(data); }); const parserCUSD2 = new ParserReadline({ delimiter: '\r\n' }); // const parserCUSD2 = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); parserCUSD2.on('data', () => { port.unpipe(parserCUSD2); mutex.releaseLockWaitForSubCommand(); }); logger.verbose('Waiting for command lock to execute USSD'); await mutex.setLockWaitForCommand(); if (includeCUSD2 === -1 || includeCUSD2 === 2) { await mutex.setLockWaitForSubCommand(); logger.info('Terminating existing USSD session'); port.pipe(parserCUSD2); await writeToPort('AT+CUSD=2\r'); } await mutex.setLockWaitForSubCommand(); logger.info(`Executing USSD code "${code}"`); port.pipe(parserMain); await writeToPort(`AT+CUSD=1,"${code}",15\r`); if (includeCUSD2 === 1 || includeCUSD2 === 2) { await mutex.setLockWaitForSubCommand(); logger.info('Terminating existing USSD session'); port.pipe(parserCUSD2); await writeToPort('AT+CUSD=2\r'); } await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); mutex.releaseLockWaitForCommand(); }); } function init() { port = new SerialPort(config.modem.device, { baudRate: 115200 }, (err) => { if (err) { logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`); process.exit(1); } registerModem(modemInfo); }); port.pipe(parserReadLine); setInterval(() => { if ((new Date() - lastTs) > MAX_LAST_DATA_AGE_MS) { logger.warn(`No data for more than ${MAX_LAST_DATA_AGE_MS} ms. Modem might be unresponsive. Terminating modem ${config.modem.device}.`); process.exit(0); } }, 30 * 1000); port.on('open', async () => { await mutex.setLockWaitForCommand(); logger.info('Modem opened'); await writeToPort('\r'); await simpleSubCommand('AT\r'); logger.info('Initializing modem to factory set'); await simpleSubCommand('AT&F\r'); logger.info('Disabling echo'); await simpleSubCommand('ATE0\r'); logger.info('Set to text mode'); await simpleSubCommand('AT+CMGF=1\r'); logger.info('Set message indication'); await simpleSubCommand('AT+CNMI=1,1,2,1,1\r'); await readCOPS(); await readManufacturer(); await readModel(); await readIMEI(); await readIMSI(); if (!config.disable_delete_inbox_on_startup) { logger.info('Deleting existing messages'); await deleteInbox(); } mutex.releaseLockWaitForCommand(); logger.verbose('Init completed'); registerModemToCenterPeriodically(); registerSignalStrengthBackgroundQuery(); }); } init(); exports.modemInfo = modemInfo; exports.sendSMS = sendSMS; exports.executeUSSD = executeUSSD;