index.js 6.18 KB
"use strict";

const fs = require('fs');
const EventEmitter = require('events');
const os = require('os');

const moment = require('moment');

const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
const Delimiter = require('@serialport/parser-delimiter');
const jsesc = require('jsesc');

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

const smsParser = require('./sms-parser');

const SIGNAL_STRENGTH_INTERVAL_MS = 60 * 1000;

fs.existsSync('logs/') || fs.mkdirSync('logs/');
const debugLogWriter = process.env.KOMODO_DEBUG_MODEM ? fs.createWriteStream('logs/log-debug-modem.txt', { flags: 'a' }) : null;

function debugLog(msg) {
    if (debugLogWriter) {
        debugLogWriter.write(msg + os.EOL);
    }
}


class Modem extends EventEmitter {
    constructor(portName, portOptions) {
        super();
        this.portName = portName;
        this.portOptions = portOptions;
    }

    open(cb) {
        const self = this;

        debugLog('MODEM: opening ' + this.portName);
        this.port = new SerialPort(this.portName, this.portOptions);

        this.port.on('error', function(err) {
            debugLog('MODEM: error opening ' + this.portName);
            if (cb) cb(err);
        });

        this.port.on('open', function() {
            self.resetModem(function() {
                self.getIMSI(function() {
                    self.emit('open');
                    self.getSignalStrength(cb);

                    setInterval(function() {
                        self.getSignalStrength();
                    }, SIGNAL_STRENGTH_INTERVAL_MS)

                })
            });

        });


    }

    resetParserToDefault() {
        const self = this;
        //this.port.unpipe(this.parser);
        if (this.parser) { this.port.unpipe(this.parser)};
        this.parser = this.port.pipe(new Readline());

        this.parser.on('data', function(data) {
            if (!data) return;

            debugLog('PARSER-DEFAULT: ' + data);

            if (data.indexOf('+CMTI:') === 0) {
                self.onSMS(data);
            }
            else if (data.indexOf('+CUSD:') === 0) {
                self.onUSSDResponse(data);
            }
            if (data.indexOf('+CSQ:') === 0) {
                debugLog('*** Got CSQ signal strength response');
                self.onSignalStrength(data);
            }

        });
    }

    _write(...args) {
        debugLog('COMMAND: ' + args[0]);
        this.port.write(...args);
        this.port.drain();
    }

    write(data) {
        debugLog('COMMAND: ' + data);
        this.port.write(data);
        this.port.drain();
    }

    getPort() {
        return this.port;
    }

    onUSSDResponse(data) {
        debugLog('USSD-RESPONSE: ' + data);
        this.emit('ussd response', data);
    }

    onSMS(data) {
        const self = this;
        const port = this.port;

        const matches = data.match(/,(\d+)\s*$/);
        if (!matches || matches.length < 2) {
            return;
        }

        const slot = matches[1];
        //console.log('*** Ada SMS masuk di slot ' + slot);

        if (!slot) {
            debugLog('*** Gagal deteksi slot sms')
            return;
        }

        if (this.parser) { this.port.unpipe(this.parser)};
        const parser = this.port.pipe(new Delimiter({ delimiter: '\r\nOK\r\n' }))

        parser.on('data', function(data) {
            self.parseSMS(data);
            self.port.unpipe(parser);
            self.resetParserToDefault();

            self.write('AT+CMGD=' + slot + '\r');
        })

        this.write('AT+CMGR=' + slot + '\r');
    }

    parseSMS(data) {
        data = data.toString().trim();
        //debugLog(jsesc(data, {wrap: true}));
        debugLog('SMS-READ: ' + data);
        const sms = smsParser.parseModemResponse(data);
        this.emit('incoming sms', sms);
    }

    resetModem(cb) {
        const self = this;

        if (this.parser) { this.port.unpipe(this.parser)};
        const parser = this.port.pipe(new Delimiter({ delimiter: '\nOK\r\n' }));

        parser.on('data', function(data) {
            const value = data.toString().replace(/^\s+/, '').replace(/\s+$/, '');

            debugLog('PARSER-RESETMODEM: modem reseted');

            self.port.unpipe(parser);
            self.resetParserToDefault();

            if (cb) {
                cb(null, value);
            }
        })

        this.write('AT &F Z E0\r');
    }

    getIMSI(cb) {
        const self = this;
        const port = this.port;

        if (this.parser) { this.port.unpipe(this.parser)};
        const parser = this.port.pipe(new Delimiter({ delimiter: '\r\nOK\r\n' }))

        parser.on('data', function(data) {
            self.imsi = data.toString().replace(/^\s+/, '').replace(/\s+$/, '');
            debugLog('PARSER-IMSI: ' + self.imsi);

            self.port.unpipe(parser);
            self.resetParserToDefault();

            self.emit('imsi', self.imsi);

            if (cb) {
                cb(null, self.imsi);
            }
        })

        this.write('AT+CIMI\r');
    }

    getCOPS(cb) {
        const self = this;

        /*
        const parser = this.port.pipe(new Delimiter({ delimiter: '\r\nOK\r\n' }))

        parser.on('data', function(data) {
            const value = data.toString().replace(/^\s+/, '').replace(/\s+$/, '');
            debugLog('PARSER-COPS: ' + value);
            self.port.unpipe(parser);
            if (cb) {
                cb(null, value);
            }
        })
        */

        //console.log('MODEM: sending AT+COPS?');
        this.write('AT+COPS?\r');
    }

    sendUSSD(cmd, cb) {
        const self = this;
        const port = this.port;

        //console.log('MODEM: ' + moment().format('HH:mm:ss') + ' *** Sending USSD command ' + cmd);
        this.write('AT+CUSD=1,"' + cmd + '",15\r');
    }

    onSignalStrength(data) {
        if (typeof data !== 'string') {
            debugLog('*** onSignalStrength data is not a string');
            return;
        }
        const matches = data.match(/: (\d+),/);
        if (matches.length < 2) return;

        this.emit('signal strength', Number(matches[1]));
    }

    getSignalStrength(cb) {
        this.write('AT+CSQ\r');

        if (cb) { cb() };
    }
}

module.exports = Modem;