Commit b53db5e8b65ce8c11129b086cc6c818f3a21c1f4

Authored by Adhidarma Hadiwinoto
1 parent 6f494f86f0
Exists in master

Migrated to modem-commands. I hope so

Showing 11 changed files with 123 additions and 36 deletions Inline Diff

1 'use strict'; 1 'use strict';
2 2
3 process.chdir(__dirname); 3 process.chdir(__dirname);
4 const fs = require('fs'); 4 const fs = require('fs');
5 5
6 fs.writeFileSync('pid.txt', process.pid); 6 fs.writeFileSync('pid.txt', process.pid);
7 7
8 const path = require('path'); 8 const path = require('path');
9 const config = require('komodo-sdk/config'); 9 const config = require('komodo-sdk/config');
10 10
11 if (!config 11 if (!config
12 || !config.modem 12 || !config.modem
13 || !config.modem.device 13 || !config.modem.device
14 || !fs.existsSync(config.modem.device) 14 || !fs.existsSync(config.modem.device)
15 ) { 15 ) {
16 process.exit(4); 16 process.exit(4);
17 } 17 }
18 18
19 process.title = (typeof config.name === 'string' && config.name.trim()) 19 process.title = (typeof config.name === 'string' && config.name.trim())
20 ? config.name.toUpperCase() 20 ? config.name.toUpperCase()
21 : `MODEM-${path.basename(config.modem.device).replace('tty', '').toUpperCase()}`; 21 : `MODEM-${path.basename(config.modem.device).replace('tty', '').toUpperCase()}`;
22 22
23 global.KOMODO_LOG_LABEL = (typeof config.name === 'string' && config.name.trim()) 23 global.KOMODO_LOG_LABEL = (typeof config.name === 'string' && config.name.trim())
24 ? `KOMODO-MODEM@${config.name.toUpperCase()}` 24 ? `KOMODO-MODEM@${config.name.toUpperCase()}`
25 : `KOMODO-MODEM@${path.basename(config.modem.device).replace('tty', '').toUpperCase()}`; 25 : `KOMODO-MODEM@${path.basename(config.modem.device).replace('tty', '').toUpperCase()}`;
26 26
27 require('./lib/http-command-server'); 27 require('./lib/http-command-server');
28 require('./lib/modem'); 28
29 // require('./lib/modem');
30 require('./lib/bootstrap');
29 31
1 /** 1 /**
2 * Modul modem bootstrap 2 * Modul modem bootstrap
3 * @module bootstrap 3 * @module
4 * @since 2019-09-25 4 * @since 2019-09-25
5 */ 5 */
6 6
7 const DEFAULT_BAUDRATE = 115200;
8
9 const fs = require('fs');
7 const SerialPort = require('serialport'); 10 const SerialPort = require('serialport');
8 11
9 const config = require('komodo-sdk/config'); 12 const config = require('komodo-sdk/config');
10 const logger = require('komodo-sdk/logger'); 13 const logger = require('komodo-sdk/logger');
11 14
12 const parsers = require('./serialport-parsers'); 15 const parsers = require('./serialport-parsers');
13 const modemCommands = require('./modem-commands'); 16 const modemCommands = require('./modem-commands');
17 const ussdCodes = require('./modem-commands/ussd-codes');
18
14 const modemInfo = require('./modem-info'); 19 const modemInfo = require('./modem-info');
20 const registerModem = require('./register-modem');
21
22 logger.info('Bootstraping modem');
15 23
16 const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (err) => { 24 if (!fs.existsSync(config.modem.device)) {
25 logger.error(`Modem not found on ${config.modem.device}. Terminating.`);
26 process.exit(1);
27 }
28
29 const port = new SerialPort(config.modem.device, {
30 baudRate: (config.modem && config.modem.options && config.modem.options.baudRate)
31 || DEFAULT_BAUDRATE,
32 }, async (err) => {
17 if (err) { 33 if (err) {
18 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`); 34 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`);
19 process.exit(1); 35 process.exit(1);
20 } 36 }
21 37
38 global.MODEM_PORT = port;
39 parsers.setPort(port);
40 // modemCommands.setPort(port);
41 port.pipe(parsers.parserReadline);
42
22 await modemCommands.writeToPortAndWaitForOkOrError(`${modemCommands.CTRLZ}AT&FE0\r`); 43 await modemCommands.writeToPortAndWaitForOkOrError(`${modemCommands.CTRLZ}AT&FE0\r`);
23 await modemCommands.initATCommands(); 44 await modemCommands.initATCommands();
24 await modemCommands.queryManufacturer(); 45 await modemCommands.queryManufacturer();
25 await modemCommands.queryModel(); 46 await modemCommands.queryModel();
26 await modemCommands.queryIMEIAndIMSI(); 47 await modemCommands.queryIMEIAndIMSI();
27 await modemCommands.queryCOPSAndSignalQuality(); 48 await modemCommands.queryCOPSAndSignalQuality();
28 49
29 logger.info('Modem state', modemInfo); 50 logger.info('Modem state', modemInfo);
51 registerModem();
52
53 if (config.debug_sms_destination_on_start) {
54 await modemCommands.sendSMS(config.debug_sms_destination_on_start,
55 `${config.name} (${modemInfo.imsi || 'UNKNOWN'}) started on ${new Date()}`);
56 }
57
58 if (config.debug_ussd_code_on_start) {
59 const ussdResponse = await modemCommands.executeUSSD(config.debug_ussd_code_on_start, 2);
60 logger.info('USSD RESPONSE', { command: config.debug_ussd_code_on_start, ussdResponse });
61 }
62
63 if ((modemInfo.networkName && modemInfo.networkName === 'TELKOMSEL') || config.bootstrap_tsel_sms_quota_check) {
64 const ussdResponse = await modemCommands.executeUSSD(ussdCodes.TSEL_SMS_QUOTA_CHECK, 1);
65 logger.info('USSD RESPONSE', { command: config.debug_ussd_code_on_start, ussdResponse });
66 }
30 67
31 setInterval(async () => { 68 setInterval(async () => {
32 await modemCommands.initATCommands(); 69 await modemCommands.initATCommands();
33 await modemCommands.queryManufacturer(); 70 await modemCommands.queryManufacturer();
34 await modemCommands.queryModel(); 71 await modemCommands.queryModel();
35 await modemCommands.queryIMEIAndIMSI(); 72 await modemCommands.queryIMEIAndIMSI();
36 await modemCommands.queryCOPSAndSignalQuality(); 73 await modemCommands.queryCOPSAndSignalQuality();
37 logger.info('Modem state', modemInfo); 74 logger.info('Modem state', modemInfo);
75 registerModem();
38 }, config.interval_beetwen_signal_strength_ms || 30000); 76 }, config.interval_beetwen_signal_strength_ms || 30000);
39 }); 77 });
40
lib/http-command-server/router-sms.js
1 'use strict'; 1 'use strict';
2 2
3 const express = require('express'); 3 const express = require('express');
4 4
5 const modem = require('../modem'); 5 // const modem = require('../modem');
6 const modem = require('../modem-commands');
6 7
7 const router = express.Router(); 8 const router = express.Router();
8 module.exports = router; 9 module.exports = router;
9 10
10 function handlerIndex(req, res) { 11 function handlerIndex(req, res) {
11 if (!req.query || !req.query.number || !req.query.msg) { 12 if (!req.query || !req.query.number || !req.query.msg) {
12 res.json({ 13 res.json({
13 status: 'NOT-OK', 14 status: 'NOT-OK',
14 error: 'INVALID-PARAMETER', 15 error: 'INVALID-PARAMETER',
15 message: 'Invalid parameter. Missing number or msg parameter.', 16 message: 'Invalid parameter. Missing number or msg parameter.',
16 }); 17 });
17 return; 18 return;
18 } 19 }
19 20
20 res.json({ 21 res.json({
21 status: 'OK', 22 status: 'OK',
22 error: false, 23 error: false,
23 message: 'Message queued.', 24 message: 'Message queued.',
24 }); 25 });
25 modem.sendSMS(req.query.number, req.query.msg); 26 modem.sendSMS(req.query.number, req.query.msg);
26 } 27 }
27 28
28 router.get('/', handlerIndex); 29 router.get('/', handlerIndex);
29 30
lib/modem-commands/index.js
1 /** 1 /**
2 * Modul modem-commands 2 * Modul modem-commands
3 * 3 *
4 * @module modem-commands 4 * @module modem-commands
5 */ 5 */
6 6
7 7
8 /** 8 /**
9 * Label mutex command 9 * Label mutex command
10 * @static 10 * @static
11 */ 11 */
12 const MUTEX_COMMAND = 'COMMAND'; 12 const MUTEX_COMMAND = 'COMMAND';
13 13
14 /** 14 /**
15 * Label mutex subcommand 15 * Label mutex subcommand
16 * @static 16 * @static
17 */ 17 */
18 const MUTEX_SUBCOMMAND = 'SUBCOMMAND'; 18 const MUTEX_SUBCOMMAND = 'SUBCOMMAND';
19 19
20 /** 20 /**
21 * CTRL-Z string 21 * CTRL-Z string
22 * @static 22 * @static
23 */ 23 */
24 const CTRLZ = '\u001a'; 24 const CTRLZ = '\u001a';
25 25
26 26
27 const pdu = require('node-pdu'); 27 const pdu = require('node-pdu');
28 const uuidv1 = require('uuid/v1'); 28 const uuidv1 = require('uuid/v1');
29 29
30 const ParserReadline = require('@serialport/parser-readline'); 30 const ParserReadline = require('@serialport/parser-readline');
31 const ParserRegex = require('@serialport/parser-regex'); 31 const ParserRegex = require('@serialport/parser-regex');
32 const ParserReady = require('@serialport/parser-ready'); 32 const ParserReady = require('@serialport/parser-ready');
33 33
34 const logger = require('komodo-sdk/logger'); 34 const logger = require('komodo-sdk/logger');
35 const mutex = require('../mutex-common'); 35 const mutex = require('../mutex-common');
36 const parsers = require('../serialport-parsers'); 36 const parsers = require('../serialport-parsers');
37 const modemInfo = require('../modem-info'); 37 const modemInfo = require('../modem-info');
38 38
39 // let port; 39 // let port;
40 40
41 function writeToPort(data) { 41 function writeToPort(data) {
42 return new Promise((resolve) => { 42 return new Promise((resolve) => {
43 const port = global.MODEM_PORT; 43 const port = global.MODEM_PORT;
44 44
45 modemInfo.lastWriteTs = new Date(); 45 modemInfo.lastWriteTs = new Date();
46 port.write(data, (err, bytesWritten) => { 46 port.write(data, (err, bytesWritten) => {
47 if (err) logger.warn(`ERROR: ${err.toString()}`); 47 if (err) logger.warn(`ERROR: ${err.toString()}`);
48 48
49 logger.verbose('OUTGOING', { data: data.toString(), bytesWritten, err }); 49 logger.verbose('OUTGOING', { data: data.toString(), bytesWritten, err });
50 resolve(bytesWritten); 50 resolve(bytesWritten);
51 }); 51 });
52 }); 52 });
53 } 53 }
54 54
55 function writeToPortAndWaitForReadline(cmd, lockName) { 55 function writeToPortAndWaitForReadline(cmd, lockName) {
56 const port = global.MODEM_PORT; 56 const port = global.MODEM_PORT;
57 let resolved = false; 57 let resolved = false;
58 58
59 return new Promise(async (resolve) => { 59 return new Promise(async (resolve) => {
60 const parser = new ParserReadline({ delimiter: parsers.PARSER_READLINE_DELIMITER }); 60 const parser = new ParserReadline({ delimiter: parsers.PARSER_READLINE_DELIMITER });
61 parser.on('data', (data) => { 61 parser.on('data', (data) => {
62 port.unpipe(parser); 62 port.unpipe(parser);
63 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim()); 63 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
64 if (!resolved) { 64 if (!resolved) {
65 resolved = true; 65 resolved = true;
66 resolve(data); 66 resolve(data);
67 } 67 }
68 }); 68 });
69 69
70 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim()); 70 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
71 port.pipe(parser); 71 port.pipe(parser);
72 await writeToPort(cmd); 72 await writeToPort(cmd);
73 }); 73 });
74 } 74 }
75 75
76 function writeToPortAndWaitForOkOrError(cmd, lockName) { 76 function writeToPortAndWaitForOkOrError(cmd, lockName) {
77 return new Promise(async (resolve) => { 77 return new Promise(async (resolve) => {
78 const port = global.MODEM_PORT; 78 const port = global.MODEM_PORT;
79 const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ }); 79 const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ });
80 80
81 parser.on('data', (data) => { 81 parser.on('data', (data) => {
82 port.unpipe(parser); 82 port.unpipe(parser);
83 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim()); 83 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
84 resolve(data); 84 resolve(data);
85 }); 85 });
86 86
87 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim()); 87 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
88 port.pipe(parser); 88 port.pipe(parser);
89 await writeToPort(cmd); 89 await writeToPort(cmd);
90 }); 90 });
91 } 91 }
92 92
93 /** 93 /**
94 * Sleep async 94 * Sleep async
95 * @static 95 * @static
96 * @param {number} ms - Milliseconds to sleep 96 * @param {number} ms - Milliseconds to sleep
97 * @return {Promise} 97 * @return {Promise}
98 */ 98 */
99 function sleep(ms) { 99 function sleep(ms) {
100 return new Promise((resolve) => { 100 return new Promise((resolve) => {
101 setTimeout(() => { 101 setTimeout(() => {
102 resolve(); 102 resolve();
103 }, ms || 0); 103 }, ms || 0);
104 }); 104 });
105 } 105 }
106 106
107 /** 107 /**
108 * Set port 108 * Set port
109 * @static 109 * @static
110 * @param {SerialPort} val 110 * @param {SerialPort} val
111 */ 111 */
112 112
113 /* 113 /*
114 function setPort(val) { 114 function setPort(val) {
115 // port = val || global.MODEM_PORT; 115 // port = val || global.MODEM_PORT;
116 } 116 }
117 */ 117 */
118 118
119 function querySignalQuality() { 119 function querySignalQuality() {
120 return new Promise(async (resolve) => { 120 return new Promise(async (resolve) => {
121 if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) { 121 if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) {
122 resolve(false); 122 resolve(false);
123 return; 123 return;
124 } 124 }
125 125
126 await writeToPort('AT+CSQ\r'); 126 await writeToPort('AT+CSQ\r');
127 mutex.unlock(MUTEX_COMMAND, 'querySignalQuality'); 127 mutex.unlock(MUTEX_COMMAND, 'querySignalQuality');
128 resolve(true); 128 resolve(true);
129 }); 129 });
130 } 130 }
131 131
132 function queryCOPS(lockName) { 132 function queryCOPS(lockName) {
133 return new Promise(async (resolve) => { 133 return new Promise(async (resolve) => {
134 await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS'); 134 await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS');
135 await writeToPort('AT+COPS?\r'); 135 await writeToPort('AT+COPS?\r');
136 mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS'); 136 mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS');
137 resolve(true); 137 resolve(true);
138 }); 138 });
139 } 139 }
140 140
141 function queryCOPSAndSignalQuality(skipOnLocked) { 141 function queryCOPSAndSignalQuality(skipOnLocked) {
142 return new Promise(async (resolve) => { 142 return new Promise(async (resolve) => {
143 if (!skipOnLocked) { 143 if (!skipOnLocked) {
144 await mutex.lock(MUTEX_COMMAND); 144 await mutex.lock(MUTEX_COMMAND);
145 } else if (!mutex.tryLock(MUTEX_COMMAND, 'queryCOPSAndSignalQuality')) { 145 } else if (!mutex.tryLock(MUTEX_COMMAND, 'queryCOPSAndSignalQuality')) {
146 resolve(false); 146 resolve(false);
147 return; 147 return;
148 } 148 }
149 149
150 await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND); 150 await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND);
151 await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND); 151 await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND);
152 152
153 mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality'); 153 mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality');
154 resolve(true); 154 resolve(true);
155 }); 155 });
156 } 156 }
157 157
158 function queryIMEI(lockName) { 158 function queryIMEI(lockName) {
159 return new Promise(async (resolve) => { 159 return new Promise(async (resolve) => {
160 const port = global.MODEM_PORT; 160 const port = global.MODEM_PORT;
161 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 161 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
162 162
163 parser.on('data', (data) => { 163 parser.on('data', (data) => {
164 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMEI' }); 164 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMEI' });
165 port.unpipe(parser); 165 port.unpipe(parser);
166 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMEI'); 166 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMEI');
167 modemInfo.imei = data.toString().trim() || null; 167 modemInfo.imei = data.toString().trim() || null;
168 logger.info('IMEI extracted', { imei: modemInfo.imei }); 168 logger.info('IMEI extracted', { imei: modemInfo.imei });
169 resolve(modemInfo.imei); 169 resolve(modemInfo.imei);
170 }); 170 });
171 171
172 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMEI'); 172 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMEI');
173 173
174 port.pipe(parser); 174 port.pipe(parser);
175 await writeToPort('AT+CGSN\r'); 175 await writeToPort('AT+CGSN\r');
176 }); 176 });
177 } 177 }
178 178
179 function queryIMSI(lockName) { 179 function queryIMSI(lockName) {
180 return new Promise(async (resolve) => { 180 return new Promise(async (resolve) => {
181 const port = global.MODEM_PORT; 181 const port = global.MODEM_PORT;
182 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 182 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
183 183
184 parser.on('data', (data) => { 184 parser.on('data', (data) => {
185 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMSI' }); 185 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMSI' });
186 port.unpipe(parser); 186 port.unpipe(parser);
187 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMSI'); 187 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMSI');
188 modemInfo.imsi = data.toString().trim() || null; 188 modemInfo.imsi = data.toString().trim() || null;
189 logger.info('IMSI extracted', { imsi: modemInfo.imsi }); 189 logger.info('IMSI extracted', { imsi: modemInfo.imsi });
190 resolve(modemInfo.imsi); 190 resolve(modemInfo.imsi);
191 }); 191 });
192 192
193 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMSI'); 193 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMSI');
194 194
195 port.pipe(parser); 195 port.pipe(parser);
196 await writeToPort('AT+CIMI\r'); 196 await writeToPort('AT+CIMI\r');
197 }); 197 });
198 } 198 }
199 199
200 async function queryIMEIAndIMSI() { 200 async function queryIMEIAndIMSI() {
201 await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); 201 await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
202 202
203 const imei = await queryIMEI(MUTEX_SUBCOMMAND); 203 const imei = await queryIMEI(MUTEX_SUBCOMMAND);
204 const imsi = await queryIMSI(MUTEX_SUBCOMMAND); 204 const imsi = await queryIMSI(MUTEX_SUBCOMMAND);
205 205
206 await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); 206 await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
207 return { imei, imsi }; 207 return { imei, imsi };
208 } 208 }
209 209
210 function queryManufacturer(lockName) { 210 function queryManufacturer(lockName) {
211 return new Promise(async (resolve) => { 211 return new Promise(async (resolve) => {
212 const port = global.MODEM_PORT; 212 const port = global.MODEM_PORT;
213 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 213 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
214 214
215 parser.on('data', (data) => { 215 parser.on('data', (data) => {
216 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserManufacturer' }); 216 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserManufacturer' });
217 port.unpipe(parser); 217 port.unpipe(parser);
218 mutex.unlock(lockName || MUTEX_COMMAND, 'parserManufacturer'); 218 mutex.unlock(lockName || MUTEX_COMMAND, 'parserManufacturer');
219 modemInfo.manufacturer = data.toString().trim() || null; 219 modemInfo.manufacturer = data.toString().trim() || null;
220 logger.info('Manufacturer extracted', { manufacturer: modemInfo.manufacturer }); 220 logger.info('Manufacturer extracted', { manufacturer: modemInfo.manufacturer });
221 resolve(modemInfo.manufacturer); 221 resolve(modemInfo.manufacturer);
222 }); 222 });
223 223
224 await mutex.lock(lockName || MUTEX_COMMAND, 'queryManufacturer'); 224 await mutex.lock(lockName || MUTEX_COMMAND, 'queryManufacturer');
225 225
226 port.pipe(parser); 226 port.pipe(parser);
227 await writeToPort('AT+CGMI\r'); 227 await writeToPort('AT+CGMI\r');
228 }); 228 });
229 } 229 }
230 230
231 function queryModel(lockName) { 231 function queryModel(lockName) {
232 return new Promise(async (resolve) => { 232 return new Promise(async (resolve) => {
233 const port = global.MODEM_PORT; 233 const port = global.MODEM_PORT;
234 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 234 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
235 235
236 parser.on('data', (data) => { 236 parser.on('data', (data) => {
237 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserModel' }); 237 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserModel' });
238 port.unpipe(parser); 238 port.unpipe(parser);
239 mutex.unlock(lockName || MUTEX_COMMAND, 'parserModel'); 239 mutex.unlock(lockName || MUTEX_COMMAND, 'parserModel');
240 modemInfo.model = data.toString().trim() || null; 240 modemInfo.model = data.toString().trim() || null;
241 logger.info('Model extracted', { model: modemInfo.model }); 241 logger.info('Model extracted', { model: modemInfo.model });
242 resolve(modemInfo.model); 242 resolve(modemInfo.model);
243 }); 243 });
244 244
245 await mutex.lock(lockName || MUTEX_COMMAND, 'queryModel'); 245 await mutex.lock(lockName || MUTEX_COMMAND, 'queryModel');
246 246
247 port.pipe(parser); 247 port.pipe(parser);
248 await writeToPort('AT+CGMM\r'); 248 await writeToPort('AT+CGMM\r');
249 }); 249 });
250 } 250 }
251 251
252 /** 252 /**
253 * Menulis CTRL-Z ke port. 253 * Menulis CTRL-Z ke port.
254 * @static 254 * @static
255 */ 255 */
256 async function sendCtrlZ() { 256 async function sendCtrlZ() {
257 await writeToPort(CTRLZ); 257 await writeToPort(CTRLZ);
258 } 258 }
259 259
260 async function initATCommands() { 260 async function initATCommands() {
261 await mutex.lock(MUTEX_COMMAND, 'INIT MODEM'); 261 await mutex.lock(MUTEX_COMMAND, 'INIT MODEM');
262 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND); 262 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND);
263 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); 263 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
264 await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND); 264 await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND);
265 mutex.unlock(MUTEX_COMMAND, 'INIT MODEM'); 265 mutex.unlock(MUTEX_COMMAND, 'INIT MODEM');
266 } 266 }
267 267
268 /** 268 /**
269 * Menulis awal pesan PDU. 269 * Menulis awal pesan PDU.
270 * 270 *
271 * @param {number} pduLength 271 * @param {number} pduLength
272 */ 272 */
273 function sendCMGSPdu(pduLength) { 273 function sendCMGSPdu(pduLength) {
274 return new Promise((resolve) => { 274 return new Promise((resolve) => {
275 const port = global.MODEM_PORT; 275 const port = global.MODEM_PORT;
276 const parser = new ParserReady({ delimiter: '>' }); 276 const parser = new ParserReady({ delimiter: '>' });
277 277
278 parser.on('data', () => { 278 parser.on('data', () => {
279 logger.verbose('Got ">" message prompt, gonna to write PDU message'); 279 logger.verbose('Got ">" message prompt, gonna to write PDU message');
280 port.unpipe(parser); 280 port.unpipe(parser);
281 mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); 281 mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
282 resolve(true); 282 resolve(true);
283 }); 283 });
284 284
285 mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); 285 mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
286 port.pipe(parser); 286 port.pipe(parser);
287 writeToPort(`AT+CMGS=${pduLength}\r`); 287 writeToPort(`AT+CMGS=${pduLength}\r`);
288 }); 288 });
289 } 289 }
290 290
291 /** 291 /**
292 * Mengirim sms 292 * Mengirim sms
293 * @param {string} destination - nomor tujuan 293 * @param {string} destination - nomor tujuan
294 * @param {string} msg - isi pesan 294 * @param {string} msg - isi pesan
295 * @return {Promise} 295 * @return {Promise}
296 * @static 296 * @static
297 */ 297 */
298 function sendSMS(destination, msg) { 298 function sendSMS(destination, msg) {
299 return new Promise(async (resolve) => { 299 return new Promise(async (resolve) => {
300 async function responseHandler(data) { 300 async function responseHandler(data) {
301 logger.verbose('SMS sent callback called', { data }); 301 logger.verbose('SMS sent callback called', { data });
302 302
303 if (data.indexOf('ERROR') >= 0 || data.indexOf('+CMS ERROR') >= 0 || data.indexOf('+CMGS') >= 0) { 303 if (data.indexOf('ERROR') >= 0 || data.indexOf('+CMS ERROR') >= 0 || data.indexOf('+CMGS') >= 0) {
304 logger.verbose('SMS sent'); 304 logger.info('SMS sent', { data });
305 parsers.setSmsSentCallback(null); 305 parsers.setSmsSentCallback(null);
306 mutex.unlock(MUTEX_COMMAND, 'sendSMS'); 306 mutex.unlock(MUTEX_COMMAND, 'sendSMS');
307 resolve(data.indexOf('ERROR') >= 0 ? null : data.toString().trim()); 307 resolve(data.indexOf('ERROR') >= 0 ? null : data.toString().trim());
308 } 308 }
309 } 309 }
310 310
311 await mutex.lock(MUTEX_COMMAND, 'sendSMS'); 311 await mutex.lock(MUTEX_COMMAND, 'sendSMS');
312 312
313 if (!destination || !destination.trim()) { 313 if (!destination || !destination.trim()) {
314 resolve(false); 314 resolve(false);
315 return; 315 return;
316 } 316 }
317 317
318 if (!msg || !msg.trim()) { 318 if (!msg || !msg.trim()) {
319 resolve(false); 319 resolve(false);
320 return; 320 return;
321 } 321 }
322 322
323 const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+'); 323 const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+');
324 logger.verbose(`Sending sms to ${correctedDestination}`, { msg }); 324 logger.info(`Sending sms to ${correctedDestination}`, { msg, length: msg.length });
325 325
326 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); 326 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
327 327
328 const submit = pdu.Submit(); 328 const submit = pdu.Submit();
329 submit.setAddress(correctedDestination); 329 submit.setAddress(correctedDestination);
330 submit.setData(msg.trim()); 330 submit.setData(msg.trim());
331 submit.getType().setSrr(0); 331 submit.getType().setSrr(0);
332 332
333 await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1); 333 await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1);
334 // await writeToPortAndWaitForOkOrError(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND); 334 // await writeToPortAndWaitForOkOrError(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);
335 335
336 parsers.setSmsSentCallback(responseHandler); 336 parsers.setSmsSentCallback(responseHandler);
337 await writeToPort(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND); 337 await writeToPort(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);
338 }); 338 });
339 } 339 }
340 340
341 /** 341 /**
342 * Ekseksusi kode USSD. 342 * Ekseksusi kode USSD.
343 * <br> 343 * <br>
344 * <br>Pilihan includeCUSD2: 344 * <br>Pilihan includeCUSD2:
345 * <br>-1: sebelum 345 * <br>-1: sebelum
346 * <br>0: tidak (default) 346 * <br>0: tidak (default)
347 * <br>1: sesudah 347 * <br>1: sesudah
348 * <br>2: sebelum dan sesudah 348 * <br>2: sebelum dan sesudah
349 * 349 *
350 * @static 350 * @static
351 * @param {string} code - Kode USSD 351 * @param {string} code - Kode USSD
352 * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2 352 * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2
353 * @return {Promise} 353 * @return {Promise}
354 */ 354 */
355 function executeUSSD(code, _includeCUSD2, _sessionId) { 355 function executeUSSD(code, _includeCUSD2, _sessionId) {
356 return new Promise(async (resolve) => { 356 return new Promise(async (resolve) => {
357 const includeCUSD2 = _includeCUSD2 || 0; 357 const includeCUSD2 = _includeCUSD2 || 0;
358 const sessionId = _sessionId || uuidv1(); 358 const sessionId = _sessionId || uuidv1();
359 359
360 async function responseHandler(data) { 360 async function responseHandler(data) {
361 logger.verbose('Processing USSD response', { data }); 361 logger.verbose('Processing USSD response', { data });
362 parsers.setUssdCallback(null); 362 parsers.setUssdCallback(null);
363 363
364 if (includeCUSD2 === 1 || includeCUSD2 === 2) { 364 if (includeCUSD2 === 1 || includeCUSD2 === 2) {
365 await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); 365 await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
366 } 366 }
367 367
368 mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); 368 mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
369 resolve(data); 369 resolve(data);
370 } 370 }
371 371
372 mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); 372 mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
373 parsers.setUssdCallback(responseHandler); 373 parsers.setUssdCallback(responseHandler);
374 374
375 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND); 375 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND);
376 376
377 if (includeCUSD2 === -1 || includeCUSD2 === 2) { 377 if (includeCUSD2 === -1 || includeCUSD2 === 2) {
378 await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); 378 await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
379 } 379 }
380 380
381 await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND); 381 await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND);
382 }); 382 });
383 } 383 }
384 384
385 exports.MUTEX_COMMAND = MUTEX_COMMAND; 385 exports.MUTEX_COMMAND = MUTEX_COMMAND;
386 exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND; 386 exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND;
387 exports.CTRLZ = CTRLZ; 387 exports.CTRLZ = CTRLZ;
388 388
389 /** 389 /**
390 * Modem info. 390 * Modem info.
391 * @type {object} 391 * @type {object}
392 */ 392 */
393 exports.modemInfo = modemInfo; 393 exports.modemInfo = modemInfo;
394 // exports.setPort = setPort; 394 // exports.setPort = setPort;
395 395
396 exports.writeToPort = writeToPort; 396 exports.writeToPort = writeToPort;
397 exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline; 397 exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline;
398 exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError; 398 exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError;
399 exports.sleep = sleep; 399 exports.sleep = sleep;
400 400
401 exports.querySignalQuality = querySignalQuality; 401 exports.querySignalQuality = querySignalQuality;
402 exports.queryCOPS = queryCOPS; 402 exports.queryCOPS = queryCOPS;
403 exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality; 403 exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality;
404 404
405 exports.queryIMEI = queryIMEI; 405 exports.queryIMEI = queryIMEI;
406 exports.queryIMSI = queryIMSI; 406 exports.queryIMSI = queryIMSI;
407 exports.queryIMEIAndIMSI = queryIMEIAndIMSI; 407 exports.queryIMEIAndIMSI = queryIMEIAndIMSI;
408 408
409 exports.queryManufacturer = queryManufacturer; 409 exports.queryManufacturer = queryManufacturer;
410 exports.queryModel = queryModel; 410 exports.queryModel = queryModel;
411 411
412 exports.sendCtrlZ = sendCtrlZ; 412 exports.sendCtrlZ = sendCtrlZ;
413 exports.initATCommands = initATCommands; 413 exports.initATCommands = initATCommands;
414 exports.sendSMS = sendSMS; 414 exports.sendSMS = sendSMS;
415 exports.executeUSSD = executeUSSD; 415 exports.executeUSSD = executeUSSD;
416 416
lib/modem-commands/ussd-codes.js
File was created 1 exports.TSEL_SMS_QUOTA_CHECK = '*888*2*2*4#';
2
1 /** 1 /**
2 * Modul modem. 2 * Modul modem.
3 * 3 *
4 * Modul ini sedang proses pengalihan ke module:bootstrap 4 * Modul ini sedang proses pengalihan ke module:bootstrap
5 * 5 *
6 * @module 6 * @module
7 * @deprecated going to move to module:bootstrap 7 * @deprecated going to move to module:bootstrap
8 * @see module:bootstrap
8 */ 9 */
9 10
10 const DEFAULT_SLEEP_AFTER_SEND_SMS_MS = 2000; 11 const DEFAULT_SLEEP_AFTER_SEND_SMS_MS = 2000;
11 const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 30000; 12 const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 30000;
12 const MAX_LAST_DATA_AGE_MS = 3 * 60 * 1000; 13 const MAX_LAST_DATA_AGE_MS = 3 * 60 * 1000;
13 const REGEX_WAIT_FOR_OK_OR_ERROR = /\n(?:OK|ERROR)\r/; 14 const REGEX_WAIT_FOR_OK_OR_ERROR = /\n(?:OK|ERROR)\r/;
14 // const REGEX_WAIT_FOR_OK_OR_ERROR_USSD = /\n(?:OK|ERROR)\r/; 15 // const REGEX_WAIT_FOR_OK_OR_ERROR_USSD = /\n(?:OK|ERROR)\r/;
15 16
17 const path = require('path');
16 const moment = require('moment'); 18 const moment = require('moment');
17 const SerialPort = require('serialport'); 19 const SerialPort = require('serialport');
18 const ParserReadline = require('@serialport/parser-readline'); 20 const ParserReadline = require('@serialport/parser-readline');
19 // const ParserDelimiter = require('@serialport/parser-delimiter'); 21 // const ParserDelimiter = require('@serialport/parser-delimiter');
20 22
21 const ParserRegex = require('@serialport/parser-regex'); 23 const ParserRegex = require('@serialport/parser-regex');
22 24
23 const config = require('komodo-sdk/config'); 25 const config = require('komodo-sdk/config');
24 const logger = require('komodo-sdk/logger'); 26 const logger = require('komodo-sdk/logger');
25 27
28 // const stack = new Error().stack;
29 logger.warn(`'${path.basename(__filename, '.js')}' is DEPRECATED, please use 'modem-commands'!`);
30 // eslint-disable-next-line no-console
31 console.trace(`'${path.basename(__filename, '.js')}' is DEPRECATED, please use 'modem-commands'!`);
32
26 const mutex = require('./mutex'); 33 const mutex = require('./mutex');
27 const common = require('./common'); 34 const common = require('./common');
28 const sms = require('./sms'); 35 const sms = require('./sms');
29 const dbCops = require('./db-cops'); 36 const dbCops = require('./db-cops');
30 const reportSender = require('./report-sender'); 37 const reportSender = require('./report-sender');
31 const registerModem = require('./register-modem'); 38 const registerModem = require('./register-modem');
32 39
33 const modemInfo = { 40 const modemInfo = require('./modem-info');
34 device: config.modem.device,
35 manufacturer: null,
36 model: null,
37 imei: null,
38 imsi: null,
39 msisdn: null,
40 cops: null,
41 networkId: null,
42 networkName: null,
43 signalStrength: null,
44 signalStrengthTs: null,
45 signalStrengthTsReadable: null,
46 };
47 41
48 let lastTs = new Date(); 42 let lastTs = new Date();
49 43
50 let port; 44 let port;
51 45
52 const parserReadLine = new ParserReadline(); 46 const parserReadLine = new ParserReadline();
53 47
54 const parserWaitForOK = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 48 const parserWaitForOK = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
55 parserWaitForOK.on('data', () => { 49 parserWaitForOK.on('data', () => {
56 mutex.releaseLockWaitForCommand(); 50 mutex.releaseLockWaitForCommand();
57 }); 51 });
58 52
59 function writeToPort(data) { 53 function writeToPort(data) {
60 return new Promise((resolve) => { 54 return new Promise((resolve) => {
61 port.write(data, (err, bytesWritten) => { 55 port.write(data, (err, bytesWritten) => {
62 if (err) logger.warn(`ERROR: ${err.toString()}`); 56 if (err) logger.warn(`ERROR: ${err.toString()}`);
63 logger.verbose(`* OUT: ${data}`); 57 logger.verbose(`* OUT: ${data}`);
64 resolve(bytesWritten); 58 resolve(bytesWritten);
65 }); 59 });
66 }); 60 });
67 } 61 }
68 62
69 async function readSMS(slot) { 63 async function readSMS(slot) {
70 const parserCMGR = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 64 const parserCMGR = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
71 parserCMGR.on('data', (data) => { 65 parserCMGR.on('data', (data) => {
72 if (data) { 66 if (data) {
73 try { 67 try {
74 reportSender.incomingSMS(sms.extract(data.toString().trim()), modemInfo); 68 reportSender.incomingSMS(sms.extract(data.toString().trim()), modemInfo);
75 } catch (e) { 69 } catch (e) {
76 logger.warn(`Exception on reporting new message. ${e.toString()}`, { smsObj: e.smsObj, dataFromModem: data }); 70 logger.warn(`Exception on reporting new message. ${e.toString()}`, { smsObj: e.smsObj, dataFromModem: data });
77 71
78 process.exit(0); 72 process.exit(0);
79 } 73 }
80 } 74 }
81 port.unpipe(parserCMGR); 75 port.unpipe(parserCMGR);
82 mutex.releaseLockWaitForCommand(); 76 mutex.releaseLockWaitForCommand();
83 }); 77 });
84 78
85 // const parserCMGD = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK }); 79 // const parserCMGD = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK });
86 const parserCMGD = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 80 const parserCMGD = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
87 parserCMGD.on('data', () => { 81 parserCMGD.on('data', () => {
88 port.unpipe(parserCMGD); 82 port.unpipe(parserCMGD);
89 mutex.releaseLockWaitForCommand(); 83 mutex.releaseLockWaitForCommand();
90 }); 84 });
91 85
92 logger.info(`Reading SMS on slot ${slot}`); 86 logger.info(`Reading SMS on slot ${slot}`);
93 await mutex.setLockWaitForCommand(); 87 await mutex.setLockWaitForCommand();
94 port.pipe(parserCMGR); 88 port.pipe(parserCMGR);
95 await writeToPort(`AT+CMGR=${slot}\r`); 89 await writeToPort(`AT+CMGR=${slot}\r`);
96 logger.info(`Finished reading SMS on slot ${slot}`); 90 logger.info(`Finished reading SMS on slot ${slot}`);
97 91
98 logger.info(`Deleting message on slot ${slot}`); 92 logger.info(`Deleting message on slot ${slot}`);
99 await mutex.setLockWaitForCommand(); 93 await mutex.setLockWaitForCommand();
100 port.pipe(parserCMGD); 94 port.pipe(parserCMGD);
101 await writeToPort(`AT+CMGD=${slot}\r`); 95 await writeToPort(`AT+CMGD=${slot}\r`);
102 logger.info('Message processing has completed'); 96 logger.info('Message processing has completed');
103 } 97 }
104 98
105 function onIncomingSMS(data) { 99 function onIncomingSMS(data) {
106 const value = common.extractValueFromReadLineData(data); 100 const value = common.extractValueFromReadLineData(data);
107 if (!value) return; 101 if (!value) return;
108 102
109 const chunks = value.split(','); 103 const chunks = value.split(',');
110 if (!chunks && !chunks[1]) return; 104 if (!chunks && !chunks[1]) return;
111 105
112 const slot = chunks[1]; 106 const slot = chunks[1];
113 107
114 logger.info(`Incoming SMS on slot ${slot}`); 108 logger.info(`Incoming SMS on slot ${slot}`);
115 readSMS(slot); 109 readSMS(slot);
116 } 110 }
117 111
118 function onCOPS(data) { 112 function onCOPS(data) {
119 modemInfo.cops = common.extractValueFromReadLineData(data).trim(); 113 modemInfo.cops = common.extractValueFromReadLineData(data).trim();
120 logger.info(`Connected Network: ${modemInfo.cops}`); 114 logger.info(`Connected Network: ${modemInfo.cops}`);
121 115
122 if (!modemInfo.cops) return; 116 if (!modemInfo.cops) return;
123 117
124 [, , modemInfo.networkId] = modemInfo.cops.split(','); 118 [, , modemInfo.networkId] = modemInfo.cops.split(',');
125 119
126 if (modemInfo.networkId) { 120 if (modemInfo.networkId) {
127 modemInfo.networkName = dbCops[modemInfo.networkId] || modemInfo.networkId; 121 modemInfo.networkName = dbCops[modemInfo.networkId] || modemInfo.networkId;
128 } 122 }
129 } 123 }
130 124
131 parserReadLine.on('data', (data) => { 125 parserReadLine.on('data', (data) => {
132 logger.verbose(`* IN: ${data}`); 126 logger.verbose(`* IN: ${data}`);
133 if (data) { 127 if (data) {
134 lastTs = new Date(); 128 lastTs = new Date();
135 if (data.indexOf('+CSQ: ') === 0) { 129 if (data.indexOf('+CSQ: ') === 0) {
136 const signalStrength = common.extractValueFromReadLineData(data).trim(); 130 const signalStrength = common.extractValueFromReadLineData(data).trim();
137 if (signalStrength) { 131 if (signalStrength) {
138 modemInfo.signalStrength = signalStrength; 132 modemInfo.signalStrength = signalStrength;
139 modemInfo.signalStrengthTs = new Date(); 133 modemInfo.signalStrengthTs = new Date();
140 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss'); 134 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss');
141 logger.info(`Signal strength: ${modemInfo.signalStrength}`); 135 logger.info(`Signal strength: ${modemInfo.signalStrength}`);
142 registerModem(modemInfo); 136 registerModem(modemInfo);
143 } 137 }
144 } else if (data.indexOf('+CMTI: ') === 0) { 138 } else if (data.indexOf('+CMTI: ') === 0) {
145 onIncomingSMS(data); 139 onIncomingSMS(data);
146 } else if (data.indexOf('+COPS: ') === 0) { 140 } else if (data.indexOf('+COPS: ') === 0) {
147 onCOPS(data); 141 onCOPS(data);
148 } 142 }
149 } 143 }
150 }); 144 });
151 145
152 async function simpleSubCommand(cmd, callback) { 146 async function simpleSubCommand(cmd, callback) {
153 const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 147 const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
154 parser.on('data', (data) => { 148 parser.on('data', (data) => {
155 port.unpipe(parser); 149 port.unpipe(parser);
156 mutex.releaseLockWaitForSubCommand(); 150 mutex.releaseLockWaitForSubCommand();
157 151
158 if (data) { 152 if (data) {
159 if (callback) callback(null, data.toString().trim()); 153 if (callback) callback(null, data.toString().trim());
160 } 154 }
161 }); 155 });
162 156
163 return new Promise(async (resolve) => { 157 return new Promise(async (resolve) => {
164 await mutex.setLockWaitForSubCommand(); 158 await mutex.setLockWaitForSubCommand();
165 port.pipe(parser); 159 port.pipe(parser);
166 writeToPort(cmd); 160 writeToPort(cmd);
167 161
168 await mutex.setLockWaitForSubCommand(); 162 await mutex.setLockWaitForSubCommand();
169 mutex.releaseLockWaitForSubCommand(); 163 mutex.releaseLockWaitForSubCommand();
170 164
171 resolve(); 165 resolve();
172 }); 166 });
173 } 167 }
174 168
175 function readManufacturer() { 169 function readManufacturer() {
176 return new Promise((resolve) => { 170 return new Promise((resolve) => {
177 simpleSubCommand('AT+CGMI\r', (err, result) => { 171 simpleSubCommand('AT+CGMI\r', (err, result) => {
178 modemInfo.manufacturer = result; 172 modemInfo.manufacturer = result;
179 logger.info(`Manufacturer: ${result}`); 173 logger.info(`Manufacturer: ${result}`);
180 resolve(result); 174 resolve(result);
181 }); 175 });
182 }); 176 });
183 } 177 }
184 178
185 function readModel() { 179 function readModel() {
186 return new Promise((resolve) => { 180 return new Promise((resolve) => {
187 simpleSubCommand('AT+CGMM\r', (err, result) => { 181 simpleSubCommand('AT+CGMM\r', (err, result) => {
188 modemInfo.model = result; 182 modemInfo.model = result;
189 logger.info(`Model: ${result}`); 183 logger.info(`Model: ${result}`);
190 resolve(result); 184 resolve(result);
191 }); 185 });
192 }); 186 });
193 } 187 }
194 188
195 function readIMEI() { 189 function readIMEI() {
196 return new Promise((resolve) => { 190 return new Promise((resolve) => {
197 simpleSubCommand('AT+CGSN\r', (err, result) => { 191 simpleSubCommand('AT+CGSN\r', (err, result) => {
198 modemInfo.imei = result; 192 modemInfo.imei = result;
199 logger.info(`IMEI: ${result}`); 193 logger.info(`IMEI: ${result}`);
200 resolve(result); 194 resolve(result);
201 }); 195 });
202 }); 196 });
203 } 197 }
204 198
205 function readIMSI() { 199 function readIMSI() {
206 return new Promise((resolve) => { 200 return new Promise((resolve) => {
207 simpleSubCommand('AT+CIMI\r', (err, result) => { 201 simpleSubCommand('AT+CIMI\r', (err, result) => {
208 modemInfo.imsi = result; 202 modemInfo.imsi = result;
209 logger.info(`IMSI: ${result}`); 203 logger.info(`IMSI: ${result}`);
210 204
211 if (result) { 205 if (result) {
212 /* 206 /*
213 modemInfo.msisdn = msisdn[result]; 207 modemInfo.msisdn = msisdn[result];
214 if (modemInfo.msisdn) { 208 if (modemInfo.msisdn) {
215 logger.info(`MSISDN: ${modemInfo.msisdn}`); 209 logger.info(`MSISDN: ${modemInfo.msisdn}`);
216 registerModem(modemInfo); 210 registerModem(modemInfo);
217 } 211 }
218 */ 212 */
219 } else { 213 } else {
220 logger.warn(`IMSI not detected. Please insert a sim card to your modem. Terminating ${config.modem.device}.`); 214 logger.warn(`IMSI not detected. Please insert a sim card to your modem. Terminating ${config.modem.device}.`);
221 process.exit(2); 215 process.exit(2);
222 } 216 }
223 resolve(result); 217 resolve(result);
224 }); 218 });
225 }); 219 });
226 } 220 }
227 221
228 function readCOPS() { 222 function readCOPS() {
229 return new Promise((resolve) => { 223 return new Promise((resolve) => {
230 simpleSubCommand('AT+COPS?\r', (err, result) => { 224 simpleSubCommand('AT+COPS?\r', (err, result) => {
231 resolve(result); 225 resolve(result);
232 }); 226 });
233 }); 227 });
234 } 228 }
235 229
236 function deleteInbox() { 230 function deleteInbox() {
237 return new Promise((resolve) => { 231 return new Promise((resolve) => {
238 simpleSubCommand('AT+CMGD=0,4\r', (err, result) => { 232 simpleSubCommand('AT+CMGD=0,4\r', (err, result) => {
239 resolve(result); 233 resolve(result);
240 }); 234 });
241 }); 235 });
242 } 236 }
243 237
244 async function querySignalStrength() { 238 async function querySignalStrength() {
245 const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 239 const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
246 parser.on('data', () => { 240 parser.on('data', () => {
247 port.unpipe(parser); 241 port.unpipe(parser);
248 mutex.releaseLockWaitForCommand(); 242 mutex.releaseLockWaitForCommand();
249 }); 243 });
250 244
251 if (mutex.tryLockWaitForCommand()) { 245 if (mutex.tryLockWaitForCommand()) {
252 port.pipe(parser); 246 port.pipe(parser);
253 await writeToPort('AT+CSQ\r'); 247 await writeToPort('AT+CSQ\r');
254 } 248 }
255 } 249 }
256 250
257 function registerModemToCenterPeriodically() { 251 function registerModemToCenterPeriodically() {
258 registerModem(modemInfo); 252 registerModem(modemInfo);
259 253
260 setInterval(() => { 254 setInterval(() => {
261 registerModem(modemInfo); 255 registerModem(modemInfo);
262 }, 60 * 1000); 256 }, 60 * 1000);
263 } 257 }
264 258
265 async function registerSignalStrengthBackgroundQuery() { 259 async function registerSignalStrengthBackgroundQuery() {
266 logger.info('Registering background signal strength query'); 260 logger.info('Registering background signal strength query');
267 261
268 querySignalStrength(); 262 querySignalStrength();
269 263
270 setInterval(() => { 264 setInterval(() => {
271 querySignalStrength(); 265 querySignalStrength();
272 }, config.interval_beetwen_signal_strength_ms || INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS); 266 }, config.interval_beetwen_signal_strength_ms || INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS);
273 } 267 }
274 268
275 async function sendSMS(destination, msg) { 269 async function sendSMS(destination, msg) {
276 if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return; 270 if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return;
277 271
278 // const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 272 // const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
279 const parser = new ParserReadline({ delimiter: '\r\n' }); 273 const parser = new ParserReadline({ delimiter: '\r\n' });
280 parser.on('data', () => { 274 parser.on('data', () => {
281 port.unpipe(parser); 275 port.unpipe(parser);
282 mutex.releaseLockWaitForSubCommand(); 276 mutex.releaseLockWaitForSubCommand();
283 }); 277 });
284 278
285 logger.verbose('Waiting for command lock to send message'); 279 logger.verbose('Waiting for command lock to send message');
286 await mutex.setLockWaitForCommand(); 280 await mutex.setLockWaitForCommand();
287 281
288 logger.info('Preparing to send message', { destination, msg }); 282 logger.info('Preparing to send message', { destination, msg });
289 283
290 const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+'); 284 const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+');
291 285
292 logger.verbose('Waiting for lock before set SMS to text mode'); 286 logger.verbose('Waiting for lock before set SMS to text mode');
293 await mutex.setLockWaitForSubCommand(); 287 await mutex.setLockWaitForSubCommand();
294 port.pipe(parser); 288 port.pipe(parser);
295 await writeToPort('AT+CMGF=1\r'); 289 await writeToPort('AT+CMGF=1\r');
296 290
297 logger.verbose('Waiting for lock before writing message'); 291 logger.verbose('Waiting for lock before writing message');
298 await mutex.setLockWaitForSubCommand(); 292 await mutex.setLockWaitForSubCommand();
299 port.pipe(parser); 293 port.pipe(parser);
300 await writeToPort(`AT+CMGS="${correctedDestination}"\r`); 294 await writeToPort(`AT+CMGS="${correctedDestination}"\r`);
301 await writeToPort(msg); 295 await writeToPort(msg);
302 await writeToPort(Buffer.from([0x1A])); 296 await writeToPort(Buffer.from([0x1A]));
303 297
304 await mutex.setLockWaitForSubCommand(); 298 await mutex.setLockWaitForSubCommand();
305 mutex.releaseLockWaitForSubCommand(); 299 mutex.releaseLockWaitForSubCommand();
306 300
307 logger.info('Message has been sent'); 301 logger.info('Message has been sent');
308 302
309 setTimeout(() => { 303 setTimeout(() => {
310 logger.verbose('Releasing command lock'); 304 logger.verbose('Releasing command lock');
311 mutex.releaseLockWaitForCommand(); 305 mutex.releaseLockWaitForCommand();
312 }, config.sleep_after_send_sms_ms || DEFAULT_SLEEP_AFTER_SEND_SMS_MS); 306 }, config.sleep_after_send_sms_ms || DEFAULT_SLEEP_AFTER_SEND_SMS_MS);
313 } 307 }
314 308
315 /** 309 /**
316 * Ekseksusi kode USSD. 310 * Ekseksusi kode USSD.
317 * 311 *
318 * Pilihan includeCUSD2: 312 * Pilihan includeCUSD2:
319 * -1: sebelum 313 * -1: sebelum
320 * 0: tidak (default) 314 * 0: tidak (default)
321 * 1: sesudah 315 * 1: sesudah
322 * 2: sebelum dan sesudah 316 * 2: sebelum dan sesudah
323 * 317 *
324 * @param {string} code - Kode USSD 318 * @param {string} code - Kode USSD
325 * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2 319 * @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2
326 */ 320 */
327 function executeUSSD(code, includeCUSD2) { 321 function executeUSSD(code, includeCUSD2) {
328 return new Promise(async (resolve) => { 322 return new Promise(async (resolve) => {
329 const parserMain = new ParserReadline({ delimiter: '\r\n' }); 323 const parserMain = new ParserReadline({ delimiter: '\r\n' });
330 // const parserMain = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 324 // const parserMain = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
331 parserMain.on('data', (data) => { 325 parserMain.on('data', (data) => {
332 if (!data || !data.toString().trim()) return; 326 if (!data || !data.toString().trim()) return;
333 327
334 if (data.toString().trim() === 'OK') return; 328 if (data.toString().trim() === 'OK') return;
335 329
336 port.unpipe(parserMain); 330 port.unpipe(parserMain);
337 mutex.releaseLockWaitForSubCommand(); 331 mutex.releaseLockWaitForSubCommand();
338 resolve(data); 332 resolve(data);
339 }); 333 });
340 334
341 const parserCUSD2 = new ParserReadline({ delimiter: '\r\n' }); 335 const parserCUSD2 = new ParserReadline({ delimiter: '\r\n' });
342 // const parserCUSD2 = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); 336 // const parserCUSD2 = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
343 parserCUSD2.on('data', () => { 337 parserCUSD2.on('data', () => {
344 port.unpipe(parserCUSD2); 338 port.unpipe(parserCUSD2);
345 mutex.releaseLockWaitForSubCommand(); 339 mutex.releaseLockWaitForSubCommand();
346 }); 340 });
347 341
348 logger.verbose('Waiting for command lock to execute USSD'); 342 logger.verbose('Waiting for command lock to execute USSD');
349 await mutex.setLockWaitForCommand(); 343 await mutex.setLockWaitForCommand();
350 344
351 if (includeCUSD2 === -1 || includeCUSD2 === 2) { 345 if (includeCUSD2 === -1 || includeCUSD2 === 2) {
352 await mutex.setLockWaitForSubCommand(); 346 await mutex.setLockWaitForSubCommand();
353 logger.info('Terminating existing USSD session'); 347 logger.info('Terminating existing USSD session');
354 port.pipe(parserCUSD2); 348 port.pipe(parserCUSD2);
355 await writeToPort('AT+CUSD=2\r'); 349 await writeToPort('AT+CUSD=2\r');
356 } 350 }
357 351
358 await mutex.setLockWaitForSubCommand(); 352 await mutex.setLockWaitForSubCommand();
359 logger.info(`Executing USSD code "${code}"`); 353 logger.info(`Executing USSD code "${code}"`);
360 port.pipe(parserMain); 354 port.pipe(parserMain);
361 await writeToPort(`AT+CUSD=1,"${code}",15\r`); 355 await writeToPort(`AT+CUSD=1,"${code}",15\r`);
362 356
363 if (includeCUSD2 === 1 || includeCUSD2 === 2) { 357 if (includeCUSD2 === 1 || includeCUSD2 === 2) {
364 await mutex.setLockWaitForSubCommand(); 358 await mutex.setLockWaitForSubCommand();
365 logger.info('Terminating existing USSD session'); 359 logger.info('Terminating existing USSD session');
366 port.pipe(parserCUSD2); 360 port.pipe(parserCUSD2);
367 await writeToPort('AT+CUSD=2\r'); 361 await writeToPort('AT+CUSD=2\r');
368 } 362 }
369 363
370 await mutex.setLockWaitForSubCommand(); 364 await mutex.setLockWaitForSubCommand();
371 mutex.releaseLockWaitForSubCommand(); 365 mutex.releaseLockWaitForSubCommand();
372 366
373 mutex.releaseLockWaitForCommand(); 367 mutex.releaseLockWaitForCommand();
374 }); 368 });
375 } 369 }
376 370
377 function init() { 371 function init() {
378 port = new SerialPort(config.modem.device, { baudRate: 115200 }, (err) => { 372 port = new SerialPort(config.modem.device, { baudRate: 115200 }, (err) => {
379 if (err) { 373 if (err) {
380 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`); 374 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`);
381 process.exit(1); 375 process.exit(1);
382 } 376 }
383 377
384 registerModem(modemInfo); 378 registerModem(modemInfo);
385 }); 379 });
386 port.pipe(parserReadLine); 380 port.pipe(parserReadLine);
387 381
388 setInterval(() => { 382 setInterval(() => {
389 if ((new Date() - lastTs) > MAX_LAST_DATA_AGE_MS) { 383 if ((new Date() - lastTs) > MAX_LAST_DATA_AGE_MS) {
390 logger.warn(`No data for more than ${MAX_LAST_DATA_AGE_MS} ms. Modem might be unresponsive. Terminating modem ${config.modem.device}.`); 384 logger.warn(`No data for more than ${MAX_LAST_DATA_AGE_MS} ms. Modem might be unresponsive. Terminating modem ${config.modem.device}.`);
391 process.exit(0); 385 process.exit(0);
392 } 386 }
393 }, 30 * 1000); 387 }, 30 * 1000);
394 388
395 port.on('open', async () => { 389 port.on('open', async () => {
396 await mutex.setLockWaitForCommand(); 390 await mutex.setLockWaitForCommand();
397 391
398 logger.info('Modem opened'); 392 logger.info('Modem opened');
399 await writeToPort('\r'); 393 await writeToPort('\r');
400 await simpleSubCommand('AT\r'); 394 await simpleSubCommand('AT\r');
401 395
402 logger.info('Initializing modem to factory set'); 396 logger.info('Initializing modem to factory set');
403 await simpleSubCommand('AT&F\r'); 397 await simpleSubCommand('AT&F\r');
404 398
405 logger.info('Disabling echo'); 399 logger.info('Disabling echo');
406 await simpleSubCommand('ATE0\r'); 400 await simpleSubCommand('ATE0\r');
407 401
408 logger.info('Set to text mode'); 402 logger.info('Set to text mode');
409 await simpleSubCommand('AT+CMGF=1\r'); 403 await simpleSubCommand('AT+CMGF=1\r');
410 404
411 logger.info('Set message indication'); 405 logger.info('Set message indication');
412 await simpleSubCommand('AT+CNMI=1,1,2,1,1\r'); 406 await simpleSubCommand('AT+CNMI=1,1,2,1,1\r');
413 407
414 await readCOPS(); 408 await readCOPS();
415 409
416 await readManufacturer(); 410 await readManufacturer();
417 await readModel(); 411 await readModel();
418 await readIMEI(); 412 await readIMEI();
419 await readIMSI(); 413 await readIMSI();
420 414
421 if (!config.disable_delete_inbox_on_startup) { 415 if (!config.disable_delete_inbox_on_startup) {
422 logger.info('Deleting existing messages'); 416 logger.info('Deleting existing messages');
423 await deleteInbox(); 417 await deleteInbox();
424 } 418 }
425 419
426 mutex.releaseLockWaitForCommand(); 420 mutex.releaseLockWaitForCommand();
427 logger.verbose('Init completed'); 421 logger.verbose('Init completed');
428 422
429 registerModemToCenterPeriodically(); 423 registerModemToCenterPeriodically();
430 registerSignalStrengthBackgroundQuery(); 424 registerSignalStrengthBackgroundQuery();
431 }); 425 });
432 } 426 }
lib/register-modem.js
1 'use strict'; 1 'use strict';
2 2
3 const path = require('path'); 3 const path = require('path');
4 const request = require('request'); 4 const request = require('request');
5 5
6 const locks = require('locks'); 6 const locks = require('locks');
7 7
8 const config = require('komodo-sdk/config'); 8 const config = require('komodo-sdk/config');
9 const logger = require('komodo-sdk/logger'); 9 const logger = require('komodo-sdk/logger');
10 10
11 const modemInfo = require('./modem-info');
12
11 const mutex = locks.createMutex(); 13 const mutex = locks.createMutex();
12 14
13 function reportUrl() { 15 function reportUrl() {
14 if (config.report_url.register_modem) { 16 if (config.report_url.register_modem) {
15 return config.report_url.register_modem; 17 return config.report_url.register_modem;
16 } 18 }
17 19
18 const baseUrl = path.dirname(config.report_url.incoming_sms); 20 const baseUrl = path.dirname(config.report_url.incoming_sms);
19 return `${baseUrl}/modems/set`; 21 return `${baseUrl}/modems/set`;
20 } 22 }
21 23
22 function sender(modemInfo) { 24 function sender() {
23 if (mutex.tryLock()) { 25 if (mutex.tryLock()) {
24 const requestOptions = { 26 const requestOptions = {
25 url: reportUrl(), 27 url: reportUrl(),
26 qs: { 28 qs: {
27 modem: config.name, 29 modem: config.name,
28 modem_device: config.modem.device, 30 modem_device: config.modem.device,
29 modem_imsi: modemInfo.imsi, 31 modem_imsi: modemInfo.imsi,
30 // modem_msisdn: modemInfo.msisdn, 32 // modem_msisdn: modemInfo.msisdn,
31 modem_network_id: modemInfo.networkId, 33 modem_network_id: modemInfo.networkId,
32 modem_network_name: modemInfo.networkName, 34 modem_network_name: modemInfo.networkName,
33 modem_signal_strength: modemInfo.signalStrength, 35 modem_signal_strength: modemInfo.signalStrength,
34 uptime: Math.floor(process.uptime()), 36 uptime: Math.floor(process.uptime()),
35 startTime: modemInfo.startTime, 37 startTime: modemInfo.startTime,
36 report_port: config.http_command_server.listen_port, 38 report_port: config.http_command_server.listen_port,
37 report_apikey: config.http_command_server.apikey, 39 report_apikey: config.http_command_server.apikey,
38 report_path_sms: '/sms', 40 report_path_sms: '/sms',
39 lastReadTs: modemInfo.lastReadTs, 41 lastReadTs: modemInfo.lastReadTs,
40 lastWriteTs: modemInfo.lastWriteTs, 42 lastWriteTs: modemInfo.lastWriteTs,
41 }, 43 },
42 }; 44 };
43 45
44 logger.info('Sending modem registration to center'); 46 logger.info('Sending modem registration to center');
45 request(requestOptions, (err, res) => { 47 request(requestOptions, (err, res) => {
46 mutex.unlock(); 48 mutex.unlock();
47 49
48 if (err) { 50 if (err) {
49 logger.warn(`Error registering modem. ${err.toString()}`); 51 logger.warn(`Error registering modem. ${err.toString()}`);
50 } else if (res.statusCode !== 200) { 52 } else if (res.statusCode !== 200) {
51 logger.warn(`SMS center respond with HTTP status code ${res.statusCode}.`); 53 logger.warn(`SMS center respond with HTTP status code ${res.statusCode}.`);
52 } 54 }
53 }); 55 });
54 } 56 }
55 } 57 }
56 58
57 module.exports = sender; 59 module.exports = sender;
58 60
lib/report-sender2.js
File was created 1 const request = require('request');
2
3 const config = require('komodo-sdk/config');
4 const logger = require('komodo-sdk/logger');
5 const modemInfo = require('./modem-info');
6
7 function incomingSMS(ts, from, msg) {
8 if (!ts || !from || !msg
9 || !config || !config.report_url || !config.report_url.incoming_sms) return;
10
11 const requestOptions = {
12 url: config.report_url.incoming_sms,
13 qs: {
14 ts,
15 // status: message.metadata.status,
16 number: from,
17 msg,
18 modem: config.name,
19 modem_imsi: modemInfo.imsi,
20 modem_msisdn: modemInfo.msisdn,
21 modem_device: config.modem.device,
22 modem_network_id: modemInfo.networkId,
23 modem_network_name: modemInfo.networkName,
24 modem_signal_strength: modemInfo.signalStrength,
25 uptime: Math.floor(process.uptime()),
26 report_port: config.http_command_server.listen_port,
27 report_apikey: config.http_command_server.apikey,
28 report_path_sms: '/sms',
29 },
30 };
31
32 logger.info('Sending report via HTTP', requestOptions);
33 request(requestOptions, (err, res, body) => {
34 if (err) {
35 logger.warn(`Error sending report via HTTP. ${err.toString()}`);
36 return;
37 }
38
39 if (res.statusCode !== 200) {
40 logger.warn(`Error sending report via HTTP. Server respond with HTTP status code ${res.statusCode}`, { http_status_code: res.statusCode, body });
41 }
42 });
43 }
44
45 exports.incomingSMS = incomingSMS;
46
lib/serialport-parsers.js
1 const PARSER_READLINE_DELIMITER = '\r\n'; 1 const PARSER_READLINE_DELIMITER = '\r\n';
2 const PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = /\n(?:OK|ERROR)\r\n/; 2 const PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = /\n(?:OK|ERROR)\r\n/;
3 3
4 const moment = require('moment'); 4 const moment = require('moment');
5 const nodePdu = require('node-pdu'); 5 const nodePdu = require('node-pdu');
6 // const pdu = require('pdu'); 6 // const pdu = require('pdu');
7 const ParserReadline = require('@serialport/parser-readline'); 7 const ParserReadline = require('@serialport/parser-readline');
8 const ParserRegex = require('@serialport/parser-regex'); 8 const ParserRegex = require('@serialport/parser-regex');
9 9
10 const logger = require('komodo-sdk/logger'); 10 const logger = require('komodo-sdk/logger');
11 11
12 const dbCops = require('./db-cops'); 12 const dbCops = require('./db-cops');
13 const modemInfo = require('./modem-info'); 13 const modemInfo = require('./modem-info');
14 const reportSender = require('./report-sender2');
14 15
15 let port; 16 let port;
16 17
17 exports.setPort = function setPort(val) { 18 exports.setPort = function setPort(val) {
18 logger.info('SERIALPORT-PARSERS: setting port'); 19 logger.info('SERIALPORT-PARSERS: setting port');
19 port = val; 20 port = val;
20 }; 21 };
21 22
22 exports.getPort = function getPort() { 23 exports.getPort = function getPort() {
23 return port; 24 return port;
24 }; 25 };
25 26
26 let ussdCallback = null; 27 let ussdCallback = null;
27 function setUssdCallback(cb) { 28 function setUssdCallback(cb) {
28 ussdCallback = cb; 29 ussdCallback = cb;
29 } 30 }
30 exports.setUssdCallback = setUssdCallback; 31 exports.setUssdCallback = setUssdCallback;
31 32
32 let smsSentCallback = null; 33 let smsSentCallback = null;
33 function setSmsSentCallback(cb) { 34 function setSmsSentCallback(cb) {
34 smsSentCallback = cb; 35 smsSentCallback = cb;
35 } 36 }
36 exports.setSmsSentCallback = setSmsSentCallback; 37 exports.setSmsSentCallback = setSmsSentCallback;
37 38
38 function isAlphaNumeric(str) { 39 function isAlphaNumeric(str) {
39 const len = str.length; 40 const len = str.length;
40 // eslint-disable-next-line no-plusplus 41 // eslint-disable-next-line no-plusplus
41 for (let i = 0; i < len; i++) { 42 for (let i = 0; i < len; i++) {
42 const code = str.charCodeAt(i); 43 const code = str.charCodeAt(i);
43 if (!(code > 47 && code < 58) // numeric (0-9) 44 if (!(code > 47 && code < 58) // numeric (0-9)
44 && !(code > 64 && code < 91) // upper alpha (A-Z) 45 && !(code > 64 && code < 91) // upper alpha (A-Z)
45 && !(code > 96 && code < 123)) { // lower alpha (a-z) 46 && !(code > 96 && code < 123)) { // lower alpha (a-z)
46 return false; 47 return false;
47 } 48 }
48 } 49 }
49 return true; 50 return true;
50 } 51 }
51 52
52 function parsePdu(_data) { 53 function parsePdu(_data) {
53 const data = _data && _data.toString().trim().toUpperCase(); 54 const data = _data && _data.toString().trim().toUpperCase();
54 55
55 if (!data) return null; 56 if (!data) return null;
56 if (!isAlphaNumeric(data)) return null; 57 if (!isAlphaNumeric(data)) return null;
57 58
58 try { 59 try {
59 const result = nodePdu.parse(data); 60 const result = nodePdu.parse(data);
60 return result; 61 return result;
61 } catch (e) { 62 } catch (e) {
62 return null; 63 return null;
63 } 64 }
64 } 65 }
65 66
66 function onCSQ(data) { 67 function onCSQ(data) {
67 const val = data.toString().trim().match(/\+CSQ:\s*(.*)/); 68 const val = data.toString().trim().match(/\+CSQ:\s*(.*)/);
68 if (!val || !val[1]) return null; 69 if (!val || !val[1]) return null;
69 70
70 const [, signalStrength] = val; 71 const [, signalStrength] = val;
71 72
72 logger.info('Signal quality extracted', { signalQuality: val[1] }); 73 logger.info('Signal quality extracted', { signalQuality: val[1] });
73 74
74 modemInfo.signalStrength = signalStrength; 75 modemInfo.signalStrength = signalStrength;
75 modemInfo.signalStrengthTs = new Date(); 76 modemInfo.signalStrengthTs = new Date();
76 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss'); 77 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss');
77 78
78 return signalStrength; 79 return signalStrength;
79 } 80 }
80 81
81 function onPduDeliver(data, parsedData) { 82 function onPduDeliver(data, parsedData) {
82 const from = parsedData.getAddress && parsedData.getAddress().getPhone 83 const from = parsedData.getAddress && parsedData.getAddress().getPhone
83 ? parsedData.getAddress().getPhone() : null; 84 ? parsedData.getAddress().getPhone() : null;
84 85
85 const msg = parsedData.getData && parsedData.getData().getData 86 const msg = parsedData.getData && parsedData.getData().getData
86 ? parsedData.getData().getData() : null; 87 ? parsedData.getData().getData() : null;
87 88
88 const ts = new Date(parsedData.getScts().getIsoString()); 89 const ts = new Date(parsedData.getScts().getIsoString());
89 90
90 logger.verbose('PDU processed', { ts, from, msg }); 91 logger.verbose('PDU processed', { ts, from, msg });
91 return { from, msg }; 92 return { ts, from, msg };
92 } 93 }
93 94
94 function onCOPS(data) { 95 function onCOPS(data) {
95 const val = data.toString().trim().match(/\+COPS:\s*(.*)/); 96 const val = data.toString().trim().match(/\+COPS:\s*(.*)/);
96 if (!val || !val[1]) return null; 97 if (!val || !val[1]) return null;
97 98
98 const cops = val[1]; 99 const cops = val[1];
99 100
100 if (!cops) return null; 101 if (!cops) return null;
101 const [mode, format, networkId] = cops.split(','); 102 const [mode, format, networkId] = cops.split(',');
102 const networkName = networkId ? dbCops[networkId] || networkId : null; 103 const networkName = networkId ? dbCops[networkId] || networkId : null;
103 104
104 logger.info('COPS extracted', { 105 logger.info('COPS extracted', {
105 cops, mode, format, networkId, networkName, 106 cops, mode, format, networkId, networkName,
106 }); 107 });
107 108
108 modemInfo.cops = cops; 109 modemInfo.cops = cops;
109 modemInfo.networkId = networkId || null; 110 modemInfo.networkId = networkId || null;
110 modemInfo.networkName = networkName || null; 111 modemInfo.networkName = networkName || null;
111 112
112 return { 113 return {
113 cops, mode, format, networkId, networkName, 114 cops, mode, format, networkId, networkName,
114 }; 115 };
115 } 116 }
116 117
117 118
118 function isResultCodeIs(data, resultCode) { 119 function isResultCodeIs(data, resultCode) {
119 if (!data) return false; 120 if (!data) return false;
120 const cleanedData = (data.toString() || '').trim(); 121 const cleanedData = (data.toString() || '').trim();
121 if (!cleanedData) return false; 122 if (!cleanedData) return false;
122 123
123 if (resultCode.indexOf('+') !== 0) { 124 if (resultCode.indexOf('+') !== 0) {
124 // eslint-disable-next-line no-param-reassign 125 // eslint-disable-next-line no-param-reassign
125 resultCode = `+${resultCode}`; 126 resultCode = `+${resultCode}`;
126 } 127 }
127 128
128 if (resultCode.search(/:$/) < 0) { 129 if (resultCode.search(/:$/) < 0) {
129 // eslint-disable-next-line no-param-reassign 130 // eslint-disable-next-line no-param-reassign
130 resultCode += ':'; 131 resultCode += ':';
131 } 132 }
132 133
133 return cleanedData.indexOf(resultCode) === 0; 134 return cleanedData.indexOf(resultCode) === 0;
134 } 135 }
135 136
136 const parserReadline = new ParserReadline({ delimiter: PARSER_READLINE_DELIMITER }); 137 const parserReadline = new ParserReadline({ delimiter: PARSER_READLINE_DELIMITER });
137 parserReadline.on('data', (data) => { 138 parserReadline.on('data', (data) => {
138 modemInfo.lastReadTs = new Date(); 139 modemInfo.lastReadTs = new Date();
139 logger.verbose('INCOMING', { data: `${data.toString()}${PARSER_READLINE_DELIMITER}`, parser: 'parserReadLine' }); 140 logger.verbose('INCOMING', { data: `${data.toString()}${PARSER_READLINE_DELIMITER}`, parser: 'parserReadLine' });
140 141
141 if (!data) return; 142 if (!data) return;
142 143
143 const pduParsed = parsePdu(data); 144 const pduParsed = parsePdu(data);
144 if (pduParsed && pduParsed.constructor.name !== 'Deliver') { 145 if (pduParsed && pduParsed.constructor.name !== 'Deliver') {
145 const pduType = pduParsed.getType(); 146 const pduType = pduParsed.getType();
146 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() }); 147 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() });
147 } 148 }
148 149
149 if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) { 150 if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) {
150 const pduType = pduParsed.getType(); 151 const pduType = pduParsed.getType();
151 logger.verbose('Got a PDU SMS-DELIVER', { pduType }); 152 logger.verbose('Got a PDU SMS-DELIVER', { pduType });
152 onPduDeliver(data, pduParsed); 153 const parsedData = onPduDeliver(data, pduParsed);
154
155 if (parsedData.ts && parsedData.from && parsedData.msg) {
156 reportSender.incomingSMS(parsedData.ts, parsedData.from, parsedData.msg);
157 } else {
158 logger.warn('Got a PDU SMS-DELIVER but failed on parsing PDU message');
159 }
153 } else if (data.toString().trim() === 'ERROR') { 160 } else if (data.toString().trim() === 'ERROR') {
154 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString()); 161 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
155 } else if (isResultCodeIs(data, 'CSQ')) { 162 } else if (isResultCodeIs(data, 'CSQ')) {
156 logger.verbose('Got a signal quality report', { data: data.toString() }); 163 logger.verbose('Got a signal quality report', { data: data.toString() });
157 onCSQ(data); 164 onCSQ(data);
158 } else if (isResultCodeIs(data, 'COPS:')) { 165 } else if (isResultCodeIs(data, 'COPS:')) {
159 logger.verbose('Got a COPS report', { data: data.toString() }); 166 logger.verbose('Got a COPS report', { data: data.toString() });
160 onCOPS(data); 167 onCOPS(data);
161 } else if (isResultCodeIs(data, 'CMT')) { 168 } else if (isResultCodeIs(data, 'CMT')) {
162 logger.verbose('Got a new message report', { data: data.toString() }); 169 logger.verbose('Got a new message report', { data: data.toString() });
163 } else if (isResultCodeIs(data, 'CMTI')) { 170 } else if (isResultCodeIs(data, 'CMTI')) {
164 logger.verbose('Got a new message notification report', { data: data.toString() }); 171 logger.verbose('Got a new message notification report', { data: data.toString() });
165 } else if (isResultCodeIs(data, 'CUSD')) { 172 } else if (isResultCodeIs(data, 'CUSD')) {
166 logger.verbose('Got a USSD command response', { data: data.toString() }); 173 logger.verbose('Got a USSD command response', { data: data.toString() });
167 if (typeof ussdCallback === 'function') { 174 if (typeof ussdCallback === 'function') {
168 logger.verbose('Calling USSD callback'); 175 logger.verbose('Calling USSD callback');
169 ussdCallback(data.toString()); 176 ussdCallback(data.toString());
170 } else { 177 } else {
171 logger.verbose('Skip unwanted USSD response'); 178 logger.verbose('Skip unwanted USSD response');
172 } 179 }
173 } else if (isResultCodeIs(data, 'CMGS')) { 180 } else if (isResultCodeIs(data, 'CMGS')) {
174 logger.verbose('Got CMGS report', { data: data.toString() }); 181 logger.verbose('Got CMGS report', { data: data.toString() });
175 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString()); 182 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
176 } else if (isResultCodeIs(data, 'CMS ERROR')) { 183 } else if (isResultCodeIs(data, 'CMS ERROR')) {
177 logger.verbose('Got CMS ERROR report', { data: data.toString() }); 184 logger.verbose('Got CMS ERROR report', { data: data.toString() });
178 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString()); 185 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
179 } 186 }
180 }); 187 });
181 188
182 const parserWaitForOkOrError = new ParserRegex({ regex: PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 189 const parserWaitForOkOrError = new ParserRegex({ regex: PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
183 parserWaitForOkOrError.on('data', (data) => { 190 parserWaitForOkOrError.on('data', (data) => {
184 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserWaitForOkOrError' }); 191 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserWaitForOkOrError' });
185 }); 192 });
186 193
187 194
188 exports.PARSER_READLINE_DELIMITER = PARSER_READLINE_DELIMITER; 195 exports.PARSER_READLINE_DELIMITER = PARSER_READLINE_DELIMITER;
189 exports.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = PARSER_WAIT_FOR_OK_OR_ERROR_REGEX; 196 exports.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = PARSER_WAIT_FOR_OK_OR_ERROR_REGEX;
190 197
191 exports.parserReadline = parserReadline; 198 exports.parserReadline = parserReadline;
192 exports.parserWaitForOkOrError = parserWaitForOkOrError; 199 exports.parserWaitForOkOrError = parserWaitForOkOrError;
193 200
1 const SerialPort = require('serialport'); 1 const SerialPort = require('serialport');
2 2
3 const config = require('komodo-sdk/config'); 3 const config = require('komodo-sdk/config');
4 const logger = require('komodo-sdk/logger'); 4 const logger = require('komodo-sdk/logger');
5 5
6 const ParserInterByteTimeout = require('@serialport/parser-inter-byte-timeout'); 6 const ParserInterByteTimeout = require('@serialport/parser-inter-byte-timeout');
7 7
8 const parsers = require('./lib/serialport-parsers'); 8 const parsers = require('./lib/serialport-parsers');
9 const modemCommands = require('./lib/modem-commands'); 9 const modemCommands = require('./lib/modem-commands');
10 const modemInfo = require('./lib/modem-info'); 10 const modemInfo = require('./lib/modem-info');
11 11
12 const parserInterByteTimeout = new ParserInterByteTimeout({ interval: 1000 }); 12 const parserInterByteTimeout = new ParserInterByteTimeout({ interval: 1000 });
13 parserInterByteTimeout.on('data', (data) => { 13 parserInterByteTimeout.on('data', (data) => {
14 logger.verbose('INCOMING', { parser: 'parserInterByteTimeout', data: data.toString() }); 14 logger.verbose('INCOMING', { parser: 'parserInterByteTimeout', data: data.toString() });
15 }); 15 });
16 16
17 const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (err) => { 17 const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (err) => {
18 if (err) { 18 if (err) {
19 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`); 19 logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`);
20 process.exit(1); 20 process.exit(1);
21 } 21 }
22 22
23 await modemCommands.sendCtrlZ(); 23 await modemCommands.sendCtrlZ();
24 // await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r'); 24 // await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r');
25 await modemCommands.writeToPortAndWaitForOkOrError('AT&FE0\r'); 25 await modemCommands.writeToPortAndWaitForOkOrError('AT&FE0\r');
26 await modemCommands.writeToPortAndWaitForOkOrError('AT+CMGF=0\r'); 26 await modemCommands.writeToPortAndWaitForOkOrError('AT+CMGF=0\r');
27 await modemCommands.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r'); 27 await modemCommands.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r');
28 28
29 await modemCommands.queryManufacturer(); 29 await modemCommands.queryManufacturer();
30 await modemCommands.queryModel(); 30 await modemCommands.queryModel();
31 31
32 await modemCommands.queryIMEIAndIMSI(); 32 await modemCommands.queryIMEIAndIMSI();
33 await modemCommands.queryCOPSAndSignalQuality(); 33 await modemCommands.queryCOPSAndSignalQuality();
34 logger.info('Modem state', modemInfo); 34 logger.info('Modem state', modemInfo);
35 35
36 // await modemCommands.sendSMS('628128364883', `coba pakai pdu ${new Date()}`); 36 if (config.debug_sms_destination_on_start) {
37 // await modemCommands.sendSMS('+6282210008543', `coba pakai pdu ${new Date()}`); 37 await modemCommands.sendSMS(config.debug_sms_destination_on_start, `${modemInfo.imsi || 'UNKNOWN'}: coba pakai pdu ${new Date()}`);
38 // await modemCommands.sendSMS('6281809903333', `coba pakai pdu ${new Date()}`); 38 }
39 await modemCommands.sendSMS('999', `coba pakai pdu ${new Date()}`);
40 39
41 // const ussdResponse = await modemCommands.executeUSSD('*888#', 2); 40 if (config.debug_ussd_code_on_start) {
42 // logger.info('USSD RESPONSE', { ussdResponse }); 41 const ussdResponse = await modemCommands.executeUSSD(config.debug_ussd_code_on_start, 2);
42 logger.info('USSD RESPONSE', { command: config.debug_ussd_code_on_start, ussdResponse });
43 }
43 44
44 setInterval(async () => { 45 setInterval(async () => {
45 await modemCommands.initATCommands(); 46 await modemCommands.initATCommands();
46 await modemCommands.queryManufacturer(); 47 await modemCommands.queryManufacturer();
47 await modemCommands.queryModel(); 48 await modemCommands.queryModel();
48 await modemCommands.queryIMEIAndIMSI(); 49 await modemCommands.queryIMEIAndIMSI();
49 await modemCommands.queryCOPSAndSignalQuality(); 50 await modemCommands.queryCOPSAndSignalQuality();
50 logger.info('Modem state', modemInfo); 51 logger.info('Modem state', modemInfo);
51 }, config.interval_beetwen_signal_strength_ms || 30000); 52 }, config.interval_beetwen_signal_strength_ms || 30000);
52 }); 53 });
53 54
54 global.MODEM_PORT = port; 55 global.MODEM_PORT = port;
55 parsers.setPort(port); 56 parsers.setPort(port);
56 modemCommands.setPort(port); 57 // modemCommands.setPort(port);
57 58
58 if (config && config.modem_tester && config.modem_tester.parser === 'regex') { 59 if (config && config.modem_tester && config.modem_tester.parser === 'regex') {
59 logger.info('Using parserWaitForOkOrError'); 60 logger.info('Using parserWaitForOkOrError');
60 port.pipe(parsers.parserWaitForOkOrError); 61 port.pipe(parsers.parserWaitForOkOrError);
61 } else if (config && config.modem_tester && config.modem_tester.parser === 'interbyte') { 62 } else if (config && config.modem_tester && config.modem_tester.parser === 'interbyte') {
62 logger.info('Using parserInterByteTimeout'); 63 logger.info('Using parserInterByteTimeout');
63 port.pipe(parserInterByteTimeout); 64 port.pipe(parserInterByteTimeout);
64 } else { 65 } else {
65 logger.info('Using parserReadline'); 66 logger.info('Using parserReadline');
66 port.pipe(parsers.parserReadline); 67 port.pipe(parsers.parserReadline);
67 } 68 }
1 { 1 {
2 "name": "komodo-modem-sms", 2 "name": "komodo-modem-sms",
3 "version": "0.10.52", 3 "version": "0.10.52",
4 "description": "Generic SMS modem driver", 4 "description": "Generic SMS modem driver",
5 "main": "index.js", 5 "main": "index.js",
6 "scripts": { 6 "scripts": {
7 "test": "mocha", 7 "test": "mocha",
8 "postversion": "git push && git push --tags" 8 "postversion": "git push && git push --tags",
9 "jsdoc": "./generate-jsdoc.sh"
9 }, 10 },
10 "repository": { 11 "repository": {
11 "type": "git", 12 "type": "git",
12 "url": "http://gitlab.kodesumber.com/komodo/komodo-modem-sms.git" 13 "url": "http://gitlab.kodesumber.com/komodo/komodo-modem-sms.git"
13 }, 14 },
14 "keywords": [ 15 "keywords": [
15 "komodo", 16 "komodo",
16 "sms", 17 "sms",
17 "tektrans", 18 "tektrans",
18 "ppob" 19 "ppob"
19 ], 20 ],
20 "author": "Adhidarma Hadiwinoto <me@adhisimon.org>", 21 "author": "Adhidarma Hadiwinoto <me@adhisimon.org>",
21 "license": "ISC", 22 "license": "ISC",
22 "devDependencies": { 23 "devDependencies": {
23 "eslint": "^5.16.0", 24 "eslint": "^5.16.0",
24 "eslint-config-airbnb-base": "^13.2.0", 25 "eslint-config-airbnb-base": "^13.2.0",
25 "eslint-plugin-import": "^2.18.2", 26 "eslint-plugin-import": "^2.18.2",
26 "should": "^13.2.3" 27 "should": "^13.2.3"
27 }, 28 },
28 "dependencies": { 29 "dependencies": {
29 "@serialport/parser-delimiter": "^2.0.2", 30 "@serialport/parser-delimiter": "^2.0.2",
30 "@serialport/parser-inter-byte-timeout": "^1.1.0", 31 "@serialport/parser-inter-byte-timeout": "^1.1.0",
31 "@serialport/parser-readline": "^2.0.2", 32 "@serialport/parser-readline": "^2.0.2",
32 "@serialport/parser-ready": "^2.0.2", 33 "@serialport/parser-ready": "^2.0.2",
33 "@serialport/parser-regex": "^2.0.2", 34 "@serialport/parser-regex": "^2.0.2",
34 "express": "^4.17.1", 35 "express": "^4.17.1",
35 "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git", 36 "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git",
36 "locks": "^0.2.2", 37 "locks": "^0.2.2",
37 "moment": "^2.24.0", 38 "moment": "^2.24.0",
38 "node-pdu": "^1.0.15", 39 "node-pdu": "^1.0.15",
39 "pdu": "^1.1.0", 40 "pdu": "^1.1.0",
40 "request": "^2.88.0", 41 "request": "^2.88.0",
41 "serialport": "^7.1.5", 42 "serialport": "^7.1.5",
42 "serialport-gsm": "^3.2.0", 43 "serialport-gsm": "^3.2.0",
43 "sms-pdu-node": "^0.1.2", 44 "sms-pdu-node": "^0.1.2",
44 "uuid": "^3.3.3" 45 "uuid": "^3.3.3"
45 } 46 }
46 } 47 }
47 48