Commit 956ce5804dcc9d0973c9d56c913f950bbaf7e6b9

Authored by Adhidarma Hadiwinoto
1 parent 56d3775c11
Exists in master

Minors

Showing 1 changed file with 1 additions and 5 deletions Inline Diff

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