UNPKG

simcom

Version:

Talk to GSM modem SIMCOM via Node

452 lines (376 loc) 10.1 kB
// vim: ts=2 expandtab var util = require('util'), Q = require('q'), pdu = require('pdu'), EventEmitter = require('events').EventEmitter; function SimCom(device, options) { this.modem = require('./modem')(device, options); var self = this; // delegates modem events ['open', 'close', 'error', 'ring', 'end ring', 'over-voltage warnning'].forEach(function(e) { self.modem.on(e, function() { var args = Array.prototype.slice.call(arguments); args.unshift(e); self.emit.apply(self, args); }); }); this.modem.on('new message', handleNewMessage.bind(this)); this.modem.on('ussd', handleUSSD.bind(this)); this.modem.open(); } util.inherits(SimCom, EventEmitter); SimCom.prototype.close = function() { this.modem.close(); } /** * Execute a Raw AT Command * @param command Raw AT Command * @returns Promise */ SimCom.prototype.execute = function(command) { return this.modem.execute(command); } var simple_methods = { productID: 'ATI', manufacturerID: 'AT+GMI', modelID: 'AT+GMM', globalID: 'AT+GOI', IMEI: 'AT+GSN', subscriberID: 'AT+CIMI', } Object.keys(simple_methods).forEach(function(name) { SimCom.prototype[name] = function() { var defer = Q.defer(); this.execute(simple_methods[name]) .then(function(res) { defer.resolve(res.lines.shift()); }) .catch(function(res) { defer.reject(res); }); return defer.promise; }; }); function parse(s) { var quoted = false; var item = ''; var items = []; for (var i = 0; i < s.length; i++) { var valid = false; switch (s[i]) { case '"': quoted = !quoted; break; case ',': valid = quoted; if (!quoted) { items.push(item); item = ''; } break; default: valid = true; } if (valid) item += s[i]; } if (item) { items.push(item); } return items; } function handleNewMessage(m) { m = parse(m); m = { storage: m[0], index: Number(m[1]), type: m.length > 2 ? m[2] : 'SMS' }; this.emit('new message', m); } function handleUSSD(m) { m = parse(m).map(function(e) { return e.trim(); }); m = { type: Number(m[0]), str: m[1], dcs: Number(m[2]) }; m.str = m.dcs == 72 ? pdu.decode16Bit(m.str) : pdu.decode7Bit(m.str); this.emit('ussd', m); } SimCom.extractResponse = SimCom.prototype.extractResponse = function(resp, readPDU) { if (!resp || !resp.command || !resp.lines || !resp.lines.length) { return; } var cmd = resp.command.match(/^AT([^\=\?]*)/); if (!cmd || cmd.length < 2) { return; } cmd = cmd[1]; var result = []; var needPDU = false; var pduResponse = null; var cmdMatched = false; for (var i = 0; i < resp.lines.length; i++) { var line = resp.lines[i]; if (line == '') { cmdMatched = false; continue; } if (!needPDU) { if (!cmdMatched) { if (line.substr(0, cmd.length) == cmd) { var tokens = line.substr(cmd.length).match(/(\:\s*)*(.+)*/); if (tokens && tokens.length > 2) { line = tokens[2]; cmdMatched = true; } } } if (cmdMatched) { if (line) { if (!readPDU) { result.push(line); } else { pduResponse = { response: line, pdu: null }; } } needPDU = readPDU; } } else { pduResponse.pdu = line; result.push(pduResponse); needPDU = false; } } return result; } /** * Invoke a RAW AT Command, Catch and process the responses. * @param command RAW AT Command * @param resultReader Callback for processing the responses * @param readPDU Try to read PDU from responses * @returns Promise */ SimCom.prototype.invoke = function(command, resultReader, readPDU) { var defer = Q.defer(); var self = this; this.execute(command).then(function(res) { var result = SimCom.extractResponse(res, readPDU) || null; if (resultReader) { result = resultReader.call(self, result); } defer.resolve(result); }).catch(function(error) { defer.reject(error); }); return defer.promise; } SimCom.prototype.serviceProvider = function() { return this.invoke('AT+CSPN?', function(lines) { return lines.shift().match(/"([^"]*)"/).pop(); }); } SimCom.prototype.listSMS = function(stat) { return this.invoke('AT+CMGL=' + stat, function(res) { return res.map(function(m) { var infos = parse(m.response); return { index: Number(infos[0]), stat: infos[1], message: pdu.parse(m.pdu) }; }); }, true); } SimCom.prototype.readSMS = function(index, peek) { return this.invoke('AT+CMGR=' + index + (peek ? 0 : 1), function(res) { return pdu.parse(res.shift().pdu); }, true); } SimCom.prototype.sendSMS = function(receiver, text) { var p = pdu.generate({ encoding: '16bit', receiver: receiver, text: text }).shift(); var pduLength = (p.length/2)-1; return this.invoke({ command: 'AT+CMGS=' + pduLength, pdu: p }, function(res) { return res.shift(); }); } SimCom.prototype.setBearerParam = function(id, tag, value) { return this.invoke('AT+SAPBR=3,' + id + ',' + '"' + tag + '","' + value + '"'); } SimCom.prototype.setBearerParams = function(id, params) { var self = this; return Object.keys(params).reduce(function(d, k) { return d.then(function() { self.setBearerParam(id, k, params[k]); }); }, Q(0)); } SimCom.prototype.getBearerParams = function(id) { return this.invoke('AT+SAPBR=4,' + id, function(lines) { return lines.reduce(function(m, v) { v = v.split(':', 2); m[v[0].trim()] = v[1].trim(); return m; }, {}); }); } SimCom.prototype.activateBearer = function(id) { return this.invoke('AT+SAPBR=1,'+id); } SimCom.prototype.deactivateBearer = function(id) { return this.invoke('AT+SAPBR=0,'+id); } SimCom.prototype.queryBearer = function(id) { return this.invoke('AT+SAPBR=2,'+id, function(lines) { var line = lines.shift() || ''; var m = line.match(/(.+),(.+),\"([^"]*)/); var cid = Number(m[1]); var status_code = Number(m[2]); var status = status_code; var ip = m[3]; switch (status_code) { case 1: status = 'connected'; break; case 2: status = 'closing'; break; case 3: status = 'closed'; break; default: status = 'unknown'; } return { id: cid, status_code: status_code, status: status, ip: ip }; }); } SimCom.prototype.startBearer = function(id) { var self = this; return self.queryBearer(id).then(function(res) { if (!res || res.status_code != 1) { return self.activateBearer(id); } }); } SimCom.prototype.initMMS = function() { return this.invoke('AT+CMMSINIT'); } SimCom.prototype.terminateMMS = function() { return this.invoke('AT+CMMSTERM'); } SimCom.prototype.startMMS = function() { var self = this; return self.initMMS().then(null, function error() { return self.terminateMMS().then(function() { return self.initMMS(); }); } ); } SimCom.prototype.editMMS = function(edit) { return this.invoke('AT+CMMSEDIT=' + Number(edit || false)); } function downloadMMS(type, data, name) { if (!/^(PIC|TEXT|TITLE)$/i.test(type)) { throw new Error('Invalid MMS Download Type'); } if (data && data.length) { type = type.toUpperCase(); var timeout = Math.max(200000, Math.ceil(data.length/this.modem.options.baudrate*1000*8)); var param = '"' + type + '",' + data.length + ',' + timeout; if (name) { param += ',"' + name + '"'; } var self = this; return self.invoke('ATE1').then(function() { return self.invoke({ command: 'AT+CMMSDOWN=' + param, pdu: data }); }); } } SimCom.prototype.downloadMMSText = function(text, name) { return downloadMMS.call(this, 'TEXT', text, name); } SimCom.prototype.downloadMMSTitle = function(title) { return downloadMMS.call(this, 'TITLE', title); } SimCom.prototype.downloadMMSPicture = function(data, name) { return downloadMMS.call(this, 'PIC', data, name); } SimCom.prototype.setMMSRecipient = function(address) { return this.invoke('AT+CMMSRECP="' + address + '"'); } SimCom.prototype.viewMMS = function() { return this.invoke('AT+CMMSVIEW', function(lines) { var tokens = parse(lines.shift()); var files = lines.map(function(line) { var t = parse(line); return { index: Number(t[0]), name: t[1], type: (function() { switch (Number(t[2])) { case 2: return 'text'; case 3: return 'text/html'; case 4: return 'text/plain'; case 5: return 'image'; case 6: return 'image/gif'; case 7: return 'image/jpg'; case 8: return 'image/tif'; case 9: return 'image/png'; case 10: return 'smil'; default: return 'unknown' } })(), size: Number(t[3]) }; }); return { type: (function() { switch (Number(tokens[0])) { case 0: return 'received'; case 1: return 'sent'; case 2: return 'unsent'; default: return 'unknown' } })(), sender: tokens[1], to: tokens[2].split(';'), cc: tokens[3].split(';'), bcc: tokens[4].split(';'), datetime: Date(tokens[5] + tokens[6]), subject: tokens[8], files: files }; }); } SimCom.prototype.pushMMS = function() { return this.invoke('AT+CMMSSEND'); } SimCom.prototype.requestUSSD = function(ussd) { return this.invoke('AT+CUSD=1,"' + ussd + '"'); } module.exports = SimCom;