Blame view
lib/modem.js
12.9 KB
e49a56032
|
1 |
/** |
eb83d58a7
|
2 3 4 5 |
* Modul modem. * * Modul ini sedang proses pengalihan ke module:bootstrap * |
e49a56032
|
6 7 8 |
* @module * @deprecated going to move to module:bootstrap */ |
49eaf19a3
|
9 |
|
e07d18058
|
10 |
const DEFAULT_SLEEP_AFTER_SEND_SMS_MS = 2000; |
e3ffe9ea6
|
11 |
const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 30000; |
fb3911e43
|
12 |
const MAX_LAST_DATA_AGE_MS = 3 * 60 * 1000; |
8ebd9e257
|
13 14 |
const REGEX_WAIT_FOR_OK_OR_ERROR = / (?:OK|ERROR)\r/; |
c5ce14d55
|
15 16 |
// const REGEX_WAIT_FOR_OK_OR_ERROR_USSD = / (?:OK|ERROR)\r/; |
35ba3f574
|
17 |
|
5a00ec06f
|
18 |
const moment = require('moment'); |
49eaf19a3
|
19 20 |
const SerialPort = require('serialport'); const ParserReadline = require('@serialport/parser-readline'); |
2db546a87
|
21 22 23 |
// const ParserDelimiter = require('@serialport/parser-delimiter'); const ParserRegex = require('@serialport/parser-regex'); |
49eaf19a3
|
24 25 26 |
const config = require('komodo-sdk/config'); const logger = require('komodo-sdk/logger'); |
49eaf19a3
|
27 |
|
cba7eef40
|
28 |
const mutex = require('./mutex'); |
67af4245e
|
29 |
const common = require('./common'); |
49eaf19a3
|
30 |
const sms = require('./sms'); |
7355ad895
|
31 |
const dbCops = require('./db-cops'); |
5ae543453
|
32 |
const reportSender = require('./report-sender'); |
a846e83f2
|
33 |
const registerModem = require('./register-modem'); |
49eaf19a3
|
34 |
|
bc541e414
|
35 |
const modemInfo = { |
209ea177a
|
36 |
device: config.modem.device, |
c428dcf05
|
37 38 |
manufacturer: null, model: null, |
3ec3e9eb3
|
39 |
imei: null, |
bc541e414
|
40 |
imsi: null, |
9ad3c8d30
|
41 |
msisdn: null, |
7355ad895
|
42 43 44 |
cops: null, networkId: null, networkName: null, |
bc541e414
|
45 |
signalStrength: null, |
5a00ec06f
|
46 47 |
signalStrengthTs: null, signalStrengthTsReadable: null, |
bc541e414
|
48 |
}; |
49eaf19a3
|
49 |
|
35ba3f574
|
50 |
let lastTs = new Date(); |
04b85c093
|
51 |
let port; |
49eaf19a3
|
52 53 |
const parserReadLine = new ParserReadline(); |
0bdac2f9c
|
54 |
|
2db546a87
|
55 |
const parserWaitForOK = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
0bdac2f9c
|
56 57 58 |
parserWaitForOK.on('data', () => { mutex.releaseLockWaitForCommand(); }); |
49eaf19a3
|
59 60 61 62 63 64 65 66 67 |
function writeToPort(data) { return new Promise((resolve) => { port.write(data, (err, bytesWritten) => { if (err) logger.warn(`ERROR: ${err.toString()}`); logger.verbose(`* OUT: ${data}`); resolve(bytesWritten); }); }); } |
49eaf19a3
|
68 |
async function readSMS(slot) { |
2db546a87
|
69 |
const parserCMGR = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
0bdac2f9c
|
70 |
parserCMGR.on('data', (data) => { |
49eaf19a3
|
71 |
if (data) { |
2db546a87
|
72 |
try { |
343164ad5
|
73 |
reportSender.incomingSMS(sms.extract(data.toString().trim()), modemInfo); |
2db546a87
|
74 75 76 77 78 |
} catch (e) { logger.warn(`Exception on reporting new message. ${e.toString()}`, { smsObj: e.smsObj, dataFromModem: data }); process.exit(0); } |
49eaf19a3
|
79 |
} |
0bdac2f9c
|
80 81 82 |
port.unpipe(parserCMGR); mutex.releaseLockWaitForCommand(); }); |
2db546a87
|
83 84 |
// const parserCMGD = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK }); const parserCMGD = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
0bdac2f9c
|
85 86 87 |
parserCMGD.on('data', () => { port.unpipe(parserCMGD); mutex.releaseLockWaitForCommand(); |
49eaf19a3
|
88 89 90 |
}); logger.info(`Reading SMS on slot ${slot}`); |
0bdac2f9c
|
91 92 93 94 |
await mutex.setLockWaitForCommand(); port.pipe(parserCMGR); await writeToPort(`AT+CMGR=${slot}\r`); logger.info(`Finished reading SMS on slot ${slot}`); |
49eaf19a3
|
95 96 |
logger.info(`Deleting message on slot ${slot}`); |
0bdac2f9c
|
97 98 99 |
await mutex.setLockWaitForCommand(); port.pipe(parserCMGD); await writeToPort(`AT+CMGD=${slot}\r`); |
49eaf19a3
|
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
logger.info('Message processing has completed'); } function onIncomingSMS(data) { const value = common.extractValueFromReadLineData(data); if (!value) return; const chunks = value.split(','); if (!chunks && !chunks[1]) return; const slot = chunks[1]; logger.info(`Incoming SMS on slot ${slot}`); readSMS(slot); } |
7355ad895
|
115 116 117 118 119 120 121 122 123 |
function onCOPS(data) { modemInfo.cops = common.extractValueFromReadLineData(data).trim(); logger.info(`Connected Network: ${modemInfo.cops}`); if (!modemInfo.cops) return; [, , modemInfo.networkId] = modemInfo.cops.split(','); if (modemInfo.networkId) { |
e811898ae
|
124 |
modemInfo.networkName = dbCops[modemInfo.networkId] || modemInfo.networkId; |
7355ad895
|
125 126 |
} } |
49eaf19a3
|
127 128 129 |
parserReadLine.on('data', (data) => { logger.verbose(`* IN: ${data}`); if (data) { |
35ba3f574
|
130 |
lastTs = new Date(); |
49eaf19a3
|
131 |
if (data.indexOf('+CSQ: ') === 0) { |
5a00ec06f
|
132 133 134 135 136 137 |
const signalStrength = common.extractValueFromReadLineData(data).trim(); if (signalStrength) { modemInfo.signalStrength = signalStrength; modemInfo.signalStrengthTs = new Date(); modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss'); logger.info(`Signal strength: ${modemInfo.signalStrength}`); |
d416919cb
|
138 |
registerModem(modemInfo); |
5a00ec06f
|
139 |
} |
49eaf19a3
|
140 141 |
} else if (data.indexOf('+CMTI: ') === 0) { onIncomingSMS(data); |
7355ad895
|
142 143 |
} else if (data.indexOf('+COPS: ') === 0) { onCOPS(data); |
49eaf19a3
|
144 145 146 |
} } }); |
2db546a87
|
147 148 |
async function simpleSubCommand(cmd, callback) { const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
c428dcf05
|
149 150 |
parser.on('data', (data) => { port.unpipe(parser); |
2db546a87
|
151 |
mutex.releaseLockWaitForSubCommand(); |
c428dcf05
|
152 153 |
if (data) { |
2db546a87
|
154 |
if (callback) callback(null, data.toString().trim()); |
c428dcf05
|
155 156 |
} }); |
2db546a87
|
157 158 159 160 161 162 163 164 165 166 |
return new Promise(async (resolve) => { await mutex.setLockWaitForSubCommand(); port.pipe(parser); writeToPort(cmd); await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); resolve(); }); |
c428dcf05
|
167 |
} |
c428dcf05
|
168 169 |
function readManufacturer() { return new Promise((resolve) => { |
2db546a87
|
170 |
simpleSubCommand('AT+CGMI\r', (err, result) => { |
c428dcf05
|
171 172 173 174 175 176 177 178 179 |
modemInfo.manufacturer = result; logger.info(`Manufacturer: ${result}`); resolve(result); }); }); } function readModel() { return new Promise((resolve) => { |
2db546a87
|
180 |
simpleSubCommand('AT+CGMM\r', (err, result) => { |
c428dcf05
|
181 182 183 184 185 186 187 188 189 |
modemInfo.model = result; logger.info(`Model: ${result}`); resolve(result); }); }); } function readIMEI() { return new Promise((resolve) => { |
2db546a87
|
190 |
simpleSubCommand('AT+CGSN\r', (err, result) => { |
c428dcf05
|
191 192 193 194 195 196 197 198 199 |
modemInfo.imei = result; logger.info(`IMEI: ${result}`); resolve(result); }); }); } function readIMSI() { return new Promise((resolve) => { |
2db546a87
|
200 |
simpleSubCommand('AT+CIMI\r', (err, result) => { |
c428dcf05
|
201 202 |
modemInfo.imsi = result; logger.info(`IMSI: ${result}`); |
9ad3c8d30
|
203 204 |
if (result) { |
e88bc6967
|
205 |
/* |
9ad3c8d30
|
206 207 208 |
modemInfo.msisdn = msisdn[result]; if (modemInfo.msisdn) { logger.info(`MSISDN: ${modemInfo.msisdn}`); |
0c5ab3931
|
209 |
registerModem(modemInfo); |
9ad3c8d30
|
210 |
} |
e88bc6967
|
211 |
*/ |
5087dec58
|
212 |
} else { |
18586efc2
|
213 |
logger.warn(`IMSI not detected. Please insert a sim card to your modem. Terminating ${config.modem.device}.`); |
24c0e1ac1
|
214 215 |
process.exit(2); } |
c428dcf05
|
216 217 218 219 |
resolve(result); }); }); } |
49eaf19a3
|
220 |
|
2db546a87
|
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
function readCOPS() { return new Promise((resolve) => { simpleSubCommand('AT+COPS?\r', (err, result) => { resolve(result); }); }); } function deleteInbox() { return new Promise((resolve) => { simpleSubCommand('AT+CMGD=0,4\r', (err, result) => { resolve(result); }); }); } |
49eaf19a3
|
236 |
async function querySignalStrength() { |
2db546a87
|
237 |
const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
0bdac2f9c
|
238 239 240 241 242 243 244 245 246 |
parser.on('data', () => { port.unpipe(parser); mutex.releaseLockWaitForCommand(); }); if (mutex.tryLockWaitForCommand()) { port.pipe(parser); await writeToPort('AT+CSQ\r'); } |
49eaf19a3
|
247 |
} |
a8340480b
|
248 249 250 251 252 253 254 |
function registerModemToCenterPeriodically() { registerModem(modemInfo); setInterval(() => { registerModem(modemInfo); }, 60 * 1000); } |
49eaf19a3
|
255 256 |
async function registerSignalStrengthBackgroundQuery() { logger.info('Registering background signal strength query'); |
0bdac2f9c
|
257 |
querySignalStrength(); |
49eaf19a3
|
258 259 |
setInterval(() => { querySignalStrength(); |
0bdac2f9c
|
260 |
}, config.interval_beetwen_signal_strength_ms || INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS); |
49eaf19a3
|
261 262 263 |
} async function sendSMS(destination, msg) { |
3023245c4
|
264 |
if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return; |
f323e782d
|
265 266 267 |
// const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); const parser = new ParserReadline({ delimiter: '\r ' }); |
0bdac2f9c
|
268 269 270 271 272 273 |
parser.on('data', () => { port.unpipe(parser); mutex.releaseLockWaitForSubCommand(); }); logger.verbose('Waiting for command lock to send message'); |
cba7eef40
|
274 |
await mutex.setLockWaitForCommand(); |
0bdac2f9c
|
275 |
|
e07d18058
|
276 |
logger.info('Preparing to send message', { destination, msg }); |
49eaf19a3
|
277 278 |
const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+'); |
e07d18058
|
279 |
logger.verbose('Waiting for lock before set SMS to text mode'); |
0bdac2f9c
|
280 281 282 283 284 285 286 |
await mutex.setLockWaitForSubCommand(); port.pipe(parser); await writeToPort('AT+CMGF=1\r'); logger.verbose('Waiting for lock before writing message'); await mutex.setLockWaitForSubCommand(); port.pipe(parser); |
063b2ec2f
|
287 288 289 |
await writeToPort(`AT+CMGS="${correctedDestination}"\r`); await writeToPort(msg); await writeToPort(Buffer.from([0x1A])); |
0bdac2f9c
|
290 291 292 |
await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); |
49eaf19a3
|
293 294 |
logger.info('Message has been sent'); |
0bdac2f9c
|
295 296 297 |
setTimeout(() => { logger.verbose('Releasing command lock'); mutex.releaseLockWaitForCommand(); |
e07d18058
|
298 |
}, config.sleep_after_send_sms_ms || DEFAULT_SLEEP_AFTER_SEND_SMS_MS); |
49eaf19a3
|
299 |
} |
4e48bc12e
|
300 |
|
c5ce14d55
|
301 302 303 |
/** * Ekseksusi kode USSD. * |
17ea7e3e9
|
304 |
* Pilihan includeCUSD2: |
71a0b3ff6
|
305 |
* -1: sebelum |
17ea7e3e9
|
306 307 |
* 0: tidak (default) * 1: sesudah |
71a0b3ff6
|
308 |
* 2: sebelum dan sesudah |
17ea7e3e9
|
309 |
* |
8a8888ce3
|
310 |
* @param {string} code - Kode USSD |
17ea7e3e9
|
311 |
* @param {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2 |
c5ce14d55
|
312 |
*/ |
49ca8506b
|
313 |
function executeUSSD(code, includeCUSD2) { |
c5ce14d55
|
314 |
return new Promise(async (resolve) => { |
761e213ec
|
315 316 317 |
const parserMain = new ParserReadline({ delimiter: '\r ' }); // const parserMain = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
49ca8506b
|
318 |
parserMain.on('data', (data) => { |
c07bd35b3
|
319 320 321 |
if (!data || !data.toString().trim()) return; if (data.toString().trim() === 'OK') return; |
49ca8506b
|
322 |
port.unpipe(parserMain); |
c5ce14d55
|
323 324 325 |
mutex.releaseLockWaitForSubCommand(); resolve(data); }); |
761e213ec
|
326 327 328 |
const parserCUSD2 = new ParserReadline({ delimiter: '\r ' }); // const parserCUSD2 = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR }); |
49ca8506b
|
329 330 331 332 |
parserCUSD2.on('data', () => { port.unpipe(parserCUSD2); mutex.releaseLockWaitForSubCommand(); }); |
c5ce14d55
|
333 334 |
logger.verbose('Waiting for command lock to execute USSD'); await mutex.setLockWaitForCommand(); |
71a0b3ff6
|
335 |
if (includeCUSD2 === -1 || includeCUSD2 === 2) { |
49ca8506b
|
336 |
await mutex.setLockWaitForSubCommand(); |
ab2ccd9a7
|
337 |
logger.info('Terminating existing USSD session'); |
49ca8506b
|
338 339 340 |
port.pipe(parserCUSD2); await writeToPort('AT+CUSD=2\r'); } |
c5ce14d55
|
341 |
await mutex.setLockWaitForSubCommand(); |
1bf87189f
|
342 |
logger.info(`Executing USSD code "${code}"`); |
49ca8506b
|
343 |
port.pipe(parserMain); |
ff8221ba9
|
344 |
await writeToPort(`AT+CUSD=1,"${code}",15\r`); |
c5ce14d55
|
345 |
|
71a0b3ff6
|
346 |
if (includeCUSD2 === 1 || includeCUSD2 === 2) { |
49ca8506b
|
347 |
await mutex.setLockWaitForSubCommand(); |
ab2ccd9a7
|
348 |
logger.info('Terminating existing USSD session'); |
49ca8506b
|
349 350 351 |
port.pipe(parserCUSD2); await writeToPort('AT+CUSD=2\r'); } |
c5ce14d55
|
352 353 354 355 356 357 |
await mutex.setLockWaitForSubCommand(); mutex.releaseLockWaitForSubCommand(); mutex.releaseLockWaitForCommand(); }); } |
49eaf19a3
|
358 359 |
function init() { |
04b85c093
|
360 361 362 363 364 |
port = new SerialPort(config.modem.device, { baudRate: 115200 }, (err) => { if (err) { logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`); process.exit(1); } |
38b05d214
|
365 366 |
registerModem(modemInfo); |
04b85c093
|
367 368 |
}); port.pipe(parserReadLine); |
18586efc2
|
369 |
setInterval(() => { |
35ba3f574
|
370 |
if ((new Date() - lastTs) > MAX_LAST_DATA_AGE_MS) { |
18586efc2
|
371 |
logger.warn(`No data for more than ${MAX_LAST_DATA_AGE_MS} ms. Modem might be unresponsive. Terminating modem ${config.modem.device}.`); |
35ba3f574
|
372 373 374 |
process.exit(0); } }, 30 * 1000); |
49eaf19a3
|
375 |
port.on('open', async () => { |
2db546a87
|
376 |
await mutex.setLockWaitForCommand(); |
49eaf19a3
|
377 378 |
logger.info('Modem opened'); |
2db546a87
|
379 380 |
await writeToPort('\r'); await simpleSubCommand('AT\r'); |
49eaf19a3
|
381 382 |
logger.info('Initializing modem to factory set'); |
2db546a87
|
383 |
await simpleSubCommand('AT&F\r'); |
49eaf19a3
|
384 385 |
logger.info('Disabling echo'); |
2db546a87
|
386 |
await simpleSubCommand('ATE0\r'); |
49eaf19a3
|
387 |
|
c3fd6c7ff
|
388 389 |
logger.info('Set to text mode'); await simpleSubCommand('AT+CMGF=1\r'); |
7012ada0e
|
390 391 |
logger.info('Set message indication'); await simpleSubCommand('AT+CNMI=1,1,2,1,1\r'); |
2db546a87
|
392 |
await readCOPS(); |
7355ad895
|
393 |
|
c428dcf05
|
394 395 396 |
await readManufacturer(); await readModel(); await readIMEI(); |
49eaf19a3
|
397 |
await readIMSI(); |
2253f95a7
|
398 399 |
if (!config.disable_delete_inbox_on_startup) { logger.info('Deleting existing messages'); |
2db546a87
|
400 |
await deleteInbox(); |
2253f95a7
|
401 |
} |
0bdac2f9c
|
402 |
mutex.releaseLockWaitForCommand(); |
2db546a87
|
403 |
logger.verbose('Init completed'); |
0bdac2f9c
|
404 |
|
a8340480b
|
405 |
registerModemToCenterPeriodically(); |
49eaf19a3
|
406 |
registerSignalStrengthBackgroundQuery(); |
49eaf19a3
|
407 408 409 410 |
}); } init(); |
bc541e414
|
411 |
exports.modemInfo = modemInfo; |
49eaf19a3
|
412 |
exports.sendSMS = sendSMS; |
c5ce14d55
|
413 |
exports.executeUSSD = executeUSSD; |