UNPKG

modem-serial

Version:

Communication module for serial modems control: This NodeJS utility aims to make it easy to interact with USB dongles, providing functions to get info, connect, make calls with just an API call.

1,038 lines (991 loc) 30.2 kB
/*jslint node: true */ 'use strict'; /** * Interface to take care of the communications with each USB modem. * * @class ModemSerial * @constructor */ var SerialPort = require('serialport').SerialPort, exec = require('child_process').exec, translate = require('./translate'); if (process.env.DEBUG) { var debug = require('debug')('modem-serial:main'); } else { var debug = function(text) {}; } var ModemSerial = function (dev) { var DEFAULT_DURATION = 90, writeLock = false, writeQ = [], readQ = []; var self = this; self.ttl = 3000; var info = { 'USB_port': dev, 'statusText': 'Initializing' }; var callInfo = { timing: {} }; var getInfo = function () { return info; }; var getCallInfo = function () { return callInfo; }; // Set time to cicle querying for modem status. var setTTL = function (ttl) { self.ttl = ttl; }; // Set default duration of calls var setDefaultDuration = function (duration) { DEFAULT_DURATION = duration; }; // Subscribe to notifications of changes in modem var getNotified = function (callback) { if (typeof callback === 'function') { notify = callback; } }; // Stablish connection to the Internet var connect = function () { // TODO: Send connect commands to the modem }; // Make a phone call to the number specified var call = function (number, duration) { if (typeof duration === "undefined") { duration = DEFAULT_DURATION; } callInfo = { type: 'outgoing', number: number, maxDuration: duration, timing: {} }; command('call', number); setTimeout(function () { if (Object.keys(callInfo.timing).length<2) { restart(); } }, callInfo.maxDuration*1000); }; var answer = function (duration, timeout) { callInfo.maxDuration = duration; command('answer'); // TODO: Alert if timeout and incoming call hasn't arrived }; var hangup = function () { command('hangup'); }; var onCallStatusChange = function (callback) { if (typeof callback === 'function') { notifyCallStatus = function () { if (arguments[0] === 'ringing') { callInfo = { type: 'incoming', timing: {}, number: arguments[1] || 'unknown' }; } callInfo.timing[arguments[0]] = new Date().getTime(); if (arguments[0] === 'nocarrier') { hangup(); } var args = [ arguments[0], // Call status callInfo.timing[arguments[0]], callInfo ]; callback.apply(null, args); switch (arguments[0]) { case 'originating': setTimeout(function () { if (Object.keys(callInfo.timing).length<2) { hangup(); } }, callInfo.maxDuration*1000); break; case 'answered': setTimeout(function () { hangup(); }, callInfo.maxDuration*1000); break; case 'ringing': answer(DEFAULT_DURATION); break; } }; } }; // Function to notify changes in call status (empty at the beginning) var notifyCallStatus = function () { if (arguments[0] === 'ringing') { callInfo = { type: 'incoming', timing: {}, number: arguments[1] || 'unknown' }; } callInfo.timing[arguments[0]] = new Date().getTime(); if (arguments[0] === 'nocarrier') { hangup(); } switch (arguments[0]) { case 'answered': setTimeout(function () { hangup(); }, callInfo.maxDuration*1000); break; case 'ringing': answer(DEFAULT_DURATION); break; } }; // Function to notify changes in modem (empty at the beginning) var notify = function (field, old, data) {}; var set = function (field, data) { info.ts = new Date().getTime(); // Set new timestamp for the info // If the value doesn't change, there's no need to update, nor notify. if (info[field]!==data) { var old = info[field]; info[field] = data; notify(field, old, data); } }; // Runs atInfo after ttl ms. var cyclicATinfo = function () { setTimeout(function () { atInfo(); cyclicATinfo(); },self.ttl); }; var options = { baudrate: 57600 }; var startSerialPort = function (dev, options) { var serialPort = new SerialPort(dev, options); serialPort.on('open', function() { debug(dev + ' opened'); serialPort.on('data', function (data) { parseRead(data.toString()); }); serialPort.on('close', function() { debug(dev + ' closed'); set('status', 'Not connected'); }); // Initialization init(); cyclicATinfo(); }); return serialPort; }; var serialPort = startSerialPort(dev, options); var isOpen = function () { return serialPort.isOpen(); }; var destroy = function () { if (serialPort.isOpen()) { serialPort.close(function (error) { debug(error); }); } }; var restart = function () { command('restart'); }; var commands = { 'restart':{ 'check':'OK', 'action': function (callback) { send('AT+CFUN=4'); send('AT+CFUN=6'); if (callback) callback('OK'); } }, 'call':{ 'check':'NO CARRIER', 'action': function (number, callback) { if (info.fabricante && ( (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || (info.fabricante.indexOf('QUALCOMM')>=0) )) { send('ATQ0 V1 E1 S0=0 S6=10 S7=30 &C1 &D2 +FCLASS=0'); send('AT+CDV' + number + ';', 'call', function (results) { debug('DATA (AT+CDV): ' + results); notifyCallStatus("nocarrier"); if (callback) callback(results); }); } else { send('AT+CLIP=1'); send('ATD' + number + ';', 'call', function (results) { debug('DATA (ATD): ' + results); notifyCallStatus("nocarrier"); if (callback) callback(results); }); } } }, 'answer':{ 'check':'ATA', 'action': function (callback) { send('ATA', 'answer', function (results) { debug('DATA (ATA): ' + results); if (callback) callback(results); }); } }, 'hangup':{ 'check':['AT+CHUP', 'ATH', '+CHV'], 'action': function (callback) { if (info.fabricante && ( (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || (info.fabricante.indexOf('QUALCOMM')>=0) )) { send('AT+CHV', 'hangup', function (results) { debug('DATA (AT+CHV): ' + results); if (callback) callback(results); }); // send('AT+CHUP', 'hangup', function (results) { // debug('DATA (AT+CHV): ' + results); // if (callback) callback(results); // }); // send('ATH0', 'hangup', function (results) { // debug('DATA (ATH0): ' + results); // if (callback) callback(results); // }); } else { send('AT+CHUP', 'hangup', function (results) { debug('DATA (AT+CHUP): ' + results); if (callback) callback(results); }); } } }, 'resetConfig': { 'check':'OK', 'action': function (callback) { send('ATZ', 'resetConfig', function (results) { debug('DATA (ATZ): ' + results); if (callback) callback(results); }); } }, 'deviceInfo': { 'check':'Manufacturer', 'action': function (callback) { send('ATI', 'deviceInfo', function (results) { debug('DATA (ATI): ' + results); var res = results.replace(/\r/g,'').split('\n'); for (var i = 0; i < res.length; i++) { if (res[i].indexOf('Manufacturer')>=0) { set('fabricante',res[i].substring(res[i].search('Manufacturer:')+14).replace(/\"/g,'')); } if (res[i].indexOf('Model')>=0) { set('modelo',res[i].substring(res[i].search('Model:')+7).replace(/\"/g,'')); } if (res[i].indexOf('softversion:')>=0) { set('modelo',res[i].parseLine('softversion:').replace(/\"/g,'')); } if (res[i].indexOf('Revision')>=0) { set('firmware',res[i].substring(res[i].search('Revision:')+10).replace(/\"/g,'')); } if (res[i].indexOf('IMEI:')>=0) { set('imei',res[i].substring(res[i].search('IMEI:')+6).replace(/\"/g,'')); } } if (callback) callback(results); // if (info.fabricante && // ( // (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || // (info.fabricante.indexOf('QUALCOMM')>=0) // )) { // setTTL(30*1000); // Set ttl to 30s instead of 3s // } }); } }, 'imsi':{ 'check': function (msg) { var m = msg.split('\n'); for (var i = m.length - 1; i >= 0; i--) { var r = (((''+(m[i]*1)).length == 15 && (m[i]-0)==m[i]) || (m[i].indexOf('+CIMI:')>=0)); debug("checking... " + m[i] + " = " + r); if (r) return true; } return false; }, 'action': function (callback) { send('AT+CIMI', 'imsi', function (results) { debug('DATA (AT+CIMI): ' + results); var m = results.split('\n'); for (var i = m.length - 1; i >= 0; i--) { if (!isNaN(m[i]*1) && (m[i]*1) !== 0) { set('imsi',''+(m[i]*1)+''); if (translate('provider', ''+(m[i]*1)+'', 1) !== ''+(m[i]*1)+'') { set('provider', translate('provider', ''+(m[i]*1)+'', 1)); } } else if (m[i].indexOf('+CIMI:')>=0) { set('imsi',''+(m[i].parseLine('+CIMI:')*1)+''); if (translate('provider', ''+(m[i].parseLine('+CIMI:')*1)+'', 1) !== ''+(m[i].parseLine('+CIMI:')*1)+'') { set('provider', translate('provider', ''+(m[i].parseLine('+CIMI:')*1)+'', 1)); } } } if (callback) callback(results); }); } }, 'iccid':{ 'check':'+CRSM:', 'action': function (callback) { send('AT+CRSM=176,12258,0,0,10', 'iccid', function (results) { debug('DATA (AT+CRSM=176,12258,0,0,10): ' + results); var res = results.substring(results.search('CRSM:')+6).split(','); if (res.length>2){ var tmp = res[2].replace(/\"/g,''); var iccid = ""; for (var c = 0; c < tmp.length; c=c+2) { iccid += tmp[c+1]+tmp[c]; } iccid = iccid.split('\n')[0]; debug('ICC-ID:'+iccid); set('iccid',iccid); } if (callback) callback(results); }); } }, 'provider': { 'check':'+COPS:', 'action': function (callback) { if (info.fabricante && ( (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || (info.fabricante.indexOf('QUALCOMM')>=0) )) { set('technology_id', 8); set('technology', translate('techCOPS', '8', 1)); set('subtechnology', translate('techCOPS', '8', 2)); send('AT+COPS=0,0', 'provider', function (results) { // TODO: Keep going getting provider for CDMA }); if (callback) callback(results); } else { send('AT+COPS=3,2', 'provider'); send('AT+COPS?', 'provider', function (results) { debug('DATA (AT+COPS): ' + results); var res = results.split('\n'); for (var i = 0; i < res.length; i++) { if (res[i].indexOf('+COPS:')>=0) { var data = res[i].parseLine('+COPS:').split('\r')[0].split(','); if (data[2]) { var operador_id = data[2].replace(/\"/g,''); set('provider_id', operador_id); set('provider', translate('provider', operador_id, 1)); data[3] = parseInt(data[3]); set('technology_id', data[3]); set('technology', translate('techCOPS', ''+data[3], 1)); set('subtechnology', translate('techCOPS', ''+data[3], 2)); } } } if (callback) callback(results); }); } } }, 'spn': { 'check':'^SPN:', 'action': function (callback) { var cbk = function (results) { debug('DATA (AT^SPN): ' + results); var res = results.split('\n'); for (var i = 0; i < res.length; i++) { if (res[i].indexOf('^SPN:')>=0) { var spn = res[i].parseLine('^SPN:').split('\r')[0].split(','); spn[2] = spn[2].replace(/"/g,''); if (spn[1]=="1") { var spn_ecs2 = spn[2]; spn[2] = ''; while (spn_ecs2.length) { var spn_tmp = spn_ecs2.slice(0,2); spn_ecs2 = spn_ecs2.slice(2); if (spn_tmp !== "FF") { spn[2] += eval("\"\\u00"+spn_tmp+"\""); } } } if (spn[2]) { debug("Operador preferido: "+spn[2]); set('spn', spn[2]); if (spn[2].indexOf('FFFFF')>=0) { debug("SPN bloqueado por el operador"); } else { switch (true) { case spn[2].toLowerCase().indexOf("etb")>=0: set('provider', 'etb'); break; case spn[2].toLowerCase().indexOf("exito")>=0: set('provider', 'exito'); break; case spn[2].toLowerCase().indexOf("uff")>=0: set('provider', 'uff'); break; case spn[2].toLowerCase().indexOf("virgin")>=0: set('provider', 'virgin'); break; case spn[2].toLowerCase().indexOf("tuenti")>=0: set('provider', 'tuenti'); break; case spn[2].toLowerCase().indexOf("pepephone")>=0: set('provider', 'pepephone'); break; case spn[2].toLowerCase().indexOf("masmovil")>=0: set('provider', 'masmovil'); break; case spn[2].toLowerCase().indexOf("yoigo")>=0: set('provider', 'yoigo'); break; default: } } } } } }; send('AT^SPN=0', 'spn', cbk); send('AT^SPN=1', 'spn', cbk); } }, 'signalStrength':{ 'check':'+CSQ:', 'action': function (callback) { send('AT+CSQ', 'signalStrength', function (results) { debug('DATA (AT+CSQ?): ' + results); var rssi = results.parseLine('+CSQ:').split(',')[0]*1; debug('AT+CSQ= ' + rssi); set('rssi',rssi); if (callback) callback(results); }); } }, 'signalQuality': { 'check':'HCSQ:', 'action': function (callback) { send('AT^HCSQ?', 'signalQuality', function (results) { debug('DATA (AT^HCSQ?): ' + results); var data = results.substring(6).split('\n')[0].split(','); switch(data[0]) { case 'GSM': debug('GSM: RSSI:'+data[1]); set('hcsq',{ 'rssi': data[1]*1 }); break; case 'WCDMA': debug('WCDMA: RSSI:'+data[1]+' RSCP:'+data[2]+' ECIO:'+data[3]); set('hcsq', { 'rssi': data[1]*1, 'rscp': data[2]*1, 'ecio': data[3]*1 }); break; case 'LTE': debug('LTE: RSSI:'+data[1]+' RSRP:'+data[2]+' SINR:'+data[3]+' RSRQ:'+data[4]); set('hcsq', { 'rssi': data[1]*1, 'rsrp': data[2]*1, 'sinr': data[3]*1, 'rsrq': data[4]*1 }); break; case 'CDMA': debug('CDMA: RSSI:'+data[1]+' ECIO:'+data[2]); set('hcsq', { 'rssi': data[1]*1, 'ecio': data[2]*1 }); break; case 'EVDO': debug('EVDO: RSSI:'+data[1]+' ECIO:'+data[2]+' SINR:'+data[3]); set('hcsq', { 'rssi': data[1]*1, 'ecio': data[2]*1, 'sinr': data[3]*1 }); break; case 'CDMA-EVDO': debug('CDMA-EVDO: RSSI:'+data[1]+' ECIO:'+data[2]+' eRSSI:'+data[3]+' eECIO:'+data[4]+' SINR:'+data[4]); set('hcsq', { 'rssi': data[1]*1, 'ecio': data[2]*1, 'erssi': data[3]*1, 'eecio': data[4]*1, 'sinr': data[5]*1 }); break; default: set('hcsq', {}); } if (callback) callback(results); }); } }, 'cellInfo':{ 'check': function (msg) { var data; if (msg.indexOf('CGREG:')>=0) { data = msg.parseLine('CGREG:').split(','); if (data.length > 3) return true; } if (msg.indexOf('CREG:')>=0) { data = msg.parseLine('CREG:').split(','); if (data.length > 3) return true; } return false; }, 'action': function (callback) { if (info.fabricante && ( (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || (info.fabricante.indexOf('QUALCOMM')>=0) )) { //send('AT+CCED=1,1'); // Start automatic snapshots and dump network information (CCED) //send('AT+CCED=1,8'); // Start automatic snapshots and dump rssi (CSQ) send('AT+CCED=1'); // Start automatic snapshots and dump rssi (CSQ) and network information (CCED) } else { var cbk = function (results) { debug('DATA (AT+CGREG?): ' + results); var data = ""; if (results.indexOf('CGREG:')>=0) { data = results.parseLine('CGREG:').split('\r')[0].split(','); if (data[2]) set("lac_id", data[2].replace(/\s/g,'')); if (data[3]) set("cell_id", data[3].replace(/\s/g,'')); } if (results.indexOf('CREG:')>=0) { data = results.parseLine("CREG: ").split('\r')[0].split(','); if (data[2]) set("lac_id", data[2].replace(/\s/g,'')); if (data[3]) set("cell_id", data[3].replace(/\s/g,'')); } if (callback) callback(results); }; send('AT+CGREG=2', 'cellInfo'); send('AT+CGREG?', 'cellInfo', cbk); send('AT+CREG=2', 'cellInfo'); send('AT+CREG?', 'cellInfo', cbk); } } }, 'bandConfig':{ 'check':'SYSCFGEX:', 'action': function (callback) { send('AT^SYSCFGEX?', 'bandConfig', function (results) { debug('DATA (AT^SYSCFGEX?): ' + results); var data = results.parseLine('SYSCFGEX:').split(',')[0].replace(/\"/g,''); set('config_tech',data); if (callback) callback(results); }); } }, 'technology':{ 'check':'SYSINFOEX:', 'action': function (callback) { send('AT^SYSINFOEX', 'technology', function (results) { debug('DATA (AT^SYSINFOEX): ' + results); var data = results.parseLine('SYSINFOEX:').split('\r')[0].replace(/\"/g,'').replace(/\s/g,'').split(','); set('roaming',data[2]*1); set('technology_new_id',data[5]*1); set('technology_new',data[6]); set('subtechnology_new_id',data[7]*1); set('subtechnology_new',data[8]); if (callback) callback(results); }); } }, 'CLIP':{ 'check':'AT+CLIP', 'action': function (callback) { send('AT+CLIP?', 'CLIP'); } }, 'isConnected':{ 'check':'CGACT: ', 'action': function (callback) { send('AT+CGACT?', 'isConnected', function (results) { debug('DATA (AT+CGACT?): ' + results); var data = results.parseLine('CGACT: ').split('\r')[0].split(','); if (data[0]!=='') { set('status',data[1]*1); // ¿Connected? } else { set('status',0); } if (callback) callback(results); }); } }, 'ipAddress':{ 'check':['CGPADDR:','+CGCONTRDP:','^DHCP'], 'action': function (callback) { send('AT+CGPADDR', 'ipAddress', function (results) { debug('DATA (AT+CGPADDR): ' + results); var data = results.parseLine('+CGPADDR: ').split('\r')[0].split(',')[1].replace(/\"/g,''); if (data !== "0.0.0.0") set('ip',data); if (callback) callback(results); send('AT+CGCONTRDP', function (results) { debug('DATA (AT+CGCONTRDP): ' + results); var data = results.parseLine('+CGCONTRDP: ').split('\n')[0].split(','); if ( ( data[1] * 1 ) > 0 ) { var ip = data[3].replace(/\"/g,'').split('.'); set('ip_cont',''+ip[0]+'.'+ip[1]+'.'+ip[2]+'.'+ip[3]); if (ip.length >= 7) { set('netmask',''+ip[4]+'.'+ip[5]+'.'+ip[6]+'.'+ip[7]); } var gw = data[4].replace(/\"/g,''); if (gw !== '') { set('gw',gw); } var dns1 = data[5].replace(/\"/g,''); var dns2 = data[6].replace(/\"/g,''); set('dns1',dns1); set('dns2',dns2); } // set('status',data); // ¿Connected? if (callback) callback(results); send('AT^DHCP', function (results) { debug('DATA (AT^DHCP): ' + results); set('status', 1); // If there is a response, the modem is connected var data = results.parseLine('^DHCP:').split('\n')[0]; var perl = 'print join(",",map { join(".", unpack("C4", pack("L", hex))) } split /,/, shift)'; exec("perl -e '"+perl+"' "+data, function (err, stdout, stderr){ var ip, netmask, gw, dns1, dns2; var data = stdout.split(','); if (data[0].indexOf(".")>=0) { ip = data[0].replace(/\"/g,''); netmask = data[1].replace(/\"/g,''); gw = data[2].replace(/\"/g,''); dns1 = data[4].replace(/\"/g,''); dns2 = data[5].replace(/\"/g,''); } else { ip = 0; netmask = 0; gw = 0; dns1 = 0; dns2 = 0; } set('ip_cont',ip); set('netmask',netmask); set('gw',gw); if (dns1 != "0.0.0.0") set('dns1',dns1); if (dns2 != "0.0.0.0") set('dns2',dns2); }); // set('status',data); // ¿Connected? if (callback) callback(results); }); }); }); } } }; var command = function () { var comm = arguments[0]; var args = []; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); } if (commands[comm]) { commands[comm].action.apply(null, args); } else { console.error(comm + " not defined."); } }; var parseRead = function (message) { var found = false; var ok = function (r) { var action = readQ.splice(r,1)[0]; clearTimeout(action.timeout); if (message) { return action.callback(message); } else { return; } }; for (var r = 0; r < readQ.length; r++) { if (readQ[r]) { for (var comm in commands) { if (readQ[r].cmd === comm) { if (typeof commands[comm].check === "function") { if (commands[comm].check(message)) { return ok(r); } } else if (Array.isArray(commands[comm].check)) { for (var c = 0; c < commands[comm].check.length; c++) { if (message.indexOf(commands[comm].check[c])>=0) { return ok(r); } } } else { if (message.indexOf(commands[comm].check)>=0) { return ok(r); } } } } if (message.indexOf(readQ[r].msg)>=0) { return; } } } // If it arrives here => unsolicited messages unsolicited(message); }; String.prototype.parseLine = function (search) { if (this.indexOf(search)>=0) { return this.substring(this.indexOf(search)+search.length); } else { return this; } }; var write = function () { writeLock = true; if (writeQ.length) { var action = writeQ.shift(); serialPort.write( action.msg + '\r', function (err, results) { if (err) debug('err ' + err); if (action.callback) { action.ts = new Date().getTime(); action.timeout = setTimeout(function () { for (var r = 0; r < readQ.length; r++) { if (readQ[r].ts == action.ts) { readQ.splice(r,1); } } }, 5000); readQ.push(action); } if (writeQ.length) { debug("#writeQ:%d",writeQ.length); setTimeout(write, 100); } else { writeLock = false; } }); } }; var send = function (message, command, callback) { writeQ.push({ 'msg': message, 'cmd': command, 'callback': callback?callback:null }); if (!writeLock) { write(); } }; var unsolicited = function (message) { var data, messages, m; if (message.indexOf('^')>=0) { messages = message.split('\n'); for (m = 0; m < messages.length; m++) { switch (messages[m].substring(1).split(':')[0]) { case 'RSSI': // Signal strength debug('RSSI= '+messages[m].substring(6)*1); set('rssi', messages[m].substring(6)*1); break; case 'HCSQ': // Quality levels debug('HCSQ= '+messages[m].substring(6)); commands['AT^HCSQ?'].action(messages[m]); break; case 'MODE': // Technology attached data = messages[m].substring(6).replace(/\r/g,'').split(','); var tech_id = data[0]; var subtech_id = data[0]; if (data[1]) subtech_id = data[1]; var tech = translate('tech',tech_id,1); var subtech = translate('subtech',subtech_id,2); debug('MODE= ' + tech + ',' + subtech); set('technology', tech); set('technology_id', parseInt(tech_id)); set('subtechnology', subtech); set('subtechnology_id', parseInt(subtech_id)); break; case 'BOOT': // Pending to know what this is for debug('BOOT= '+messages[m].substring(6)); break; case 'DSFLOWRPT': // Data session statistics (send AT^DSFLOWCLR to clear them). data = messages[m].substring(11).split(','); var msg = 'DSFLOWRPT'; if (data[0]) { msg += '\n\t' + data[0] + ' s connected'; } if (data[1]) { msg += '\n\t' + (data[1]*8/1000) + ' kbps upload speed'; } if (data[2]) { msg += '\n\t' + (data[2]*8/1000) + ' kbps download speed'; } if (data[3]) { msg += '\n\t' + data[3] + ' bytes transmitted'; } if (data[4]) { msg += '\n\t' + data[4] + ' bytes received'; } if (data[5]) { msg += '\n\t' + (data[5]*8/1000) + ' kbps negociated QoS uplink speed'; } if (data[6]) { msg += '\n\t' + (data[6]*8/1000) + ' kbps negociated QoS downlink speed'; } debug(msg); break; case 'ORIG': notifyCallStatus('originating'); set('statusText', 'Dialing'); break; case 'CONF': notifyCallStatus('tone'); set('statusText', 'Dialing'); break; case 'CONN': notifyCallStatus('answered'); if (callInfo.type === 'incoming') { set('statusText', 'Incoming'); } else { set('statusText', 'Outgoing'); } break; case 'CEND': data = messages[m].substring(6).split(','); if (info.fabricante && ( (info.fabricante.indexOf('QualComm .CO') && info.modelo && info.modelo.indexOf('BU580')>=0) || (info.fabricante.indexOf('QUALCOMM')>=0) )) { if (callInfo.timing && callInfo.timing.answered) { callInfo.duration = (Date.now() - callInfo.timing.answered)/1000; } else { callInfo.duration = 0; } // TODO: Get endingStatus and hangupCause for CDMA callInfo.endStatus = 16; callInfo.hangupCause = 0; } else { callInfo.duration = data[1]*1; callInfo.endStatus = data[2]*1; callInfo.hangupCause = data[3]*1; } notifyCallStatus('hangup'); set('statusText', 'Free'); break; default: if (messages[m].length>1) { console.warn('UNKNOWN UNSOLICITED \'^\' MESSAGE: ' + messages[m]); } } } } else if (message.indexOf('+')>=0) { messages = message.split('\n'); for (m = 0; m < messages.length; m++) { switch (messages[m].substring(1).split(':')[0]) { case 'CREG': data = messages[m].parseLine('+CREG: ').split(','); debug('Unsolicited CREG= ' + messages[m]); if (data[1]) set('lac_id', data[1].replace(/\s/g,'')); if (data[2]) set('cell_id', data[2].replace(/\s/g,'')); break; case 'CSQ': var rssi = messages[m].parseLine('+CSQ: ').split(',')[0]*1; debug('AT+CSQ (unsolicited)= ' + rssi); set('rssi',rssi); break; case 'CCED': data = messages[m].parseLine('+CCED:').split(','); debug('Unsolicited CCED= ' + messages[m]); //Main Cell: <mode>, <band class>, <Channel #>, SID, NID, <Base Station P Rev>, [<Pilot PN offset>], // <Base Station ID>, [<Slot cycle index>], [<Ec/Io>], <Rx power>, <Tx power>, <Tx Adj> // TODO: Keep parsing if (data[3]) { set('provider_id', ''+data[3].replace(/\s/g,'')); set('provider', translate('provider', ''+data[3].replace(/\s/g,''), 1)); } if (data[4]) { set("number", data[4].replace(/\s/g,'')); } break; case 'CLIP': //+CLIP:NUMBER,129,,,,0 debug('Message CLIP:' + messages[m]); set('statusText','Ringing'); notifyCallStatus('ringing', messages[m].parseLine('+CLIP:').split(',')[0].replace(/\s/g,'').replace(/"/g,'')); break; default: if (messages[m].length>1) { console.warn('UNKNOWN UNSOLICITED \'+\' MESSAGE: ' + messages[m]); } } } } else { messages = message.split('\n'); for (m = 0; m < messages.length; m++) { switch (messages[m].split(':')[0].split('\r')[0]) { case 'RING': debug('Message RING:' + messages[m]); set('statusText','Ringing'); break; default: if (messages[m].length>1) { debug('ELSE UNSOLICITED ' + message); } } } } }; var init = function () { // Initialization command('resetConfig',function () { command('deviceInfo', function () { atInfo(); set('statusText','Free'); }); }); }; var atInfo = function () { command('imsi'); // IMSI command('iccid'); // ICC-ID command('provider'); // Provider & Technology command('signalStrength'); // Signal strength command('signalQuality'); // Signal quality command('cellInfo'); // LAC & Cell ID command('technology'); // Frequency command('bandConfig'); // Frequency command('isConnected'); // Is Connected? command('ipAddress'); // IP Address command('spn'); // SIM Preferred Network (detect virtual operator name) }; return { isOpen: isOpen, setTTL: setTTL, setDefaultDuration: setDefaultDuration, destroy: destroy, getInfo: getInfo, getCallInfo: getCallInfo, getNotified: getNotified, connect: connect, call: call, answer: answer, hangup: hangup, restart: restart, onCallStatusChange: onCallStatusChange }; }; module.exports = ModemSerial;