├── index.js ├── examples ├── callerid.js ├── read_all_sms.js └── send_sms.js ├── package.json ├── lib ├── ussd_session.js └── modem.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | return module.exports = { 2 | Modem : require('./lib/modem.js'), 3 | Ussd_Session : require('./lib/ussd_session.js') 4 | } -------------------------------------------------------------------------------- /examples/callerid.js: -------------------------------------------------------------------------------- 1 | var device = process.argv[2]; 2 | if(!device) { 3 | console.log('Usage: node callerid.js /path/to/device'); 4 | process.exit(); 5 | } 6 | var modem = require('../index.js').Modem(); 7 | modem.open(device, function() { 8 | modem.on('ring', function(msisdn) { 9 | console.log('Ringing', msisdn); 10 | }); 11 | }); -------------------------------------------------------------------------------- /examples/read_all_sms.js: -------------------------------------------------------------------------------- 1 | /* Usage: node read_all_sms.js /path/to/device */ 2 | 3 | function err(message) { 4 | console.log('Usage: node read_all_sms.js /path/to/device'); 5 | process.exit(); 6 | } 7 | 8 | var device = process.argv[2]; 9 | 10 | if(!device) err(); 11 | 12 | var modem = require('../index.js').Modem(); 13 | modem.open(device, function() { 14 | modem.getMessages(function() { 15 | console.log(arguments); 16 | }) 17 | 18 | modem.on('sms received', function(sms) { 19 | console.log(sms); 20 | }); 21 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modem", 3 | "version": "1.0.3", 4 | "description": "Send and receive messages and make ussd queries using your GSM modems", 5 | "homepage": "http://github.com/emilsedgh/modem", 6 | "main": "./index.js", 7 | "keywords": [ 8 | "ussd", 9 | "sms" 10 | ], 11 | "author": { 12 | "name": "Emil Sedgh", 13 | "email": "emilsedgh@kde.org", 14 | "url": "http://emilsedgh.info/" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/emilsedgh/modem/issues" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/emilsedgh/modem.git" 22 | }, 23 | "dependencies" : { 24 | "serialport" : ">= 1.1.1", 25 | "pdu" : ">= 1.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/send_sms.js: -------------------------------------------------------------------------------- 1 | /* Usage: node send_sms.js /path/to/device xxxxyyzz "Foo Bar" */ 2 | 3 | function err(message) { 4 | console.log('Usage: node send_sms.js /path/to/device xxxxyyzz "Foo Bar"'); 5 | process.exit(); 6 | } 7 | 8 | var device = process.argv[2]; 9 | var receiver = process.argv[3]; 10 | var text = process.argv[4]; 11 | 12 | if(!device || !receiver || !text) err(); 13 | 14 | var modem = require('../index.js').Modem(); 15 | modem.open('/dev/ttyUSB0', function() { 16 | modem.sms({ 17 | receiver:receiver, 18 | text:text, 19 | encoding:'7bit' 20 | }, function(err, sent_ids) { 21 | console.log('>>', arguments); 22 | if(err) 23 | console.log('Error sending sms:', err); 24 | else 25 | console.log('Message sent successfully, here are reference ids:', sent_ids.join(',')); 26 | }); 27 | }); -------------------------------------------------------------------------------- /lib/ussd_session.js: -------------------------------------------------------------------------------- 1 | var pdu = require('pdu'); 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | var createSession = function() { 6 | var ussd_session = new EventEmitter(); 7 | 8 | ussd_session.modem = false; 9 | ussd_session.expect_more_data = false; 10 | ussd_session.closed = false; 11 | ussd_session.state = 'pending'; 12 | 13 | ussd_session.start = function() { 14 | this.modem.on('data',this.findUssdResponse); 15 | this.execute(); 16 | } 17 | 18 | ussd_session.setTimeout = function(ms) { 19 | this.timeout = setTimeout(function() { 20 | this.emit('timeout'); 21 | this.close(); 22 | }.bind(this), ms); 23 | 24 | this.on('close', function() { 25 | this.clearTimeout(); 26 | }); 27 | } 28 | 29 | ussd_session.clearTimeout = function() { 30 | clearTimeout(this.timeout); 31 | } 32 | 33 | ussd_session.query = function(string, callback, end) { 34 | if(callback) 35 | this.once('response', callback); 36 | var encoded = ussd_session.encode(string); 37 | 38 | var session = this; 39 | var execute_job = this.modem.execute('AT+CUSD=1,"'+encoded+'",15', function(response, escape_char) { 40 | if(escape_char.match(/error/i)) 41 | session.close(); 42 | }, end); 43 | 44 | if(!execute_job) 45 | return ; 46 | 47 | var session = this; 48 | execute_job.on('start', function() { 49 | if(session.state == 'pending') { 50 | session.state = 'started'; 51 | session.emit('start'); 52 | session.setTimeout(10000); 53 | } 54 | 55 | session.emit('request', string); 56 | }); 57 | } 58 | 59 | ussd_session.close = function(callback) { 60 | if(!this.closed) { 61 | this.modem.execute('AT+CUSD=2', callback); 62 | this.state = 'closed'; 63 | this.emit('close'); 64 | } 65 | } 66 | 67 | ussd_session.isResponseCompleted = function() { 68 | var args = this.modem.parseResponse(this.partial_data); 69 | return (args[1] && this.partial_data.match(/\"/g).length === 2); 70 | } 71 | 72 | ussd_session.appendResponse = function(data) { 73 | this.partial_data += data; 74 | } 75 | 76 | ussd_session.responseCompleted = function() { 77 | var args = this.modem.parseResponse(this.partial_data); 78 | 79 | if(this.modem.ussd_pdu && args[2] == '15') 80 | var response = pdu.decode7Bit(args[1]); 81 | else if(this.modem.ussd_pdu) 82 | var response = pdu.decode16Bit(args[1]); 83 | else 84 | var response = args[1]; 85 | 86 | this.expect_more_data = false; 87 | this.partial_data = ''; 88 | this.partial_response_code = ''; 89 | 90 | this.closed = (args[0] == '2' || args[0] == '0'); 91 | 92 | this.emit('response', args[0], response.replace(/\u0000/g, ''), args[2]); 93 | 94 | if(this.closed && !this.expect_more_data) { 95 | this.emit('close'); 96 | this.state = 'closed'; 97 | } 98 | } 99 | 100 | ussd_session.findUssdResponse = function(data, escape_char) { 101 | var data = data.trim(); 102 | 103 | if(data === '+CME ERROR: retry operation' || data === '+CUSD: 4') { 104 | this.modem.ussd_pdu = false; 105 | this.close(); 106 | return ; 107 | } 108 | 109 | 110 | if(data.slice(0,5).trim() === '+CUSD') { 111 | //USSD response starts. 112 | this.appendResponse(data); 113 | 114 | if(this.isResponseCompleted(data)) 115 | this.responseCompleted(data); 116 | else 117 | this.expect_more_data = true; 118 | 119 | return ; 120 | } 121 | 122 | //Not a +CUSD. If we arent expecting more data, this has nothing to do with us. 123 | if(!this.expect_more_data) 124 | return ; 125 | 126 | //There should be some data coming from last part(s) of message. 127 | if(!this.partial_data) 128 | return ; 129 | 130 | if(data.slice(0, 1) === '+' || data.slice(0, 1) === '^') 131 | return ; //Ignore notifications. 132 | 133 | this.appendResponse(data); 134 | if(this.isResponseCompleted()) 135 | this.responseCompleted(); 136 | 137 | }.bind(ussd_session); 138 | 139 | ussd_session.on('close', function() { 140 | this.modem.removeListener('data', this.findUssdResponse); 141 | }); 142 | 143 | ussd_session.encode = function(string) { 144 | if(ussd_session.modem.ussd_pdu) 145 | return pdu.encode7Bit(string); 146 | else 147 | return string; 148 | } 149 | 150 | return ussd_session; 151 | } 152 | 153 | module.exports = createSession; 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Modem.js, GSM Modems on Node 2 | ============================ 3 | > Modem.js allows you to use your GSM modems on node. 4 | It offers a very simple API. 5 | It supports: 6 | * Sending SMS messages 7 | * Receiving SMS messages 8 | * Getting delivery reports 9 | * Deleting messages from memory 10 | * Getting notifications when memory is full 11 | * Getting signal quality 12 | * Making ussd sessions 13 | * 16bit (ucsd messages) 14 | * 7bit (ascii) messages 15 | * Multipart messages 16 | * Getting notifications when someone calls 17 | 18 | Installation 19 | ------------ 20 | ```bash 21 | npm install modem 22 | ``` 23 | 24 | Instantiate 25 | ----------- 26 | ```javascript 27 | var modem = require('modem').Modem() 28 | ``` 29 | 30 | Open modem 31 | ---------- 32 | ```javascript 33 | modem.open(device, callback) 34 | ``` 35 | 36 | * device `String` Path to device, like `/dev/ttyUSB0` 37 | * callback `Function` called when modem is ready for further action 38 | 39 | Send a message 40 | -------------- 41 | ```javascript 42 | modem.sms(message, callback) 43 | ``` 44 | 45 | * message `Object` 46 | * text `String` message body. Longs messages will be splitted and 47 | sent in multiple parts transparently. 48 | * receiver `String` receiver number. 49 | * encoding `String`. '16bit' or '7bit'. Use 7bit in case of English messages. 50 | 51 | callback `Fucntion`(err, references) is called when sending is done. 52 | * references `Array` contains reference ids for each part of sent message. (A message may take up to several parts) 53 | 54 | 55 | Get delivery reports 56 | -------------------- 57 | ```javascript 58 | modem.on('delivery', callback) 59 | ``` 60 | 61 | * callback `Function` is called with the following arguments: 62 | 63 | * details `Object` detailed status report 64 | * smsc `String` Msisdn of SMS Sender 65 | * reference `String` Reference number of the delivered message 66 | * sender `Msisdn of receiver` 67 | * status `Delivery status` 68 | 69 | * index `String` index of this delivery report in storage 70 | 71 | Receive a message 72 | ----------------- 73 | ```javascript 74 | modem.on('sms received', callback) 75 | ``` 76 | 77 | * callback `Function` will be called on each new message with following arguments: 78 | * message `Object` 79 | * smsc `String` MSISDN of SMS Center 80 | * sender `String` MSISDN of sender 81 | * time `Date` Send time 82 | * text `String` message body 83 | 84 | Get stored messages 85 | ------------------- 86 | ```javascript 87 | modem.getMessages(callback) 88 | ``` 89 | * callback `Function` will be called with a single argument 90 | messages `Array`, which contains stores messages 91 | 92 | Delete a message 93 | ---------------- 94 | ```javascript 95 | modem.deleteMessage(message_index, callback) 96 | ``` 97 | 98 | * message_index `Int` is the message index to be deleted 99 | * callback `Function` called when message is deleted 100 | 101 | Get notified when memory is full 102 | -------------------------------- 103 | ```javascript 104 | modem.on('memory full', callback) 105 | ``` 106 | * callback `Function` will be called when modem has no more space 107 | for further messages 108 | 109 | Get notified when someone calls 110 | -------------------------------- 111 | ```javascript 112 | modem.on('ring', callback) 113 | ``` 114 | * callback `Function` will be called on each RING with a single argument 115 | * msisdn `Number` 116 | 117 | Running custom AT commands 118 | ========================== 119 | > Modem.js allows you to run AT commands in a queue and manage them without messing the modem. 120 | API is still quite simple. 121 | 122 | Run a command 123 | ------------- 124 | ```javascript 125 | job = modem.execute(at_command, [callback], [priority], [timeout]) 126 | ``` 127 | 128 | * at_command `String` AT command you would like to execute 129 | * callback `Function` called when execution is done, in form of `(escape_char, [response])` 130 | * escape_char `String` could be 'OK', 'ERROR' or '>'. 131 | * response `String` modem's response 132 | * prior `Boolean` if set to true, command will be executed immediately 133 | * timeout `Integer` timeout, in milliseconds, defaults to 60 seconds. 134 | * job `EventEmitter` represents the added job. 135 | * it will emit `timeout` if executing job times out 136 | 137 | USSD Sessions 138 | ============ 139 | > Modem.js allows you to run ussd sessions. 140 | 141 | Instantiate 142 | ----------- 143 | ```javascript 144 | var Session = require('modem').Ussd_Session 145 | ``` 146 | 147 | Create a session 148 | ---------------- 149 | ```javascript 150 | var Session = require('modem').Ussd_Session 151 | var CheckBalance = function(c) { 152 | var session = new Session; 153 | session.callback = c; 154 | 155 | session.parseResponse = function(response_code, message) { 156 | this.close(); 157 | 158 | var match = message.match(/([0-9,\,]+)\sRial/); 159 | if(!match) { 160 | if(this.callback) 161 | this.callback(false); 162 | return ; 163 | } 164 | 165 | 166 | if(this.callback) 167 | this.callback(match[1]); 168 | 169 | session.modem.credit = match[1]; 170 | } 171 | 172 | session.execute = function() { 173 | this.query('*141*#', session.parseResponse); 174 | } 175 | 176 | return session; 177 | } 178 | ``` 179 | -------------------------------------------------------------------------------- /lib/modem.js: -------------------------------------------------------------------------------- 1 | var pdu = require('pdu'); 2 | var sp = require('serialport'); 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | var createModem = function() { 6 | var modem = new EventEmitter(); 7 | 8 | modem.queue = []; //Holds queue of commands to be executed. 9 | modem.isLocked = false; //Device status 10 | modem.partials = {}; //List of stored partial messages 11 | modem.isOpened = false; 12 | modem.job_id = 1; 13 | modem.ussd_pdu = true; //Should USSD queries be done in PDU mode? 14 | 15 | //For each job, there will be a timeout stored here. We cant store timeout in item's themselves because timeout's are 16 | //circular objects and we want to JSON them to send them over sock.io which would be problematic. 17 | var timeouts = {}; 18 | 19 | //Adds a command to execution queue. 20 | //Command is the AT command, c is callback. If prior is true, the command will be added to the beginning of the queue (It has priority). 21 | modem.execute = function(command, c, prior, timeout) { 22 | if(!this.isOpened) { 23 | this.emit('close'); 24 | return ; 25 | } 26 | 27 | var item = new EventEmitter(); 28 | item.command = command; 29 | item.callback = c; 30 | item.add_time = new Date(); 31 | item.id = ++this.job_id; 32 | item.timeout = timeout; 33 | if(item.timeout == undefined) //Default timeout it 60 seconds. Send false to disable timeouts. 34 | item.timeout = 60000; 35 | 36 | if(prior) 37 | this.queue.unshift(item); 38 | else 39 | this.queue.push(item); 40 | 41 | this.emit('job', item); 42 | process.nextTick(this.executeNext.bind(this)); 43 | return item; 44 | } 45 | 46 | //Executes the first item in the queue. 47 | modem.executeNext = function() { 48 | if(!this.isOpened) { 49 | this.emit('close'); 50 | return ; 51 | } 52 | //Someone else is running. Wait. 53 | if(this.isLocked) 54 | return ; 55 | 56 | var item = this.queue[0]; 57 | 58 | if(!item) { 59 | this.emit('idle'); 60 | return ; //Queue is empty. 61 | } 62 | 63 | //Lock the device and null the data buffer for this command. 64 | this.data = ''; 65 | this.isLocked = true; 66 | 67 | item.execute_time = new Date(); 68 | 69 | item.emit('start'); 70 | 71 | if(item.timeout) 72 | timeouts[item.id] = setTimeout(function() { 73 | item.emit('timeout'); 74 | this.release(); 75 | this.executeNext(); 76 | }.bind(this), item.timeout); 77 | 78 | modem.port.write(item['command']+"\r"); 79 | } 80 | 81 | modem.open = function(device, options, callback) { 82 | if (typeof callback === 'function') { 83 | options['parser'] = sp.parsers.raw; 84 | modem.port = new sp.SerialPort(device, options); 85 | } else { 86 | modem.port = new sp.SerialPort(device, { 87 | parser: sp.parsers.raw 88 | }); 89 | callback = options; 90 | } 91 | 92 | modem.port.on('open', function() { 93 | modem.isOpened = true; 94 | 95 | modem.port.on('data', modem.dataReceived.bind(modem)); 96 | 97 | modem.emit('open'); 98 | 99 | if(callback) 100 | callback(); 101 | }); 102 | 103 | modem.port.on('close', function() { 104 | modem.port.close(); 105 | modem.isOpened = false; 106 | modem.emit('close'); 107 | }); 108 | 109 | modem.port.on('error', function() { 110 | modem.close(); 111 | }); 112 | } 113 | 114 | modem.close = function(device) { 115 | this.port.removeAllListeners(); 116 | this.port.close(); 117 | this.port = null; 118 | this.isOpened = false; 119 | this.emit('close'); 120 | } 121 | 122 | modem.dataReceived = function(buffer) { 123 | //We dont seriously expect such little amount of data. Ignore it. 124 | if(buffer.length < 2) 125 | return ; 126 | 127 | var datas = buffer.toString().trim().split('\r'); 128 | 129 | datas.forEach(function(data) { 130 | // When we write to modem, it gets echoed. 131 | // Filter out queue we just executed. 132 | if(this.queue[0] && this.queue[0]['command'].trim().slice(0, data.length) === data) { 133 | this.queue[0]['command'] = this.queue[0]['command'].slice(data.length); 134 | return ; 135 | } 136 | 137 | //Emit received data for those who care. 138 | this.emit('data', data); 139 | 140 | 141 | if(data.trim().slice(0,5).trim() === '+CMTI') { 142 | this.smsReceived(data); 143 | return ; 144 | } 145 | 146 | if(data.trim().slice(0,5).trim() === '+CDSI') { 147 | this.deliveryReceived(data); 148 | return ; 149 | } 150 | 151 | if(data.trim().slice(0,5).trim() === '+CLIP') { 152 | this.ring(data); 153 | return ; 154 | } 155 | 156 | if(data.trim().slice(0,10).trim() === '^SMMEMFULL') { 157 | modem.emit('memory full', modem.parseResponse(data)[0]); 158 | return ; 159 | } 160 | 161 | //We are expecting results to a command. Modem, at the same time, is notifying us (of something). 162 | //Filter out modem's notification. Its not our response. 163 | if(this.queue[0] && data.trim().substr(0,1) === '^') 164 | return ; 165 | 166 | if(data.trim() === 'OK' || data.trim().match(/error/i) || data.trim() === '>') { //Command finished running. 167 | if(this.queue[0] && this.queue[0]['callback']) 168 | var c = this.queue[0]['callback'] 169 | else 170 | var c = null; 171 | 172 | var allData = this.data; 173 | var delimeter = data.trim(); 174 | 175 | /* 176 | Ordering of the following lines is important. 177 | First, we should release the modem. That will remove the current running item from queue. 178 | Then, we should call the callback. It might add another item with priority which will be added at the top of the queue. 179 | Then executeNext will execute the next command. 180 | */ 181 | 182 | this.queue[0]['end_time'] = new Date(); 183 | this.queue[0].emit('end', allData, data.trim()); 184 | clearTimeout(timeouts[this.queue[0].id]); 185 | 186 | this.release(); 187 | 188 | if(c) 189 | c(allData, data.trim()); //Calling the callback and letting her know about data. 190 | 191 | this.executeNext(); 192 | 193 | } else 194 | this.data += data; //Rest of data for a command. (Long answers will happen on multiple dataReceived events) 195 | }.bind(this)); 196 | } 197 | 198 | modem.release = function() { 199 | this.data = ''; //Empty the result buffer. 200 | this.isLocked = false; //release the modem for next command. 201 | this.queue.shift(); //Remove current item from queue. 202 | } 203 | 204 | modem.smsReceived = function(cmti) { 205 | var message_info = this.parseResponse(cmti); 206 | var memory = message_info[0]; 207 | this.execute('AT+CPMS="'+memory+'"', function(memory_usage) { 208 | var memory_usage = modem.parseResponse(memory_usage); 209 | var used = parseInt(memory_usage[0]); 210 | var total = parseInt(memory_usage[1]); 211 | 212 | if(used === total) 213 | modem.emit('memory full', memory); 214 | }); 215 | this.execute('AT+CMGR='+message_info[1], function(cmgr) { 216 | var lines = cmgr.trim().split("\n"); 217 | var message = this.processReceivedPdu(lines[1], message_info[1]); 218 | if(message) 219 | this.emit('sms received', message); 220 | }.bind(this)); 221 | } 222 | 223 | modem.deliveryReceived = function(delivery) { 224 | var response = this.parseResponse(delivery); 225 | this.execute('AT+CPMS="'+response[0]+'"'); 226 | this.execute('AT+CMGR='+response[1], function(cmgr) { 227 | var lines = cmgr.trim().split("\n"); 228 | var deliveryResponse = pdu.parseStatusReport(lines[1]); 229 | this.emit('delivery', deliveryResponse, response[1]); 230 | }.bind(this)); 231 | } 232 | 233 | modem.ring = function(data) { 234 | var clip = this.parseResponse(data); 235 | modem.emit('ring', clip[0]); 236 | } 237 | 238 | modem.parseResponse = function(response) { 239 | var plain = response.slice(response.indexOf(':')+1).trim(); 240 | var parts = plain.split(/,(?=(?:[^"]|"[^"]*")*$)/); 241 | for(i in parts) 242 | parts[i] = parts[i].replace(/\"/g, ''); 243 | 244 | return parts; 245 | } 246 | 247 | modem.processReceivedPdu = function(pduString, index) { 248 | try { 249 | var message = pdu.parse(pduString); 250 | message.text = message.text.replace(/^\0+/, '').replace(/\0+$/, ''); 251 | } catch(error) { 252 | return ; 253 | } 254 | message['indexes'] = [index]; 255 | 256 | if(typeof(message['udh']) === 'undefined') //Messages has no data-header and therefore, is not contatenated. 257 | return message; 258 | 259 | if(message['udh']['iei'] !== '00' && message['udh']['iei'] !== '08') //Message has some data-header, but its not a contatenated message; 260 | return message; 261 | 262 | var messagesId = message.sender+'_'+message.udh.reference_number; 263 | if(typeof(this.partials[messagesId]) === 'undefined') 264 | this.partials[messagesId] = []; 265 | 266 | this.partials[messagesId].push(message); 267 | if(this.partials[messagesId].length < message.udh.parts) 268 | return ; 269 | 270 | var text = ''; 271 | var indexes = []; 272 | 273 | for(var i = 0; i' in response. 318 | //Then, appendPdu should append the PDU+^Z 'immediately'. Thats why the appendPdu executes the pdu using priority argument of modem.execute. 319 | var sendPdu = function(pdu) { // Execute 'AT+CMGS=X', which means modem should get ready to read a PDU of X bytes. 320 | this.execute("AT+CMGS="+((pdu.length/2)-1), appendPdu); 321 | }.bind(this); 322 | 323 | var appendPdu = function(response, escape_char) { //Response to a AT+CMGS=X is '>'. Which means we should enter PDU. If aything else has been returned, there's an error. 324 | if(escape_char !== '>') 325 | return callback(response+' '+escape_char); //An error has happened. 326 | 327 | var job = this.execute(pdus[i]+String.fromCharCode(26), function(response, escape_char) { 328 | if(escape_char.match(/error/i)) 329 | return callback(response+' '+escape_char); 330 | 331 | var response = this.parseResponse(response); 332 | 333 | ids.push(response[0]); 334 | i++; 335 | 336 | if(typeof(pdus[i]) === 'undefined') { 337 | if(callback) 338 | callback(null, ids); //We've pushed all PDU's and gathered their ID's. calling the callback. 339 | modem.emit('sms sent', message, ids); 340 | } else { 341 | sendPdu(pdus[i]); //There's at least one more PDU to send. 342 | } 343 | }.bind(this), true, false); 344 | 345 | }.bind(this); 346 | 347 | sendPdu(pdus[i]); 348 | } 349 | 350 | modem.on('newListener', function(listener) { 351 | //If user wants to get sms events, we have to ask modem to give us notices. 352 | if(listener == 'sms received') 353 | this.execute('AT+CNMI=2,1,0,2,0'); 354 | 355 | if(listener == 'ring') 356 | this.execute('AT+CLIP=1'); 357 | }); 358 | 359 | modem.deleteMessage = function(index, cb) { 360 | modem.execute('AT+CMGD='+index, cb); 361 | } 362 | 363 | return modem; 364 | } 365 | 366 | module.exports = createModem; 367 | --------------------------------------------------------------------------------