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