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