modemjs
Version:
NPM package to simplify sending and receiving SMS with a GSM Modem on Node.js
312 lines (311 loc) • 13.3 kB
JavaScript
;
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;