UNPKG

modemjs

Version:

NPM package to simplify sending and receiving SMS with a GSM Modem on Node.js

312 lines (311 loc) 13.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var ramda_1 = require("ramda"); var rxjs_1 = require("rxjs"); var operators_1 = require("rxjs/operators"); var serialport_1 = __importDefault(require("serialport")); // tslint:disable-next-line: no-var-requires var Readline = require('@serialport/parser-readline'); var notNull = function (value) { return value !== null; }; var defaultModemInitCommands = [ '\u241bAT', 'AT+CMGF=1', 'AT+CNMI=1,1,0,1,0', 'AT+CNMI=2', 'AT+CSMP=49,167,0,0', 'AT+CPMS="SM","SM","SM"', ]; var Modem = /** @class */ (function () { function Modem(modemCfg, errorCallback) { var _this = this; this.log$ = new rxjs_1.Subject(); this.status$ = new rxjs_1.BehaviorSubject({ connected: false, debugMode: false, error: false, }); this.currentTask = null; this.taskStack = []; this.tasksCounter = 0; this.data$ = new rxjs_1.Subject(); this.error$ = new rxjs_1.Subject(); this.port = new serialport_1.default(modemCfg.port, { baudRate: modemCfg.baudRate, autoOpen: false }); this.updateStatus({ debugMode: modemCfg.debugMode ? true : false }); this.msPause = modemCfg.msPause; this.initCommands = modemCfg.initCommands; this.port.on('close', function (_a) { var disconnected = _a.disconnected; if (disconnected) { _this.updateStatus({ connected: false, error: true }); _this.error$.next('Modem disconnected'); } }); this.port.pipe(new Readline({ delimiter: '>' })).on('data', function () { _this.currentTask = null; _this.nextTaskExecute(); }); this.port.pipe(new Readline({ delimiter: '\r\n' })).on('data', function (receivedData) { if (receivedData) { _this.data$.next(ramda_1.clone(receivedData)); } }); this.data$ .pipe( // logs receivedData from modem operators_1.tap(function (receivedData) { return _this.log$.next(receivedData); }), operators_1.tap(function (receivedData) { if (receivedData.includes('ERROR')) { _this.error$.next(receivedData); } }), operators_1.tap(function (receivedData) { _this.status$.subscribe(function (status) { if (status.debugMode) { // tslint:disable-next-line: no-console console.log('\r\n\r\n------------------modem says-------------------------'); // tslint:disable-next-line: no-console console.log(receivedData .replace('\r', '<CR>') .replace('\n', '<LF>') .replace('\x1b', '<ESC>') .replace('\x1A', '<CTRL-Z>')); // tslint:disable-next-line: no-console console.log('\r\n'); } }); }), // verify modem answer, remove currentTask and start nextTaskExecute() operators_1.tap(function (receivedData) { if (!_this.currentTask && !_this.taskStack) { return; } if (!_this.currentTask && _this.taskStack) { return; } if (_this.currentTask && !_this.taskStack) { if (receivedData.includes(_this.currentTask.expectedResult)) { _this.currentTask.onResultFn(receivedData); _this.currentTask = null; } return; } if (_this.currentTask && _this.taskStack) { if (receivedData.includes(_this.currentTask.expectedResult)) { _this.currentTask.onResultFn(receivedData); _this.currentTask = null; _this.nextTaskExecute(); } return; } })) .subscribe(); this.port.on('error', this.handleError); this.error$ .pipe(operators_1.tap(function (err) { _this.updateStatus({ error: true }); // tslint:disable-next-line: no-console console.log('Modem error:', err); }), // logs err operators_1.tap(function (err) { return _this.log$.next(err); })) .subscribe(); this.port.on('open', function (err) { if (err) { _this.error$.next(err); _this.updateStatus({ connected: false, error: true }); return; } _this.updateStatus({ connected: true }); }); // init modem if (typeof modemCfg.autoOpen === 'undefined' || modemCfg.autoOpen === true) { this.init(errorCallback); } } Modem.prototype.init = function (errorCallback) { var _this = this; var modemInitComands = this.initCommands || defaultModemInitCommands; this.port.open(errorCallback); // init modem modemInitComands.forEach(function (command) { _this.addTask({ description: command, expectedResult: 'OK', fn: function () { return _this.port.write(command + "\r", _this.handleError); }, id: _this.generateTaskID(), onResultFn: function (x) { return null; }, }); }); this.nextTaskExecute(); }; Modem.prototype.onReceivedSMS = function () { var _this = this; var readingSMS = false; var newSMS = { id: 0, phoneNumber: null, submitTime: null, text: '', }; return this.data$.pipe(operators_1.tap(function (data) { if (data.includes('+CMTI:')) { newSMS.id = +data.split(',')[1]; _this.addTask({ description: "AT+CMGR=" + +data.split(',')[1], expectedResult: '+CMGR:', fn: function () { return _this.port.write("AT+CMGR=" + +data.split(',')[1] + "\r", _this.handleError); }, id: _this.generateTaskID(), onResultFn: function (x) { return null; }, }); _this.nextTaskExecute(); } if (data.includes('+CMGR:')) { readingSMS = true; } }), operators_1.filter(function (data) { return readingSMS; }), operators_1.tap(function (data) { if (data.includes('OK')) { readingSMS = false; } }), operators_1.map(function (data) { if (data.includes('OK')) { newSMS.text = newSMS.text ? newSMS.text.trim() : ''; return newSMS; } if (data.includes('+CMGR:')) { var today = new Date(); var cmgr = data .replace(/\+CMGR\:\ /gi, '') .replace(/\"/gi, '') .replace(/\//gi, '-') .split(','); newSMS.phoneNumber = +cmgr[1].replace('+', '00').replace(/00351/, ''); newSMS.submitTime = new Date(today.getFullYear() + cmgr[3].slice(2) + 'T' + cmgr[4].replace('+', '.000+') + ':00'); return null; } newSMS.text = newSMS.text ? newSMS.text + data : data; return null; }), operators_1.filter(notNull), operators_1.tap(function () { newSMS = { id: 0, phoneNumber: null, submitTime: null, text: '', }; }), operators_1.tap(function (_a) { var id = _a.id; _this.addTask({ description: "AT+CMGD=" + id, expectedResult: 'OK', fn: function () { return _this.port.write("AT+CMGD=" + id + "\r", _this.handleError); }, id: _this.generateTaskID(), onResultFn: function (x) { return null; }, }); _this.nextTaskExecute(); })); }; Modem.prototype.sendSMS = function (_a) { var _this = this; var phoneNumber = _a.phoneNumber, text = _a.text; var smsInfo$ = new rxjs_1.BehaviorSubject(null); var cmgsNumber; this.error$.pipe(operators_1.take(1)).subscribe(function (n) { return smsInfo$.error(n); }); this.addTask({ description: "AT+CMGS=\"" + phoneNumber + "\"", expectedResult: 'notImportant', fn: function () { setTimeout(function () { _this.port.write("AT+CMGS=\"" + phoneNumber + "\"\r", _this.handleError); }, _this.msPause); }, id: this.generateTaskID(), onResultFn: function (x) { return null; }, }); this.addTask({ description: text + "\u001A", expectedResult: '+CMGS:', fn: function () { return _this.port.write(text + "\u001A", _this.handleError); }, id: this.generateTaskID(), onResultFn: function (receivedData) { smsInfo$.next(receivedData); }, }); this.nextTaskExecute(); return smsInfo$.pipe(operators_1.filter(notNull), operators_1.filter(function (data) { return data.includes('+CMGS:'); }), operators_1.tap(function (data) { cmgsNumber = parseInt(data.split(':')[1], 10); }), operators_1.concatMap(function () { return _this.data$; }), operators_1.filter(function (data) { return data.includes('+CDS:'); }), operators_1.filter(function (data) { return parseInt(data.split(',')[1], 10) === cmgsNumber; }), // convert +CDS string to DeliveredSMSReport object operators_1.map(function (data) { // data = '+CDS: 6,238,"910000000",129,"19/12/21,00:04:39+00","19/12/21,00:04:41+00",0' var cds = data .replace(/\+CDS\:\ /gi, '') .replace(/\"/gi, '') .replace(/\//gi, '-') .split(','); // cds = '6,238,910000000,129,19-12-21,00:04:39+00,19-12-21,00:04:41+00,0' var today = new Date(); var report = { deliveryTime: new Date(today.getFullYear() + cds[6].slice(2) + 'T' + cds[7].replace('+', '.000+') + ':00'), firstOctet: +cds[0], id: +cds[1], phoneNumber: +cds[2], st: +cds[8], submitTime: new Date(today.getFullYear() + cds[4].slice(2) + 'T' + cds[5].replace('+', '.000+') + ':00'), }; // report = { firstOctet: 6, id: 238, phoneNumber: 910000000, submitTime: "2019-12-21T00:04:39.000Z", deliveryTime: "2019-12-21T00:04:41.000Z", 0 } return report; }), operators_1.takeWhile(function (_a) { var st = _a.st; return st !== 0; }, true)); }; Modem.prototype.addTask = function (task) { this.taskStack = __spreadArrays(this.taskStack, [task]); }; Modem.prototype.generateTaskID = function () { this.tasksCounter = this.tasksCounter + 1; return this.tasksCounter; }; Modem.prototype.handleError = function (err) { if (err) { this.error$.next(ramda_1.clone(err)); } }; Modem.prototype.nextTaskExecute = function () { if (this.currentTask) { return; } if (!this.taskStack[0]) { return; } this.currentTask = ramda_1.clone(this.taskStack[0]); this.taskStack = ramda_1.clone(this.taskStack.slice(1)); this.currentTask.fn(); }; Modem.prototype.updateStatus = function (patch) { var _this = this; this.status$.pipe(operators_1.take(1)).subscribe(function (status) { _this.status$.next(__assign(__assign({}, status), patch)); }); }; return Modem; }()); exports.Modem = Modem;