'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;