├── .gitattributes ├── .gitignore ├── README.md ├── example ├── all_sms.js ├── lena.jpg ├── linux.jpg ├── mms.js ├── open.js ├── open_batch.js ├── read_sms.js ├── send_sms.js ├── sim800.js ├── urc.js └── ussd.js ├── index.js ├── lib ├── modem.js └── simcom.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear on external disk 36 | .Spotlight-V100 37 | .Trashes 38 | 39 | # Node 40 | node_modules 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simcom 2 | ============ 3 | 4 | Talk to GSM modem from SIMCOM via serial port using Node 5 | -- 6 | This module was written with Raspberry Pi aspect in mind, but it basically should work on all linux distros, it might work on Windows as well (not tested yet), you just need a serial port filesystem (/dev/ttyAMA0 for RPi). 7 | 8 | It has been tested on SIM800L module. 9 | 10 | # Installation 11 | ```sh 12 | npm install simcom 13 | ``` 14 | 15 | # Example 16 | 17 | ### Openning the port 18 | ```javascript 19 | var modem = require('simcom').modem('/dev/ttyAMA0'); 20 | 21 | modem.on('open', function() { 22 | // do something with the modem 23 | }); 24 | 25 | modem.error('error', function() { 26 | 27 | }); 28 | 29 | modem.open(); // open the port 30 | ``` 31 | 32 | The `modem` function is a factory function, it creates an instance of `Modem` class for a device if neccessary. 33 | 34 | ### Sending raw data 35 | Talk directly to the modem using `Modem.write()` method. 36 | ```javascript 37 | modem.write('ATI\r'); 38 | ``` 39 | 40 | ### Executing a command 41 | `Modem.execute()` returns a promise which you can easily setup callbacks to process the response or error. 42 | ```javascript 43 | modem.execute('ATI').then(function(lines) { 44 | console.log('ATI Response', lines); 45 | }, function(error) { 46 | console.error('ATI Command Error', error); 47 | }); 48 | ``` 49 | -------------------------------------------------------------------------------- /example/all_sms.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom, 2 | pdu = require('pdu'); 3 | 4 | var simcom = new SimCom('/dev/ttyAMA0'); 5 | simcom.on('open', function() { 6 | this.listSMS(4).then(function(res) { 7 | console.log(res); 8 | }) 9 | .done(function() { 10 | simcom.close(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /example/lena.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vittee/simcom/e837acda169d25d4988eac76682c9099e1218e0a/example/lena.jpg -------------------------------------------------------------------------------- /example/linux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vittee/simcom/e837acda169d25d4988eac76682c9099e1218e0a/example/linux.jpg -------------------------------------------------------------------------------- /example/mms.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom, 2 | Q = require('q'), 3 | fs = require('fs'); 4 | 5 | var simcom = new SimCom('/dev/ttyAMA0', { baudrate: 115200 }); 6 | simcom.on('open', function() { 7 | simcom.startMMS().then(function() { 8 | console.log('MMS OK'); 9 | 10 | return simcom.setBearerParams(1, { 11 | 'Contype': 'GPRS', 12 | 'APN': 'hmms' 13 | }); 14 | }) 15 | .then(function() { 16 | return simcom.getBearerParams(1); 17 | }) 18 | .then(function(res) { 19 | console.log('Bearer settings', res); 20 | return simcom.startBearer(1); 21 | }) 22 | .then(function(res) { 23 | console.log('Started'); 24 | return simcom.queryBearer(1); 25 | }) 26 | .then(function(res) { 27 | console.log('Bearer Query', res); 28 | return simcom.editMMS(true); 29 | }) 30 | .then(function(res) { 31 | return simcom.downloadMMSTitle('Test'); 32 | }) 33 | .then(function(res) { 34 | return Q.nfcall(fs.readFile, 'lena.jpg'); 35 | }) 36 | .then(function(jpg) { 37 | return simcom.downloadMMSPicture(jpg, 'test.jpg'); 38 | }) 39 | .then(function(res) { 40 | return simcom.setMMSRecipient('66866266103'); 41 | //return simcom.setMMSRecipient('66840946635'); 42 | }) 43 | .then(function(res) { 44 | return simcom.viewMMS(); 45 | }) 46 | .then(function(res) { 47 | console.log('View MMS:', res); 48 | console.log('Sending...'); 49 | return simcom.pushMMS(); 50 | }) 51 | .then(function(res) { 52 | console.log('Sent'); 53 | return simcom.editMMS(false); 54 | }) 55 | .then(function(res) { 56 | return simcom.terminateMMS(); 57 | }) 58 | .then(function(res) { 59 | console.log('Deactivating Bearer'); 60 | return simcom.deactivateBearer(1); 61 | }) 62 | .catch(function(error) { 63 | console.log('Error', error); 64 | }) 65 | .done(function() { 66 | console.log('done'); 67 | simcom.close(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /example/open.js: -------------------------------------------------------------------------------- 1 | var modem = require('../').modem('/dev/ttyAMA0'); 2 | 3 | modem.on('open', function() { 4 | console.log('port open'); 5 | 6 | modem.execute("ATI") 7 | .then(function(res) { 8 | console.log('#1', res.code, res.lines); 9 | return modem.execute("AT+COPS?"); 10 | }) 11 | .then(function(res) { 12 | console.log('#2', res.code, res.lines); 13 | }) 14 | .done(function() { 15 | modem.close(); 16 | }); 17 | 18 | }); 19 | 20 | modem.on('error', function(err) { 21 | console.error('ERR:', err); 22 | }); 23 | 24 | modem.open(); 25 | 26 | -------------------------------------------------------------------------------- /example/open_batch.js: -------------------------------------------------------------------------------- 1 | var modem = require('../').modem('/dev/ttyAMA0'); 2 | 3 | function catchError(e) { 4 | console.log('error', e); 5 | } 6 | 7 | modem.on('open', function() { 8 | console.log('port open'); 9 | 10 | modem.execute("ATI").then(function(res) { 11 | console.log('#1', res.code, res.lines); 12 | }) 13 | .catch(catchError); 14 | 15 | modem.execute("AT+COPS?").then(function(res) { 16 | console.log('#2', res.code, res.lines); 17 | }) 18 | .catch(catchError) 19 | .done(function() { 20 | modem.close(); 21 | }); 22 | 23 | 24 | }); 25 | 26 | modem.on('error', function(err) { 27 | console.error('ERR:', err); 28 | }); 29 | 30 | modem.open(); 31 | 32 | -------------------------------------------------------------------------------- /example/read_sms.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom, 2 | pdu = require('pdu'); 3 | 4 | var simcom = new SimCom('/dev/ttyAMA0'); 5 | simcom.on('open', function() { 6 | this.readSMS(1,1).then(function(res) { 7 | console.log(res); 8 | }) 9 | .done(function() { 10 | simcom.close(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /example/send_sms.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom, 2 | pdu = require('pdu'); 3 | 4 | if (process.argv.length - 2 < 2) { 5 | console.log('Usage: ' + process.argv[1] + ' '); 6 | process.exit(1); 7 | return; 8 | } 9 | 10 | var simcom = new SimCom('/dev/ttyAMA0'); 11 | simcom.on('open', function() { 12 | var count = process.argv[4] || 1; 13 | var promise = null; 14 | 15 | for (var i = 0; i < count; i++) { 16 | promise = this.sendSMS(process.argv[2], process.argv[3]); 17 | 18 | promise.then(function(res) { 19 | console.log( res); 20 | }).catch(function(error) { 21 | console.log('ERR', error); 22 | }); 23 | } 24 | 25 | promise.done(function() { 26 | simcom.close(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /example/sim800.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom; 2 | 3 | var simcom = new SimCom('/dev/ttyAMA0'); 4 | simcom.on('open', function() { 5 | var info = {}; 6 | 7 | simcom.productID().then(function(res) { 8 | info.product = res; 9 | return simcom.manufacturerID(); 10 | }).then(function(res) { 11 | info.manufacturer = res; 12 | return simcom.modelID(); 13 | }).then(function(res) { 14 | info.model = res; 15 | return simcom.globalID(); 16 | }).then(function(res) { 17 | info.globalID = res; 18 | return simcom.IMEI(); 19 | }).then(function(res) { 20 | info.IMEI = res; 21 | return simcom.subscriberID(); 22 | }).then(function(res) { 23 | info.subscriberID = res; 24 | return simcom.serviceProvider(); 25 | }).then(function(res) { 26 | info.serviceProvider = res; 27 | }) 28 | .catch(function(error) { 29 | console.log('error', error); 30 | }) 31 | .done(function() { 32 | console.log(info); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /example/urc.js: -------------------------------------------------------------------------------- 1 | var modem = require('../').modem('/dev/ttyAMA0'); 2 | 3 | console.log(modem); 4 | 5 | modem.on('open', function() { 6 | console.log('Waiting...'); 7 | }); 8 | 9 | modem.on('error', function(err) { 10 | console.error('ERR:', err); 11 | }); 12 | 13 | modem.on('ring', function() { 14 | console.log('Ringing...'); 15 | }); 16 | 17 | modem.on('end ring', function() { 18 | console.log('End Ring'); 19 | }); 20 | 21 | modem.on('new message', function(notification) { 22 | console.log('new message', notification); 23 | }); 24 | 25 | modem.open(); 26 | 27 | -------------------------------------------------------------------------------- /example/ussd.js: -------------------------------------------------------------------------------- 1 | var SimCom = require('../').SimCom; 2 | 3 | var simcom = new SimCom('/dev/ttyAMA0'); 4 | simcom.on('open', function() { 5 | simcom.requestUSSD('#123#').then(function(res) { 6 | console.log('ussd result', res); 7 | }).catch(function(error) { 8 | console.log('error', error); 9 | }) 10 | .done(function() { 11 | }); 12 | }); 13 | 14 | simcom.on('ussd', function(ussd) { 15 | console.log('USSD:', ussd); 16 | }); 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | modem: require('./lib/modem'), 3 | SimCom: require('./lib/simcom') 4 | } 5 | -------------------------------------------------------------------------------- /lib/modem.js: -------------------------------------------------------------------------------- 1 | // vim: ts=2 expandtab 2 | 3 | var util = require('util'); 4 | var serialport = require('serialport'); 5 | var buffertools = require('buffertools'); 6 | var Q = require('q'); 7 | var EventEmitter = require('events').EventEmitter; 8 | 9 | var Modem = (function Modem_cctor() { 10 | function Modem(device, options) { 11 | options = options || {}; 12 | 13 | if (!options.lineEnd) { 14 | options.lineEnd = "\r\n"; 15 | } 16 | 17 | if (!options.baudrate) { 18 | options.baudrate = 115200; 19 | } 20 | 21 | this.options = options; 22 | this.opened = false; 23 | this.device = device; 24 | this.port = null; 25 | this.buffer = new Buffer(0); 26 | this.lines = []; 27 | this.executions = []; 28 | buffertools.extend(this.buffer); 29 | } 30 | 31 | util.inherits(Modem, EventEmitter); 32 | 33 | Modem.prototype.open = function(timeout) { 34 | var self = this; 35 | 36 | if (self.opened) { 37 | self.emit('open'); 38 | return; 39 | } 40 | 41 | timeout = timeout || 5000; 42 | 43 | this.port = new serialport.SerialPort(this.device, { 44 | baudrate: this.options.baudrate, 45 | parser: serialport.parsers.raw, 46 | }); 47 | 48 | this.port.on('open', function() { 49 | this.on('data', function(data) { 50 | self.buffer = Buffer.concat([self.buffer, data]); 51 | readBuffer.call(self); 52 | }); 53 | 54 | self.execute({ command: 'AT', timeout: timeout }).then(function() { 55 | self.emit('open'); 56 | }).catch(function(error) { 57 | self.emit('error', error); 58 | }).done();; 59 | }); 60 | 61 | this.port.on('close', function() { 62 | self.opened = false; 63 | self.emit('close'); 64 | }); 65 | 66 | this.port.on('error', function(err) { 67 | self.emit('error', err); 68 | }); 69 | 70 | this.opened = true; 71 | } 72 | 73 | Modem.prototype.close = function() { 74 | this.port.close(); 75 | this.port = null; 76 | instances[this.device] = null; 77 | } 78 | 79 | Modem.prototype.write = function(data, callback) { 80 | this.port.write(data, callback); 81 | } 82 | 83 | Modem.prototype.writeAndWait = function(data, callback) { 84 | var self = this; 85 | this.write(data, function() { 86 | self.port.drain(callback); 87 | }); 88 | } 89 | 90 | /* 91 | Modem.prototype.execute = function(command) { 92 | var p = null; 93 | var timeout; 94 | 95 | if (typeof command == 'object') { 96 | 97 | if (command.timeout) { 98 | timeout = Number(timeout); 99 | } 100 | 101 | if (command.defers) { 102 | defer_times = command.defers || 1; 103 | } 104 | 105 | p = command.pdu; 106 | command = command.command; 107 | 108 | } 109 | // 110 | var defer = Q.defer(); 111 | 112 | defer.command = command.split("\r", 1).shift(); 113 | defer.pdu = p; 114 | this.defers.push(defer); 115 | this.write(command + "\r"); 116 | 117 | if (timeout) { 118 | setTimeout(function() { 119 | defer.reject(new Error('timed out')); 120 | }, timeout); 121 | } 122 | 123 | return defer.promise; 124 | } 125 | */ 126 | 127 | function fetchExecution() { 128 | var defer = this.executions[0]; 129 | 130 | if (!defer) { 131 | return; 132 | } 133 | 134 | var execution = defer.execution; 135 | this.write(execution.exec + "\r"); 136 | 137 | if (execution.timeout) { 138 | defer.timer = setTimeout(function() { 139 | defer.reject(new Error('timed out')); 140 | }, execution.timeout); 141 | } 142 | } 143 | 144 | Modem.prototype.execute = function(command) { 145 | if (typeof command != 'object') { 146 | command = { command: String(command) }; 147 | } 148 | 149 | if (!command.command) { 150 | return; 151 | } 152 | 153 | var defer = Q.defer(); 154 | 155 | defer.execution = { 156 | exec: command.command, 157 | pdu: command.pdu || null, 158 | timeout: command.timeout || false 159 | }; 160 | 161 | if (this.executions.push(defer) == 1) { 162 | fetchExecution.call(this); 163 | } 164 | 165 | return defer.promise; 166 | } 167 | 168 | function readBuffer() { 169 | var self = this; 170 | 171 | var lineEndLength = self.options.lineEnd.length; 172 | var lineEndPosition = buffertools.indexOf(self.buffer, self.options.lineEnd); 173 | 174 | if (lineEndPosition === -1) { 175 | if (this.buffer.length == 2 && this.buffer.toString() == '> ') { 176 | processLine.call(this, this.buffer.toString()); 177 | } 178 | return; 179 | } 180 | 181 | var line = this.buffer.slice(0, lineEndPosition); 182 | var newBuffer = new Buffer(this.buffer.length - lineEndPosition - lineEndLength); 183 | this.buffer.copy(newBuffer, 0, lineEndPosition + lineEndLength); 184 | this.buffer = newBuffer; 185 | 186 | processLine.call(this, line.toString('ascii')); 187 | process.nextTick(readBuffer.bind(this)); 188 | } 189 | 190 | var unboundExprs = [ 191 | { 192 | expr: /^OVER-VOLTAGE WARNNING$/i, 193 | func: function(m) { 194 | this.emit('over-voltage warnning'); 195 | } 196 | }, 197 | 198 | { 199 | expr: /^RING$/i, 200 | func: function(m) { 201 | this.ringing = true; 202 | this.emit('ring'); 203 | } 204 | }, 205 | 206 | { 207 | expr: /^\+CMTI:(.+)$/i, 208 | func: function(m) { 209 | this.emit('new message', m[1]); 210 | } 211 | }, 212 | 213 | { 214 | expr: /^\+CPIN: (NOT .+)/i, 215 | unhandled: true, 216 | func: function(m) { 217 | this.emit('sim error', m[1]); 218 | } 219 | }, 220 | 221 | { 222 | expr: /^\+CUSD:(.+)$/i, 223 | func: function(m) { 224 | this.emit('ussd', m[1]); 225 | } 226 | } 227 | ]; 228 | 229 | function processUnboundLine(line) { 230 | for (var i = 0; i < unboundExprs.length; i++) { 231 | var u = unboundExprs[i]; 232 | var m = line.match(u.expr); 233 | 234 | if (m) { 235 | u.func && u.func.call(this, m); 236 | 237 | if (!u.unhandle) { 238 | this.emit('urc', m, u.expr); 239 | return true; 240 | } 241 | } 242 | } 243 | 244 | return false; 245 | } 246 | 247 | function processLine(line) { 248 | if (line.substr(0, 2) == 'AT') { 249 | // echo'd line 250 | return; 251 | } 252 | 253 | if (processUnboundLine.call(this, line)) { 254 | return; 255 | } 256 | 257 | // special handling for ring 258 | if (this.ringing && line == 'NO CARRIER') { 259 | this.ringing = false; 260 | this.emit('end ring'); 261 | return; 262 | } 263 | 264 | this.lines.push(line); 265 | processLines.call(this); 266 | } 267 | 268 | function isResultCode(line) { 269 | return /(^OK|ERROR|BUSY|DATA|NO CARRIER|> $)|(^CONNECT( .+)*$)/i.test(line); 270 | } 271 | 272 | function processLines() { 273 | if (!this.lines.length) { 274 | return; 275 | } 276 | 277 | if (!isResultCode(this.lines[this.lines.length-1])) { 278 | return; 279 | } 280 | 281 | if (this.lines[0].trim() == '') { 282 | this.lines.shift(); 283 | } 284 | 285 | processResponse.call(this); 286 | this.lines = []; 287 | } 288 | 289 | function processResponse() { 290 | var responseCode = this.lines.pop(); 291 | var defer = this.executions[0]; 292 | var execution = defer && defer.execution; 293 | 294 | if (responseCode == '> ') { 295 | if (execution && execution.pdu) { 296 | var pduSize = execution.pdu.length; 297 | var b = new Buffer(pduSize + 1); 298 | b.write(execution.pdu); 299 | b.writeUInt8(26, pduSize); 300 | this.write(b); 301 | execution.pdu = null; 302 | } 303 | return; 304 | } 305 | 306 | if (responseCode.match(/^CONNECT( .+)*$/i)) { 307 | if (execution && execution.pdu) { 308 | this.write(execution.pdu); 309 | execution.pdu = null; 310 | } 311 | return; 312 | } 313 | 314 | if (defer) { 315 | var cmd = execution.exec.split("\r", 1).shift(); 316 | this.executions.shift(); 317 | 318 | if (defer.timer) { 319 | clearTimeout(defer.timer); 320 | defer.timer = null; 321 | } 322 | 323 | if (responseCode == 'ERROR') { 324 | defer.reject({ code: responseCode, command: cmd }); 325 | return; 326 | } 327 | 328 | defer.resolve({ code: responseCode, command: cmd, lines: this.lines }); 329 | } 330 | 331 | if (this.executions.length) { 332 | fetchExecution.call(this); 333 | } 334 | } 335 | 336 | // 337 | return Modem; 338 | })(); 339 | 340 | var instances = {}; 341 | var init = function(device, options) { 342 | device = device || '/dev/ttyAMA0'; 343 | 344 | if (!instances[device]) { 345 | instances[device] = new Modem(device, options); 346 | } 347 | 348 | return instances[device]; 349 | } 350 | 351 | module.exports = init; 352 | -------------------------------------------------------------------------------- /lib/simcom.js: -------------------------------------------------------------------------------- 1 | // vim: ts=2 expandtab 2 | var util = require('util'), 3 | Q = require('q'), 4 | pdu = require('pdu'), 5 | EventEmitter = require('events').EventEmitter; 6 | 7 | function SimCom(device, options) { 8 | this.modem = require('./modem')(device, options); 9 | 10 | var self = this; 11 | 12 | // delegates modem events 13 | ['open', 'close', 'error', 'ring', 'end ring', 'over-voltage warnning'].forEach(function(e) { 14 | self.modem.on(e, function() { 15 | var args = Array.prototype.slice.call(arguments); 16 | args.unshift(e); 17 | self.emit.apply(self, args); 18 | }); 19 | }); 20 | 21 | this.modem.on('new message', handleNewMessage.bind(this)); 22 | this.modem.on('ussd', handleUSSD.bind(this)); 23 | 24 | this.modem.open(); 25 | } 26 | 27 | util.inherits(SimCom, EventEmitter); 28 | 29 | SimCom.prototype.close = function() { 30 | this.modem.close(); 31 | } 32 | 33 | /** 34 | * Execute a Raw AT Command 35 | * @param command Raw AT Command 36 | * @returns Promise 37 | */ 38 | SimCom.prototype.execute = function(command) { 39 | return this.modem.execute(command); 40 | } 41 | 42 | var simple_methods = { 43 | productID: 'ATI', 44 | manufacturerID: 'AT+GMI', 45 | modelID: 'AT+GMM', 46 | globalID: 'AT+GOI', 47 | IMEI: 'AT+GSN', 48 | subscriberID: 'AT+CIMI', 49 | } 50 | 51 | Object.keys(simple_methods).forEach(function(name) { 52 | SimCom.prototype[name] = function() { 53 | var defer = Q.defer(); 54 | 55 | this.execute(simple_methods[name]) 56 | .then(function(res) { 57 | defer.resolve(res.lines.shift()); 58 | }) 59 | .catch(function(res) { 60 | defer.reject(res); 61 | }); 62 | 63 | return defer.promise; 64 | }; 65 | }); 66 | 67 | function parse(s) { 68 | var quoted = false; 69 | var item = ''; 70 | var items = []; 71 | 72 | for (var i = 0; i < s.length; i++) { 73 | var valid = false; 74 | 75 | switch (s[i]) { 76 | case '"': 77 | quoted = !quoted; 78 | break; 79 | 80 | case ',': 81 | valid = quoted; 82 | if (!quoted) { 83 | items.push(item); 84 | item = ''; 85 | } 86 | break; 87 | 88 | default: 89 | valid = true; 90 | } 91 | 92 | if (valid) item += s[i]; 93 | } 94 | 95 | if (item) { 96 | items.push(item); 97 | } 98 | 99 | return items; 100 | } 101 | 102 | function handleNewMessage(m) { 103 | m = parse(m); 104 | m = { 105 | storage: m[0], 106 | index: Number(m[1]), 107 | type: m.length > 2 ? m[2] : 'SMS' 108 | }; 109 | 110 | this.emit('new message', m); 111 | } 112 | 113 | function handleUSSD(m) { 114 | m = parse(m).map(function(e) { return e.trim(); }); 115 | m = { 116 | type: Number(m[0]), 117 | str: m[1], 118 | dcs: Number(m[2]) 119 | }; 120 | 121 | m.str = m.dcs == 72 ? pdu.decode16Bit(m.str) : pdu.decode7Bit(m.str); 122 | this.emit('ussd', m); 123 | } 124 | 125 | SimCom.extractResponse = SimCom.prototype.extractResponse = function(resp, readPDU) { 126 | if (!resp || !resp.command || !resp.lines || !resp.lines.length) { 127 | return; 128 | } 129 | 130 | var cmd = resp.command.match(/^AT([^\=\?]*)/); 131 | if (!cmd || cmd.length < 2) { 132 | return; 133 | } 134 | 135 | cmd = cmd[1]; 136 | 137 | var result = []; 138 | var needPDU = false; 139 | var pduResponse = null; 140 | var cmdMatched = false; 141 | for (var i = 0; i < resp.lines.length; i++) { 142 | var line = resp.lines[i]; 143 | 144 | if (line == '') { 145 | cmdMatched = false; 146 | continue; 147 | } 148 | 149 | if (!needPDU) { 150 | if (!cmdMatched) { 151 | if (line.substr(0, cmd.length) == cmd) { 152 | var tokens = line.substr(cmd.length).match(/(\:\s*)*(.+)*/); 153 | if (tokens && tokens.length > 2) { 154 | line = tokens[2]; 155 | cmdMatched = true; 156 | } 157 | } 158 | } 159 | 160 | if (cmdMatched) { 161 | if (line) { 162 | if (!readPDU) { 163 | result.push(line); 164 | } else { 165 | pduResponse = { response: line, pdu: null }; 166 | } 167 | } 168 | 169 | needPDU = readPDU; 170 | } 171 | } else { 172 | pduResponse.pdu = line; 173 | result.push(pduResponse); 174 | needPDU = false; 175 | } 176 | } 177 | 178 | return result; 179 | } 180 | 181 | /** 182 | * Invoke a RAW AT Command, Catch and process the responses. 183 | * @param command RAW AT Command 184 | * @param resultReader Callback for processing the responses 185 | * @param readPDU Try to read PDU from responses 186 | * @returns Promise 187 | */ 188 | SimCom.prototype.invoke = function(command, resultReader, readPDU) { 189 | var defer = Q.defer(); 190 | var self = this; 191 | this.execute(command).then(function(res) { 192 | var result = SimCom.extractResponse(res, readPDU) || null; 193 | if (resultReader) { 194 | result = resultReader.call(self, result); 195 | } 196 | 197 | defer.resolve(result); 198 | 199 | }).catch(function(error) { 200 | defer.reject(error); 201 | }); 202 | 203 | return defer.promise; 204 | } 205 | 206 | SimCom.prototype.serviceProvider = function() { 207 | return this.invoke('AT+CSPN?', function(lines) { 208 | return lines.shift().match(/"([^"]*)"/).pop(); 209 | }); 210 | } 211 | 212 | SimCom.prototype.listSMS = function(stat) { 213 | return this.invoke('AT+CMGL=' + stat, function(res) { 214 | return res.map(function(m) { 215 | var infos = parse(m.response); 216 | return { 217 | index: Number(infos[0]), 218 | stat: infos[1], 219 | message: pdu.parse(m.pdu) 220 | }; 221 | }); 222 | }, true); 223 | } 224 | 225 | SimCom.prototype.readSMS = function(index, peek) { 226 | return this.invoke('AT+CMGR=' + index + ',' + (peek ? 0 : 1), function(res) { 227 | return pdu.parse(res.shift().pdu); 228 | }, true); 229 | } 230 | 231 | SimCom.prototype.sendSMS = function(receiver, text) { 232 | var p = pdu.generate({ 233 | encoding: '16bit', 234 | receiver: receiver, 235 | text: text 236 | }).shift(); 237 | 238 | var pduLength = (p.length/2)-1; 239 | return this.invoke({ command: 'AT+CMGS=' + pduLength, pdu: p }, function(res) { 240 | return res.shift(); 241 | }); 242 | } 243 | 244 | SimCom.prototype.setBearerParam = function(id, tag, value) { 245 | return this.invoke('AT+SAPBR=3,' + id + ',' + '"' + tag + '","' + value + '"'); 246 | } 247 | 248 | SimCom.prototype.setBearerParams = function(id, params) { 249 | var self = this; 250 | return Object.keys(params).reduce(function(d, k) { 251 | return d.then(function() { 252 | self.setBearerParam(id, k, params[k]); 253 | }); 254 | }, Q(0)); 255 | } 256 | 257 | SimCom.prototype.getBearerParams = function(id) { 258 | return this.invoke('AT+SAPBR=4,' + id, function(lines) { 259 | return lines.reduce(function(m, v) { 260 | v = v.split(':', 2); 261 | m[v[0].trim()] = v[1].trim(); 262 | return m; 263 | }, {}); 264 | }); 265 | } 266 | 267 | SimCom.prototype.activateBearer = function(id) { 268 | return this.invoke('AT+SAPBR=1,'+id); 269 | } 270 | 271 | SimCom.prototype.deactivateBearer = function(id) { 272 | return this.invoke('AT+SAPBR=0,'+id); 273 | } 274 | 275 | SimCom.prototype.queryBearer = function(id) { 276 | return this.invoke('AT+SAPBR=2,'+id, function(lines) { 277 | var line = lines.shift() || ''; 278 | var m = line.match(/(.+),(.+),\"([^"]*)/); 279 | 280 | var cid = Number(m[1]); 281 | var status_code = Number(m[2]); 282 | var status = status_code; 283 | var ip = m[3]; 284 | 285 | switch (status_code) { 286 | case 1: 287 | status = 'connected'; 288 | break; 289 | case 2: 290 | status = 'closing'; 291 | break; 292 | case 3: 293 | status = 'closed'; 294 | break; 295 | default: 296 | status = 'unknown'; 297 | } 298 | 299 | return { 300 | id: cid, 301 | status_code: status_code, 302 | status: status, 303 | ip: ip 304 | }; 305 | }); 306 | } 307 | 308 | SimCom.prototype.startBearer = function(id) { 309 | var self = this; 310 | 311 | return self.queryBearer(id).then(function(res) { 312 | if (!res || res.status_code != 1) { 313 | return self.activateBearer(id); 314 | } 315 | }); 316 | } 317 | 318 | SimCom.prototype.initMMS = function() { 319 | return this.invoke('AT+CMMSINIT'); 320 | } 321 | 322 | SimCom.prototype.terminateMMS = function() { 323 | return this.invoke('AT+CMMSTERM'); 324 | } 325 | 326 | SimCom.prototype.startMMS = function() { 327 | var self = this; 328 | 329 | return self.initMMS().then(null, 330 | function error() { 331 | return self.terminateMMS().then(function() { 332 | return self.initMMS(); 333 | }); 334 | } 335 | ); 336 | } 337 | 338 | SimCom.prototype.editMMS = function(edit) { 339 | return this.invoke('AT+CMMSEDIT=' + Number(edit || false)); 340 | } 341 | 342 | function downloadMMS(type, data, name) { 343 | if (!/^(PIC|TEXT|TITLE)$/i.test(type)) { 344 | throw new Error('Invalid MMS Download Type'); 345 | } 346 | 347 | if (data && data.length) { 348 | type = type.toUpperCase(); 349 | var timeout = Math.max(200000, Math.ceil(data.length/this.modem.options.baudrate*1000*8)); 350 | var param = '"' + type + '",' + data.length + ',' + timeout; 351 | if (name) { 352 | param += ',"' + name + '"'; 353 | } 354 | 355 | var self = this; 356 | 357 | return self.invoke('ATE1').then(function() { 358 | 359 | return self.invoke({ command: 'AT+CMMSDOWN=' + param, pdu: data, timeout: timeout }); 360 | }); 361 | 362 | } 363 | } 364 | 365 | SimCom.prototype.downloadMMSText = function(text, name) { 366 | return downloadMMS.call(this, 'TEXT', text, name); 367 | } 368 | 369 | SimCom.prototype.downloadMMSTitle = function(title) { 370 | return downloadMMS.call(this, 'TITLE', title); 371 | } 372 | 373 | SimCom.prototype.downloadMMSPicture = function(data, name) { 374 | return downloadMMS.call(this, 'PIC', data, name); 375 | } 376 | 377 | SimCom.prototype.setMMSRecipient = function(address) { 378 | if (Array.isArray(address)) { 379 | var self = this; 380 | 381 | return Q.all(address.map(function(addr) { 382 | return self.setMMSRecipient(addr); 383 | })); 384 | } 385 | 386 | return this.invoke('AT+CMMSRECP="' + address + '"'); 387 | } 388 | 389 | SimCom.prototype.viewMMS = function() { 390 | return this.invoke('AT+CMMSVIEW', function(lines) { 391 | var tokens = parse(lines.shift()); 392 | var files = lines.map(function(line) { 393 | var t = parse(line); 394 | return { 395 | index: Number(t[0]), 396 | name: t[1], 397 | type: (function() { 398 | switch (Number(t[2])) { 399 | case 2: 400 | return 'text'; 401 | case 3: 402 | return 'text/html'; 403 | case 4: 404 | return 'text/plain'; 405 | case 5: 406 | return 'image'; 407 | case 6: 408 | return 'image/gif'; 409 | case 7: 410 | return 'image/jpg'; 411 | case 8: 412 | return 'image/tif'; 413 | case 9: 414 | return 'image/png'; 415 | case 10: 416 | return 'smil'; 417 | default: 418 | return 'unknown' 419 | } 420 | })(), 421 | size: Number(t[3]) 422 | }; 423 | }); 424 | 425 | return { 426 | type: (function() { 427 | switch (Number(tokens[0])) { 428 | case 0: 429 | return 'received'; 430 | case 1: 431 | return 'sent'; 432 | case 2: 433 | return 'unsent'; 434 | default: 435 | return 'unknown' 436 | } 437 | })(), 438 | 439 | sender: tokens[1], 440 | to: tokens[2].split(';'), 441 | cc: tokens[3].split(';'), 442 | bcc: tokens[4].split(';'), 443 | datetime: Date(tokens[5]), 444 | subject: tokens[6], 445 | size: Number(tokens[7]), 446 | files: files 447 | }; 448 | }); 449 | } 450 | 451 | SimCom.prototype.pushMMS = function() { 452 | return this.invoke({ command: 'AT+CMMSSEND', timeout: 200000 }); 453 | } 454 | 455 | SimCom.prototype.requestUSSD = function(ussd) { 456 | return this.invoke('AT+CUSD=1,"' + ussd + '"'); 457 | } 458 | 459 | module.exports = SimCom; 460 | 461 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simcom", 3 | "version": "0.3.0", 4 | "description": "Talk to GSM modem SIMCOM via Node", 5 | "homepage": "https://github.com/vittee/simcom", 6 | "main": "index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/vittee/simcom.git" 10 | }, 11 | "keywords": [ 12 | "at", 13 | "gsm", 14 | "modem", 15 | "serial", 16 | "sms", 17 | "mms", 18 | "ussd", 19 | "simcom", 20 | "sim800", 21 | "sim900" 22 | ], 23 | "author": { 24 | "name": "Wittawas Nakkasem", 25 | "email": "vittee@hotmail.com" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/vittee/simcom/issues" 29 | }, 30 | "dependencies": { 31 | "pdu": ">= 1.1.0", 32 | "q": ">=1.0.1", 33 | "serialport": ">= 1.1.1", 34 | "buffertools": "~2.1.2" 35 | } 36 | } 37 | --------------------------------------------------------------------------------