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