Commit 0ea228ab658abc5acbb5ce20c5a799de4ee67d1f

Authored by Adhidarma Hadiwinoto
1 parent ea7f35320e
Exists in master

sms and ussd

Showing 6 changed files with 180 additions and 46 deletions Side-by-side Diff

lib/modem-commands/index.js
... ... @@ -4,10 +4,14 @@ exports.MUTEX_COMMAND = MUTEX_COMMAND;
4 4 const MUTEX_SUBCOMMAND = 'SUBCOMMAND';
5 5 exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND;
6 6  
  7 +const CTRLZ = '\u001a';
  8 +
7 9 const pdu = require('node-pdu');
  10 +const uuidv1 = require('uuid/v1');
8 11  
9 12 const ParserReadline = require('@serialport/parser-readline');
10 13 const ParserRegex = require('@serialport/parser-regex');
  14 +const ParserReady = require('@serialport/parser-ready');
11 15  
12 16 const logger = require('komodo-sdk/logger');
13 17 const mutex = require('../mutex-common');
... ... @@ -27,8 +31,9 @@ function writeToPort(data) {
27 31 });
28 32 });
29 33 }
  34 +exports.writeToPort = writeToPort;
30 35  
31   -exports.writeToPortAndWaitForReadline = function writeToPortAndWaitForReadline(cmd, lockName) {
  36 +function writeToPortAndWaitForReadline(cmd, lockName) {
32 37 let resolved = false;
33 38  
34 39 return new Promise(async (resolve) => {
... ... @@ -46,9 +51,10 @@ exports.writeToPortAndWaitForReadline = function writeToPortAndWaitForReadline(c
46 51 port.pipe(parser);
47 52 await writeToPort(cmd);
48 53 });
49   -};
  54 +}
  55 +exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline;
50 56  
51   -exports.writeToPortAndWaitForOkOrError = function writeToPortAndWaitForOkOrError(cmd, lockName) {
  57 +function writeToPortAndWaitForOkOrError(cmd, lockName) {
52 58 return new Promise(async (resolve) => {
53 59 const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ });
54 60 parser.on('data', (data) => {
... ... @@ -61,22 +67,23 @@ exports.writeToPortAndWaitForOkOrError = function writeToPortAndWaitForOkOrError
61 67 port.pipe(parser);
62 68 await writeToPort(cmd);
63 69 });
64   -};
  70 +}
  71 +exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError;
65 72  
66   -exports.sleep = function sleep(ms) {
  73 +function sleep(ms) {
67 74 return new Promise((resolve) => {
68 75 setTimeout(() => {
69 76 resolve();
70 77 }, ms || 0);
71 78 });
72   -};
73   -
  79 +}
  80 +exports.sleep = sleep;
74 81  
75 82 exports.setPort = function setPort(val) {
76 83 port = val;
77 84 };
78 85  
79   -exports.querySignalQuality = function querySignalQuality() {
  86 +function querySignalQuality() {
80 87 return new Promise(async (resolve) => {
81 88 if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) {
82 89 resolve(false);
... ... @@ -87,18 +94,20 @@ exports.querySignalQuality = function querySignalQuality() {
87 94 mutex.unlock(MUTEX_COMMAND, 'querySignalQuality');
88 95 resolve(true);
89 96 });
90   -};
  97 +}
  98 +exports.querySignalQuality = querySignalQuality;
91 99  
92   -exports.queryCOPS = function queryCOPS(lockName) {
  100 +function queryCOPS(lockName) {
93 101 return new Promise(async (resolve) => {
94 102 await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS');
95 103 await writeToPort('AT+COPS?\r');
96 104 mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS');
97 105 resolve(true);
98 106 });
99   -};
  107 +}
  108 +exports.queryCOPS = queryCOPS;
100 109  
101   -exports.queryCOPSAndSignalQuality = function queryCOPSAndSignalQuality(skipOnLocked) {
  110 +function queryCOPSAndSignalQuality(skipOnLocked) {
102 111 return new Promise(async (resolve) => {
103 112 if (!skipOnLocked) {
104 113 await mutex.lock(MUTEX_COMMAND);
... ... @@ -107,15 +116,16 @@ exports.queryCOPSAndSignalQuality = function queryCOPSAndSignalQuality(skipOnLoc
107 116 return;
108 117 }
109 118  
110   - await this.writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND);
111   - await this.writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND);
  119 + await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND);
  120 + await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND);
112 121  
113 122 mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality');
114 123 resolve(true);
115 124 });
116   -};
  125 +}
  126 +exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality;
117 127  
118   -exports.queryIMEI = function queryIMEI(lockName) {
  128 +function queryIMEI(lockName) {
119 129 return new Promise(async (resolve) => {
120 130 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
121 131 parser.on('data', (data) => {
... ... @@ -132,9 +142,10 @@ exports.queryIMEI = function queryIMEI(lockName) {
132 142 port.pipe(parser);
133 143 await writeToPort('AT+CGSN\r');
134 144 });
135   -};
  145 +}
  146 +exports.queryIMEI = queryIMEI;
136 147  
137   -exports.queryIMSI = function queryIMSI(lockName) {
  148 +function queryIMSI(lockName) {
138 149 return new Promise(async (resolve) => {
139 150 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
140 151 parser.on('data', (data) => {
... ... @@ -151,13 +162,14 @@ exports.queryIMSI = function queryIMSI(lockName) {
151 162 port.pipe(parser);
152 163 await writeToPort('AT+CIMI\r');
153 164 });
154   -};
  165 +}
  166 +exports.queryIMSI = queryIMSI;
155 167  
156 168 exports.queryIMEIAndIMSI = async function queryIMEIAndIMSI() {
157 169 await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
158 170  
159   - const imei = await this.queryIMEI(MUTEX_SUBCOMMAND);
160   - const imsi = await this.queryIMSI(MUTEX_SUBCOMMAND);
  171 + const imei = await queryIMEI(MUTEX_SUBCOMMAND);
  172 + const imsi = await queryIMSI(MUTEX_SUBCOMMAND);
161 173  
162 174 await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
163 175 return { imei, imsi };
... ... @@ -201,16 +213,94 @@ exports.queryModel = function queryModel(lockName) {
201 213 });
202 214 };
203 215  
204   -exports.sendSMS = function sendSMS(destination, msg) {
205   - return new Promise(async (resolve) => {
206   -
207   - });
208   -};
  216 +async function sendCtrlZ() {
  217 + await writeToPort(CTRLZ);
  218 +}
  219 +exports.sendCtrlZ = sendCtrlZ;
209 220  
210   -exports.initATCommands = async function initATCommands() {
  221 +async function initATCommands() {
211 222 await mutex.lock(MUTEX_COMMAND, 'INIT MODEM');
212   - await this.writeToPortAndWaitForOkOrError('\rATE0\r', MUTEX_SUBCOMMAND);
  223 + await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND);
213 224 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
214 225 await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND);
215 226 mutex.unlock(MUTEX_COMMAND, 'INIT MODEM');
  227 +}
  228 +exports.initATCommands = initATCommands;
  229 +
  230 +function sendCMGSPdu(pduLength) {
  231 + return new Promise((resolve) => {
  232 + const parser = new ParserReady({ delimiter: '>' });
  233 + parser.on('data', () => {
  234 + logger.verbose('Got ">" message prompt, gonna to write PDU message');
  235 + port.unpipe(parser);
  236 + mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
  237 + resolve(true);
  238 + });
  239 +
  240 + mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
  241 + port.pipe(parser);
  242 + writeToPort(`AT+CMGS=${pduLength}\r`);
  243 + });
  244 +}
  245 +
  246 +exports.sendSMS = function sendSMS(destination, msg) {
  247 + return new Promise(async (resolve) => {
  248 + await mutex.lock(MUTEX_COMMAND, 'sendSMS');
  249 +
  250 + if (!destination || !destination.trim()) {
  251 + resolve(false);
  252 + return;
  253 + }
  254 +
  255 + if (!msg || !msg.trim()) {
  256 + resolve(false);
  257 + return;
  258 + }
  259 +
  260 + const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+');
  261 + logger.verbose(`Sending sms to ${correctedDestination}`, { msg });
  262 +
  263 + await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
  264 +
  265 + const submit = pdu.Submit();
  266 + submit.setAddress(correctedDestination);
  267 + submit.setData(msg.trim());
  268 + submit.getType().setSrr(0);
  269 +
  270 + await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1);
  271 + await writeToPort(`${submit.toString()}${CTRLZ}`);
  272 +
  273 + mutex.unlock(MUTEX_COMMAND, 'sendSMS');
  274 + resolve(true);
  275 + });
  276 +};
  277 +
  278 +exports.executeUSSD = function executeUSSD(code, _includeCUSD2, _sessionId) {
  279 + return new Promise(async (resolve) => {
  280 + const includeCUSD2 = _includeCUSD2 || 0;
  281 + const sessionId = _sessionId || uuidv1();
  282 +
  283 + async function responseHandler(data) {
  284 + logger.verbose('Processing USSD response', { data });
  285 + parsers.setUssdCallback(null);
  286 +
  287 + if (includeCUSD2 === 1 || includeCUSD2 === 2) {
  288 + await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
  289 + }
  290 +
  291 + mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
  292 + resolve(data);
  293 + }
  294 +
  295 + mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
  296 + parsers.setUssdCallback(responseHandler);
  297 +
  298 + await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND);
  299 +
  300 + if (includeCUSD2 === -1 || includeCUSD2 === 2) {
  301 + await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
  302 + }
  303 +
  304 + await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND);
  305 + });
216 306 };
1   -const MAX_LAST_DATA_AGE_MS = 2 * 60 * 1000;
2   -const INTERVAL_BETWEEN_IDLE_CHECK_MS = 30 * 1000;
  1 +const DEFAULT_MAX_LAST_DATA_AGE_MS = 2 * 60 * 1000;
  2 +const DEFAULT_INTERVAL_BETWEEN_IDLE_CHECK_MS = 30 * 1000;
3 3  
4 4 const config = require('komodo-sdk/config');
5 5 const logger = require('komodo-sdk/logger');
... ... @@ -24,11 +24,11 @@ const modemInfo = {
24 24 if (!config.disable_idle_check) {
25 25 setInterval(() => {
26 26 const deltaMs = new Date() - Math.max(modemInfo.lastWriteTs, modemInfo.startTime);
27   - if (deltaMs >= (config.max_last_data_age_ms || MAX_LAST_DATA_AGE_MS)) {
  27 + if (deltaMs >= (config.max_last_data_age_ms || DEFAULT_MAX_LAST_DATA_AGE_MS)) {
28 28 logger.warn(`Modem idle for ${deltaMs} ms. Modem stucked? Terminating!`);
29 29 process.exit(1);
30 30 }
31   - }, INTERVAL_BETWEEN_IDLE_CHECK_MS);
  31 + }, config.interval_beetwen_signal_strength_ms || DEFAULT_INTERVAL_BETWEEN_IDLE_CHECK_MS);
32 32 }
33 33  
34 34 module.exports = modemInfo;
lib/serialport-parsers.js
... ... @@ -23,11 +23,34 @@ exports.getPort = function getPort() {
23 23 return port;
24 24 };
25 25  
26   -function parsePdu(data) {
  26 +let ussdCallback = null;
  27 +function setUssdCallback(cb) {
  28 + ussdCallback = cb;
  29 +}
  30 +exports.setUssdCallback = setUssdCallback;
  31 +
  32 +function isAlphaNumeric(str) {
  33 + const len = str.length;
  34 + // eslint-disable-next-line no-plusplus
  35 + for (let i = 0; i < len; i++) {
  36 + const code = str.charCodeAt(i);
  37 + if (!(code > 47 && code < 58) // numeric (0-9)
  38 + && !(code > 64 && code < 91) // upper alpha (A-Z)
  39 + && !(code > 96 && code < 123)) { // lower alpha (a-z)
  40 + return false;
  41 + }
  42 + }
  43 + return true;
  44 +}
  45 +
  46 +function parsePdu(_data) {
  47 + const data = _data && _data.toString().trim().toUpperCase();
  48 +
27 49 if (!data) return null;
  50 + if (!isAlphaNumeric(data)) return null;
28 51  
29 52 try {
30   - const result = nodePdu.parse(data.toString().trim() || '');
  53 + const result = nodePdu.parse(data);
31 54 return result;
32 55 } catch (e) {
33 56 return null;
... ... @@ -112,16 +135,14 @@ parserReadline.on(&#39;data&#39;, (data) =&gt; {
112 135 if (!data) return;
113 136  
114 137 const pduParsed = parsePdu(data);
115   - if (pduParsed) {
116   - logger.verbose('PDU parsed', { type: (typeof pduParsed.getType === 'function') && pduParsed.getType() });
117   - }
118   -
119 138 if (pduParsed && pduParsed.constructor.name !== 'Deliver') {
120   - logger.warn('WARN-9DA32C41: Unknown PDU message type name. PLEASE REPORT IT TO DEVELOPER AT TEKTRANS', { typeName: pduParsed.constructor.name, type: pduParsed.getType(), data: data.toString().trim() });
  139 + const pduType = pduParsed.getType();
  140 + logger.warn('WARN-9DA32C41: Unknown PDU message type name. PLEASE REPORT IT TO DEVELOPER AT TEKTRANS', { typeName: pduParsed.constructor.name, pduType, data: data.toString().trim() });
121 141 }
122 142  
123 143 if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) {
124   - logger.verbose('Got a PDU SMS-DELIVER', { type: pduParsed.getType() });
  144 + const pduType = pduParsed.getType();
  145 + logger.verbose('Got a PDU SMS-DELIVER', { pduType });
125 146 onPduDeliver(data, pduParsed);
126 147 } else if (isResultCodeIs(data, 'CSQ')) {
127 148 logger.verbose('Got a signal quality report', { data: data.toString() });
... ... @@ -133,6 +154,14 @@ parserReadline.on(&#39;data&#39;, (data) =&gt; {
133 154 logger.verbose('Got a new message report', { data: data.toString() });
134 155 } else if (isResultCodeIs(data, 'CMTI')) {
135 156 logger.verbose('Got a new message notification report', { data: data.toString() });
  157 + } else if (isResultCodeIs(data, 'CUSD')) {
  158 + logger.verbose('Got a USSD command response', { data: data.toString() });
  159 + if (ussdCallback && typeof ussdCallback === 'function') {
  160 + logger.verbose('Calling USSD callback');
  161 + ussdCallback(data.toString());
  162 + } else {
  163 + logger.verbose('Skip unwanted USSD response');
  164 + }
136 165 }
137 166 });
138 167  
... ... @@ -20,7 +20,8 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er
20 20 process.exit(1);
21 21 }
22 22  
23   - await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r');
  23 + await modemCommands.sendCtrlZ();
  24 + // await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r');
24 25 await modemCommands.writeToPortAndWaitForOkOrError('AT&FE0\r');
25 26 await modemCommands.writeToPortAndWaitForOkOrError('AT+CMGF=0\r');
26 27 await modemCommands.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r');
... ... @@ -32,6 +33,12 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er
32 33 await modemCommands.queryCOPSAndSignalQuality();
33 34 logger.info('Modem state', modemInfo);
34 35  
  36 + // await modemCommands.sendSMS('628128364883', `coba pakai pdu ${new Date()}`);
  37 + // await modemCommands.sendSMS('+6282210008543', `coba pakai pdu ${new Date()}`);
  38 + // await modemCommands.sendSMS('6281809903333', `coba pakai pdu ${new Date()}`);
  39 + // const ussdResponse = await modemCommands.executeUSSD('*888#', 2);
  40 + // logger.info('USSD RESPONSE', { ussdResponse });
  41 +
35 42 setInterval(async () => {
36 43 await modemCommands.initATCommands();
37 44 await modemCommands.queryManufacturer();
... ... @@ -39,7 +46,7 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er
39 46 await modemCommands.queryIMEIAndIMSI();
40 47 await modemCommands.queryCOPSAndSignalQuality();
41 48 logger.info('Modem state', modemInfo);
42   - }, (config && config.interval_beetwen_signal_strength_ms) || 30000);
  49 + }, config.interval_beetwen_signal_strength_ms || 30000);
43 50 });
44 51  
45 52 global.MODEM_PORT = port;
... ... @@ -4155,6 +4155,11 @@
4155 4155 "is-fullwidth-code-point": "^2.0.0"
4156 4156 }
4157 4157 },
  4158 + "sms-pdu-node": {
  4159 + "version": "0.1.2",
  4160 + "resolved": "https://registry.npmjs.org/sms-pdu-node/-/sms-pdu-node-0.1.2.tgz",
  4161 + "integrity": "sha1-lyaFkBVvBYdOBNN4NpIZ6FlfhdM="
  4162 + },
4158 4163 "snapdragon": {
4159 4164 "version": "0.8.2",
4160 4165 "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
... ... @@ -4844,9 +4849,9 @@
4844 4849 "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
4845 4850 },
4846 4851 "uuid": {
4847   - "version": "3.3.2",
4848   - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
4849   - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
  4852 + "version": "3.3.3",
  4853 + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
  4854 + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
4850 4855 },
4851 4856 "validate-npm-package-license": {
4852 4857 "version": "3.0.4",
... ... @@ -29,6 +29,7 @@
29 29 "@serialport/parser-delimiter": "^2.0.2",
30 30 "@serialport/parser-inter-byte-timeout": "^1.1.0",
31 31 "@serialport/parser-readline": "^2.0.2",
  32 + "@serialport/parser-ready": "^2.0.2",
32 33 "@serialport/parser-regex": "^2.0.2",
33 34 "express": "^4.17.1",
34 35 "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git",
... ... @@ -38,6 +39,8 @@
38 39 "pdu": "^1.1.0",
39 40 "request": "^2.88.0",
40 41 "serialport": "^7.1.5",
41   - "serialport-gsm": "^3.2.0"
  42 + "serialport-gsm": "^3.2.0",
  43 + "sms-pdu-node": "^0.1.2",
  44 + "uuid": "^3.3.3"
42 45 }
43 46 }