Commit beba81d88625590172223aff46fb008136030ad4

Authored by Adhidarma Hadiwinoto
1 parent 37d0dbf8ab
Exists in master

SMS error

Showing 2 changed files with 12 additions and 4 deletions Inline Diff

lib/modem-commands/index.js
1 const MUTEX_COMMAND = 'COMMAND'; 1 const MUTEX_COMMAND = 'COMMAND';
2 exports.MUTEX_COMMAND = MUTEX_COMMAND; 2 exports.MUTEX_COMMAND = MUTEX_COMMAND;
3 3
4 const MUTEX_SUBCOMMAND = 'SUBCOMMAND'; 4 const MUTEX_SUBCOMMAND = 'SUBCOMMAND';
5 exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND; 5 exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND;
6 6
7 const CTRLZ = '\u001a'; 7 const CTRLZ = '\u001a';
8 8
9 const pdu = require('node-pdu'); 9 const pdu = require('node-pdu');
10 const uuidv1 = require('uuid/v1'); 10 const uuidv1 = require('uuid/v1');
11 11
12 const ParserReadline = require('@serialport/parser-readline'); 12 const ParserReadline = require('@serialport/parser-readline');
13 const ParserRegex = require('@serialport/parser-regex'); 13 const ParserRegex = require('@serialport/parser-regex');
14 const ParserReady = require('@serialport/parser-ready'); 14 const ParserReady = require('@serialport/parser-ready');
15 15
16 const logger = require('komodo-sdk/logger'); 16 const logger = require('komodo-sdk/logger');
17 const mutex = require('../mutex-common'); 17 const mutex = require('../mutex-common');
18 const parsers = require('../serialport-parsers'); 18 const parsers = require('../serialport-parsers');
19 const modemInfo = require('../modem-info'); 19 const modemInfo = require('../modem-info');
20 20
21 let port; 21 let port;
22 22
23 function writeToPort(data) { 23 function writeToPort(data) {
24 return new Promise((resolve) => { 24 return new Promise((resolve) => {
25 modemInfo.lastWriteTs = new Date(); 25 modemInfo.lastWriteTs = new Date();
26 port.write(data, (err, bytesWritten) => { 26 port.write(data, (err, bytesWritten) => {
27 if (err) logger.warn(`ERROR: ${err.toString()}`); 27 if (err) logger.warn(`ERROR: ${err.toString()}`);
28 28
29 logger.verbose('OUTGOING', { data: data.toString(), bytesWritten, err }); 29 logger.verbose('OUTGOING', { data: data.toString(), bytesWritten, err });
30 resolve(bytesWritten); 30 resolve(bytesWritten);
31 }); 31 });
32 }); 32 });
33 } 33 }
34 exports.writeToPort = writeToPort; 34 exports.writeToPort = writeToPort;
35 35
36 function writeToPortAndWaitForReadline(cmd, lockName) { 36 function writeToPortAndWaitForReadline(cmd, lockName) {
37 let resolved = false; 37 let resolved = false;
38 38
39 return new Promise(async (resolve) => { 39 return new Promise(async (resolve) => {
40 const parser = new ParserReadline({ delimiter: parsers.PARSER_READLINE_DELIMITER }); 40 const parser = new ParserReadline({ delimiter: parsers.PARSER_READLINE_DELIMITER });
41 parser.on('data', (data) => { 41 parser.on('data', (data) => {
42 port.unpipe(parser); 42 port.unpipe(parser);
43 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim()); 43 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
44 if (!resolved) { 44 if (!resolved) {
45 resolved = true; 45 resolved = true;
46 resolve(data); 46 resolve(data);
47 } 47 }
48 }); 48 });
49 49
50 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim()); 50 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
51 port.pipe(parser); 51 port.pipe(parser);
52 await writeToPort(cmd); 52 await writeToPort(cmd);
53 }); 53 });
54 } 54 }
55 exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline; 55 exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline;
56 56
57 function writeToPortAndWaitForOkOrError(cmd, lockName) { 57 function writeToPortAndWaitForOkOrError(cmd, lockName) {
58 return new Promise(async (resolve) => { 58 return new Promise(async (resolve) => {
59 const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ }); 59 const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ });
60 parser.on('data', (data) => { 60 parser.on('data', (data) => {
61 port.unpipe(parser); 61 port.unpipe(parser);
62 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim()); 62 mutex.unlock(lockName || MUTEX_COMMAND, cmd.trim());
63 resolve(data); 63 resolve(data);
64 }); 64 });
65 65
66 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim()); 66 await mutex.lock(lockName || MUTEX_COMMAND, cmd.trim());
67 port.pipe(parser); 67 port.pipe(parser);
68 await writeToPort(cmd); 68 await writeToPort(cmd);
69 }); 69 });
70 } 70 }
71 exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError; 71 exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError;
72 72
73 function sleep(ms) { 73 function sleep(ms) {
74 return new Promise((resolve) => { 74 return new Promise((resolve) => {
75 setTimeout(() => { 75 setTimeout(() => {
76 resolve(); 76 resolve();
77 }, ms || 0); 77 }, ms || 0);
78 }); 78 });
79 } 79 }
80 exports.sleep = sleep; 80 exports.sleep = sleep;
81 81
82 exports.setPort = function setPort(val) { 82 exports.setPort = function setPort(val) {
83 port = val; 83 port = val;
84 }; 84 };
85 85
86 function querySignalQuality() { 86 function querySignalQuality() {
87 return new Promise(async (resolve) => { 87 return new Promise(async (resolve) => {
88 if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) { 88 if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) {
89 resolve(false); 89 resolve(false);
90 return; 90 return;
91 } 91 }
92 92
93 await writeToPort('AT+CSQ\r'); 93 await writeToPort('AT+CSQ\r');
94 mutex.unlock(MUTEX_COMMAND, 'querySignalQuality'); 94 mutex.unlock(MUTEX_COMMAND, 'querySignalQuality');
95 resolve(true); 95 resolve(true);
96 }); 96 });
97 } 97 }
98 exports.querySignalQuality = querySignalQuality; 98 exports.querySignalQuality = querySignalQuality;
99 99
100 function queryCOPS(lockName) { 100 function queryCOPS(lockName) {
101 return new Promise(async (resolve) => { 101 return new Promise(async (resolve) => {
102 await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS'); 102 await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS');
103 await writeToPort('AT+COPS?\r'); 103 await writeToPort('AT+COPS?\r');
104 mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS'); 104 mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS');
105 resolve(true); 105 resolve(true);
106 }); 106 });
107 } 107 }
108 exports.queryCOPS = queryCOPS; 108 exports.queryCOPS = queryCOPS;
109 109
110 function queryCOPSAndSignalQuality(skipOnLocked) { 110 function queryCOPSAndSignalQuality(skipOnLocked) {
111 return new Promise(async (resolve) => { 111 return new Promise(async (resolve) => {
112 if (!skipOnLocked) { 112 if (!skipOnLocked) {
113 await mutex.lock(MUTEX_COMMAND); 113 await mutex.lock(MUTEX_COMMAND);
114 } else if (!mutex.tryLock(MUTEX_COMMAND, 'queryCOPSAndSignalQuality')) { 114 } else if (!mutex.tryLock(MUTEX_COMMAND, 'queryCOPSAndSignalQuality')) {
115 resolve(false); 115 resolve(false);
116 return; 116 return;
117 } 117 }
118 118
119 await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND); 119 await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND);
120 await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND); 120 await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND);
121 121
122 mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality'); 122 mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality');
123 resolve(true); 123 resolve(true);
124 }); 124 });
125 } 125 }
126 exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality; 126 exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality;
127 127
128 function queryIMEI(lockName) { 128 function queryIMEI(lockName) {
129 return new Promise(async (resolve) => { 129 return new Promise(async (resolve) => {
130 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 130 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
131 parser.on('data', (data) => { 131 parser.on('data', (data) => {
132 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMEI' }); 132 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMEI' });
133 port.unpipe(parser); 133 port.unpipe(parser);
134 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMEI'); 134 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMEI');
135 modemInfo.imei = data.toString().trim() || null; 135 modemInfo.imei = data.toString().trim() || null;
136 logger.info('IMEI extracted', { imei: modemInfo.imei }); 136 logger.info('IMEI extracted', { imei: modemInfo.imei });
137 resolve(modemInfo.imei); 137 resolve(modemInfo.imei);
138 }); 138 });
139 139
140 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMEI'); 140 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMEI');
141 141
142 port.pipe(parser); 142 port.pipe(parser);
143 await writeToPort('AT+CGSN\r'); 143 await writeToPort('AT+CGSN\r');
144 }); 144 });
145 } 145 }
146 exports.queryIMEI = queryIMEI; 146 exports.queryIMEI = queryIMEI;
147 147
148 function queryIMSI(lockName) { 148 function queryIMSI(lockName) {
149 return new Promise(async (resolve) => { 149 return new Promise(async (resolve) => {
150 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 150 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
151 parser.on('data', (data) => { 151 parser.on('data', (data) => {
152 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMSI' }); 152 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserIMSI' });
153 port.unpipe(parser); 153 port.unpipe(parser);
154 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMSI'); 154 mutex.unlock(lockName || MUTEX_COMMAND, 'queryIMSI');
155 modemInfo.imsi = data.toString().trim() || null; 155 modemInfo.imsi = data.toString().trim() || null;
156 logger.info('IMSI extracted', { imsi: modemInfo.imsi }); 156 logger.info('IMSI extracted', { imsi: modemInfo.imsi });
157 resolve(modemInfo.imsi); 157 resolve(modemInfo.imsi);
158 }); 158 });
159 159
160 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMSI'); 160 await mutex.lock(lockName || MUTEX_COMMAND, 'queryIMSI');
161 161
162 port.pipe(parser); 162 port.pipe(parser);
163 await writeToPort('AT+CIMI\r'); 163 await writeToPort('AT+CIMI\r');
164 }); 164 });
165 } 165 }
166 exports.queryIMSI = queryIMSI; 166 exports.queryIMSI = queryIMSI;
167 167
168 exports.queryIMEIAndIMSI = async function queryIMEIAndIMSI() { 168 exports.queryIMEIAndIMSI = async function queryIMEIAndIMSI() {
169 await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); 169 await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
170 170
171 const imei = await queryIMEI(MUTEX_SUBCOMMAND); 171 const imei = await queryIMEI(MUTEX_SUBCOMMAND);
172 const imsi = await queryIMSI(MUTEX_SUBCOMMAND); 172 const imsi = await queryIMSI(MUTEX_SUBCOMMAND);
173 173
174 await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); 174 await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI');
175 return { imei, imsi }; 175 return { imei, imsi };
176 }; 176 };
177 177
178 exports.queryManufacturer = function queryManufacturer(lockName) { 178 exports.queryManufacturer = function queryManufacturer(lockName) {
179 return new Promise(async (resolve) => { 179 return new Promise(async (resolve) => {
180 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 180 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
181 parser.on('data', (data) => { 181 parser.on('data', (data) => {
182 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserManufacturer' }); 182 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserManufacturer' });
183 port.unpipe(parser); 183 port.unpipe(parser);
184 mutex.unlock(lockName || MUTEX_COMMAND, 'parserManufacturer'); 184 mutex.unlock(lockName || MUTEX_COMMAND, 'parserManufacturer');
185 modemInfo.manufacturer = data.toString().trim() || null; 185 modemInfo.manufacturer = data.toString().trim() || null;
186 logger.info('Manufacturer extracted', { manufacturer: modemInfo.manufacturer }); 186 logger.info('Manufacturer extracted', { manufacturer: modemInfo.manufacturer });
187 resolve(modemInfo.manufacturer); 187 resolve(modemInfo.manufacturer);
188 }); 188 });
189 189
190 await mutex.lock(lockName || MUTEX_COMMAND, 'queryManufacturer'); 190 await mutex.lock(lockName || MUTEX_COMMAND, 'queryManufacturer');
191 191
192 port.pipe(parser); 192 port.pipe(parser);
193 await writeToPort('AT+CGMI\r'); 193 await writeToPort('AT+CGMI\r');
194 }); 194 });
195 }; 195 };
196 196
197 exports.queryModel = function queryModel(lockName) { 197 exports.queryModel = function queryModel(lockName) {
198 return new Promise(async (resolve) => { 198 return new Promise(async (resolve) => {
199 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 199 const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
200 parser.on('data', (data) => { 200 parser.on('data', (data) => {
201 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserModel' }); 201 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserModel' });
202 port.unpipe(parser); 202 port.unpipe(parser);
203 mutex.unlock(lockName || MUTEX_COMMAND, 'parserModel'); 203 mutex.unlock(lockName || MUTEX_COMMAND, 'parserModel');
204 modemInfo.model = data.toString().trim() || null; 204 modemInfo.model = data.toString().trim() || null;
205 logger.info('Model extracted', { model: modemInfo.model }); 205 logger.info('Model extracted', { model: modemInfo.model });
206 resolve(modemInfo.model); 206 resolve(modemInfo.model);
207 }); 207 });
208 208
209 await mutex.lock(lockName || MUTEX_COMMAND, 'queryModel'); 209 await mutex.lock(lockName || MUTEX_COMMAND, 'queryModel');
210 210
211 port.pipe(parser); 211 port.pipe(parser);
212 await writeToPort('AT+CGMM\r'); 212 await writeToPort('AT+CGMM\r');
213 }); 213 });
214 }; 214 };
215 215
216 async function sendCtrlZ() { 216 async function sendCtrlZ() {
217 await writeToPort(CTRLZ); 217 await writeToPort(CTRLZ);
218 } 218 }
219 exports.sendCtrlZ = sendCtrlZ; 219 exports.sendCtrlZ = sendCtrlZ;
220 220
221 async function initATCommands() { 221 async function initATCommands() {
222 await mutex.lock(MUTEX_COMMAND, 'INIT MODEM'); 222 await mutex.lock(MUTEX_COMMAND, 'INIT MODEM');
223 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND); 223 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND);
224 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); 224 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
225 await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND); 225 await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND);
226 mutex.unlock(MUTEX_COMMAND, 'INIT MODEM'); 226 mutex.unlock(MUTEX_COMMAND, 'INIT MODEM');
227 } 227 }
228 exports.initATCommands = initATCommands; 228 exports.initATCommands = initATCommands;
229 229
230 function sendCMGSPdu(pduLength) { 230 function sendCMGSPdu(pduLength) {
231 return new Promise((resolve) => { 231 return new Promise((resolve) => {
232 const parser = new ParserReady({ delimiter: '>' }); 232 const parser = new ParserReady({ delimiter: '>' });
233 parser.on('data', () => { 233 parser.on('data', () => {
234 logger.verbose('Got ">" message prompt, gonna to write PDU message'); 234 logger.verbose('Got ">" message prompt, gonna to write PDU message');
235 port.unpipe(parser); 235 port.unpipe(parser);
236 mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); 236 mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
237 resolve(true); 237 resolve(true);
238 }); 238 });
239 239
240 mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); 240 mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand');
241 port.pipe(parser); 241 port.pipe(parser);
242 writeToPort(`AT+CMGS=${pduLength}\r`); 242 writeToPort(`AT+CMGS=${pduLength}\r`);
243 }); 243 });
244 } 244 }
245 245
246 exports.sendSMS = function sendSMS(destination, msg) { 246 exports.sendSMS = function sendSMS(destination, msg) {
247 return new Promise(async (resolve) => { 247 return new Promise(async (resolve) => {
248 async function responseHandler(data) { 248 async function responseHandler(data) {
249 logger.verbose('SMS sent callback called', { data }); 249 logger.verbose('SMS sent callback called', { data });
250
251 if (data.indexOf('ERROR') >= 0 || data.indexOf('+CMS ERROR') >= 0 || data.indexOf('+CMGS') >= 0) {
252 logger.verbose('SMS sent');
253 parsers.setSmsSentCallback(null);
254 mutex.unlock(MUTEX_COMMAND, 'sendSMS');
255 resolve(data.indexOf('ERROR') >= 0 ? null : data.toString().trim());
256 }
250 } 257 }
251 258
252 await mutex.lock(MUTEX_COMMAND, 'sendSMS'); 259 await mutex.lock(MUTEX_COMMAND, 'sendSMS');
253 parsers.setSmsSentCallback(responseHandler);
254 260
255 if (!destination || !destination.trim()) { 261 if (!destination || !destination.trim()) {
256 resolve(false); 262 resolve(false);
257 return; 263 return;
258 } 264 }
259 265
260 if (!msg || !msg.trim()) { 266 if (!msg || !msg.trim()) {
261 resolve(false); 267 resolve(false);
262 return; 268 return;
263 } 269 }
264 270
265 const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+'); 271 const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+');
266 logger.verbose(`Sending sms to ${correctedDestination}`, { msg }); 272 logger.verbose(`Sending sms to ${correctedDestination}`, { msg });
267 273
268 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); 274 await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND);
269 275
270 const submit = pdu.Submit(); 276 const submit = pdu.Submit();
271 submit.setAddress(correctedDestination); 277 submit.setAddress(correctedDestination);
272 submit.setData(msg.trim()); 278 submit.setData(msg.trim());
273 submit.getType().setSrr(0); 279 submit.getType().setSrr(0);
274 280
275 await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1); 281 await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1);
276 await writeToPortAndWaitForOkOrError(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND); 282 // await writeToPortAndWaitForOkOrError(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);
277 283
278 mutex.unlock(MUTEX_COMMAND, 'sendSMS'); 284 parsers.setSmsSentCallback(responseHandler);
279 resolve(true); 285 await writeToPort(`${submit.toString()}${CTRLZ}`, MUTEX_SUBCOMMAND);
280 }); 286 });
281 }; 287 };
282 288
283 exports.executeUSSD = function executeUSSD(code, _includeCUSD2, _sessionId) { 289 exports.executeUSSD = function executeUSSD(code, _includeCUSD2, _sessionId) {
284 return new Promise(async (resolve) => { 290 return new Promise(async (resolve) => {
285 const includeCUSD2 = _includeCUSD2 || 0; 291 const includeCUSD2 = _includeCUSD2 || 0;
286 const sessionId = _sessionId || uuidv1(); 292 const sessionId = _sessionId || uuidv1();
287 293
288 async function responseHandler(data) { 294 async function responseHandler(data) {
289 logger.verbose('Processing USSD response', { data }); 295 logger.verbose('Processing USSD response', { data });
290 parsers.setUssdCallback(null); 296 parsers.setUssdCallback(null);
291 297
292 if (includeCUSD2 === 1 || includeCUSD2 === 2) { 298 if (includeCUSD2 === 1 || includeCUSD2 === 2) {
293 await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); 299 await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
294 } 300 }
295 301
296 mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); 302 mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
297 resolve(data); 303 resolve(data);
298 } 304 }
299 305
300 mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); 306 mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`);
301 parsers.setUssdCallback(responseHandler); 307 parsers.setUssdCallback(responseHandler);
302 308
303 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND); 309 await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND);
304 310
305 if (includeCUSD2 === -1 || includeCUSD2 === 2) { 311 if (includeCUSD2 === -1 || includeCUSD2 === 2) {
306 await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); 312 await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND);
307 } 313 }
308 314
309 await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND); 315 await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND);
310 }); 316 });
311 }; 317 };
lib/serialport-parsers.js
1 const PARSER_READLINE_DELIMITER = '\r\n'; 1 const PARSER_READLINE_DELIMITER = '\r\n';
2 const PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = /\n(?:OK|ERROR)\r\n/; 2 const PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = /\n(?:OK|ERROR)\r\n/;
3 3
4 const moment = require('moment'); 4 const moment = require('moment');
5 const nodePdu = require('node-pdu'); 5 const nodePdu = require('node-pdu');
6 // const pdu = require('pdu'); 6 // const pdu = require('pdu');
7 const ParserReadline = require('@serialport/parser-readline'); 7 const ParserReadline = require('@serialport/parser-readline');
8 const ParserRegex = require('@serialport/parser-regex'); 8 const ParserRegex = require('@serialport/parser-regex');
9 9
10 const logger = require('komodo-sdk/logger'); 10 const logger = require('komodo-sdk/logger');
11 11
12 const dbCops = require('./db-cops'); 12 const dbCops = require('./db-cops');
13 const modemInfo = require('./modem-info'); 13 const modemInfo = require('./modem-info');
14 14
15 let port; 15 let port;
16 16
17 exports.setPort = function setPort(val) { 17 exports.setPort = function setPort(val) {
18 logger.info('SERIALPORT-PARSERS: setting port'); 18 logger.info('SERIALPORT-PARSERS: setting port');
19 port = val; 19 port = val;
20 }; 20 };
21 21
22 exports.getPort = function getPort() { 22 exports.getPort = function getPort() {
23 return port; 23 return port;
24 }; 24 };
25 25
26 let ussdCallback = null; 26 let ussdCallback = null;
27 function setUssdCallback(cb) { 27 function setUssdCallback(cb) {
28 ussdCallback = cb; 28 ussdCallback = cb;
29 } 29 }
30 exports.setUssdCallback = setUssdCallback; 30 exports.setUssdCallback = setUssdCallback;
31 31
32 let smsSentCallback = null; 32 let smsSentCallback = null;
33 function setSmsSentCallback(cb) { 33 function setSmsSentCallback(cb) {
34 smsSentCallback = cb; 34 smsSentCallback = cb;
35 } 35 }
36 exports.setSmsSentCallback = setSmsSentCallback; 36 exports.setSmsSentCallback = setSmsSentCallback;
37 37
38 function isAlphaNumeric(str) { 38 function isAlphaNumeric(str) {
39 const len = str.length; 39 const len = str.length;
40 // eslint-disable-next-line no-plusplus 40 // eslint-disable-next-line no-plusplus
41 for (let i = 0; i < len; i++) { 41 for (let i = 0; i < len; i++) {
42 const code = str.charCodeAt(i); 42 const code = str.charCodeAt(i);
43 if (!(code > 47 && code < 58) // numeric (0-9) 43 if (!(code > 47 && code < 58) // numeric (0-9)
44 && !(code > 64 && code < 91) // upper alpha (A-Z) 44 && !(code > 64 && code < 91) // upper alpha (A-Z)
45 && !(code > 96 && code < 123)) { // lower alpha (a-z) 45 && !(code > 96 && code < 123)) { // lower alpha (a-z)
46 return false; 46 return false;
47 } 47 }
48 } 48 }
49 return true; 49 return true;
50 } 50 }
51 51
52 function parsePdu(_data) { 52 function parsePdu(_data) {
53 const data = _data && _data.toString().trim().toUpperCase(); 53 const data = _data && _data.toString().trim().toUpperCase();
54 54
55 if (!data) return null; 55 if (!data) return null;
56 if (!isAlphaNumeric(data)) return null; 56 if (!isAlphaNumeric(data)) return null;
57 57
58 try { 58 try {
59 const result = nodePdu.parse(data); 59 const result = nodePdu.parse(data);
60 return result; 60 return result;
61 } catch (e) { 61 } catch (e) {
62 return null; 62 return null;
63 } 63 }
64 } 64 }
65 65
66 function onCSQ(data) { 66 function onCSQ(data) {
67 const val = data.toString().trim().match(/\+CSQ:\s*(.*)/); 67 const val = data.toString().trim().match(/\+CSQ:\s*(.*)/);
68 if (!val || !val[1]) return null; 68 if (!val || !val[1]) return null;
69 69
70 const [, signalStrength] = val; 70 const [, signalStrength] = val;
71 71
72 logger.info('Signal quality extracted', { signalQuality: val[1] }); 72 logger.info('Signal quality extracted', { signalQuality: val[1] });
73 73
74 modemInfo.signalStrength = signalStrength; 74 modemInfo.signalStrength = signalStrength;
75 modemInfo.signalStrengthTs = new Date(); 75 modemInfo.signalStrengthTs = new Date();
76 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss'); 76 modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss');
77 77
78 return signalStrength; 78 return signalStrength;
79 } 79 }
80 80
81 function onPduDeliver(data, parsedData) { 81 function onPduDeliver(data, parsedData) {
82 const from = parsedData.getAddress && parsedData.getAddress().getPhone 82 const from = parsedData.getAddress && parsedData.getAddress().getPhone
83 ? parsedData.getAddress().getPhone() : null; 83 ? parsedData.getAddress().getPhone() : null;
84 84
85 const msg = parsedData.getData && parsedData.getData().getData 85 const msg = parsedData.getData && parsedData.getData().getData
86 ? parsedData.getData().getData() : null; 86 ? parsedData.getData().getData() : null;
87 87
88 const ts = new Date(parsedData.getScts().getIsoString()); 88 const ts = new Date(parsedData.getScts().getIsoString());
89 89
90 logger.verbose('PDU processed', { ts, from, msg }); 90 logger.verbose('PDU processed', { ts, from, msg });
91 return { from, msg }; 91 return { from, msg };
92 } 92 }
93 93
94 function onCOPS(data) { 94 function onCOPS(data) {
95 const val = data.toString().trim().match(/\+COPS:\s*(.*)/); 95 const val = data.toString().trim().match(/\+COPS:\s*(.*)/);
96 if (!val || !val[1]) return null; 96 if (!val || !val[1]) return null;
97 97
98 const cops = val[1]; 98 const cops = val[1];
99 99
100 if (!cops) return null; 100 if (!cops) return null;
101 const [mode, format, networkId] = cops.split(','); 101 const [mode, format, networkId] = cops.split(',');
102 const networkName = networkId ? dbCops[networkId] || networkId : null; 102 const networkName = networkId ? dbCops[networkId] || networkId : null;
103 103
104 logger.info('COPS extracted', { 104 logger.info('COPS extracted', {
105 cops, mode, format, networkId, networkName, 105 cops, mode, format, networkId, networkName,
106 }); 106 });
107 107
108 modemInfo.cops = cops; 108 modemInfo.cops = cops;
109 modemInfo.networkId = networkId || null; 109 modemInfo.networkId = networkId || null;
110 modemInfo.networkName = networkName || null; 110 modemInfo.networkName = networkName || null;
111 111
112 return { 112 return {
113 cops, mode, format, networkId, networkName, 113 cops, mode, format, networkId, networkName,
114 }; 114 };
115 } 115 }
116 116
117 117
118 function isResultCodeIs(data, resultCode) { 118 function isResultCodeIs(data, resultCode) {
119 if (!data) return false; 119 if (!data) return false;
120 const cleanedData = (data.toString() || '').trim(); 120 const cleanedData = (data.toString() || '').trim();
121 if (!cleanedData) return false; 121 if (!cleanedData) return false;
122 122
123 if (resultCode.indexOf('+') !== 0) { 123 if (resultCode.indexOf('+') !== 0) {
124 // eslint-disable-next-line no-param-reassign 124 // eslint-disable-next-line no-param-reassign
125 resultCode = `+${resultCode}`; 125 resultCode = `+${resultCode}`;
126 } 126 }
127 127
128 if (resultCode.search(/:$/) < 0) { 128 if (resultCode.search(/:$/) < 0) {
129 // eslint-disable-next-line no-param-reassign 129 // eslint-disable-next-line no-param-reassign
130 resultCode += ':'; 130 resultCode += ':';
131 } 131 }
132 132
133 return cleanedData.indexOf(resultCode) === 0; 133 return cleanedData.indexOf(resultCode) === 0;
134 } 134 }
135 135
136 const parserReadline = new ParserReadline({ delimiter: PARSER_READLINE_DELIMITER }); 136 const parserReadline = new ParserReadline({ delimiter: PARSER_READLINE_DELIMITER });
137 parserReadline.on('data', (data) => { 137 parserReadline.on('data', (data) => {
138 modemInfo.lastReadTs = new Date(); 138 modemInfo.lastReadTs = new Date();
139 logger.verbose('INCOMING', { data: `${data.toString()}${PARSER_READLINE_DELIMITER}`, parser: 'parserReadLine' }); 139 logger.verbose('INCOMING', { data: `${data.toString()}${PARSER_READLINE_DELIMITER}`, parser: 'parserReadLine' });
140 140
141 if (!data) return; 141 if (!data) return;
142 142
143 const pduParsed = parsePdu(data); 143 const pduParsed = parsePdu(data);
144 if (pduParsed && pduParsed.constructor.name !== 'Deliver') { 144 if (pduParsed && pduParsed.constructor.name !== 'Deliver') {
145 const pduType = pduParsed.getType(); 145 const pduType = pduParsed.getType();
146 logger.warn('WARN-9DA32C41: Unknown PDU message type name. PLEASE REPORT IT TO DEVELOPER AT TEKTRANS', { typeName: pduParsed.constructor.name, pduType, data: data.toString().trim() }); 146 logger.warn('WARN-9DA32C41: Unknown PDU message type name. PLEASE REPORT IT TO DEVELOPER AT TEKTRANS', { typeName: pduParsed.constructor.name, pduType, data: data.toString().trim() });
147 } 147 }
148 148
149 if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) { 149 if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) {
150 const pduType = pduParsed.getType(); 150 const pduType = pduParsed.getType();
151 logger.verbose('Got a PDU SMS-DELIVER', { pduType }); 151 logger.verbose('Got a PDU SMS-DELIVER', { pduType });
152 onPduDeliver(data, pduParsed); 152 onPduDeliver(data, pduParsed);
153 } else if (data.toString().trim() === 'ERROR') {
154 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
153 } else if (isResultCodeIs(data, 'CSQ')) { 155 } else if (isResultCodeIs(data, 'CSQ')) {
154 logger.verbose('Got a signal quality report', { data: data.toString() }); 156 logger.verbose('Got a signal quality report', { data: data.toString() });
155 onCSQ(data); 157 onCSQ(data);
156 } else if (isResultCodeIs(data, 'COPS:')) { 158 } else if (isResultCodeIs(data, 'COPS:')) {
157 logger.verbose('Got a COPS report', { data: data.toString() }); 159 logger.verbose('Got a COPS report', { data: data.toString() });
158 onCOPS(data); 160 onCOPS(data);
159 } else if (isResultCodeIs(data, 'CMT')) { 161 } else if (isResultCodeIs(data, 'CMT')) {
160 logger.verbose('Got a new message report', { data: data.toString() }); 162 logger.verbose('Got a new message report', { data: data.toString() });
161 } else if (isResultCodeIs(data, 'CMTI')) { 163 } else if (isResultCodeIs(data, 'CMTI')) {
162 logger.verbose('Got a new message notification report', { data: data.toString() }); 164 logger.verbose('Got a new message notification report', { data: data.toString() });
163 } else if (isResultCodeIs(data, 'CUSD')) { 165 } else if (isResultCodeIs(data, 'CUSD')) {
164 logger.verbose('Got a USSD command response', { data: data.toString() }); 166 logger.verbose('Got a USSD command response', { data: data.toString() });
165 if (typeof ussdCallback === 'function') { 167 if (typeof ussdCallback === 'function') {
166 logger.verbose('Calling USSD callback'); 168 logger.verbose('Calling USSD callback');
167 ussdCallback(data.toString()); 169 ussdCallback(data.toString());
168 } else { 170 } else {
169 logger.verbose('Skip unwanted USSD response'); 171 logger.verbose('Skip unwanted USSD response');
170 } 172 }
171 } else if (isResultCodeIs(data, 'CMGS')) { 173 } else if (isResultCodeIs(data, 'CMGS')) {
172 logger.verbose('Got CMGS report', { data: data.toString() }); 174 logger.verbose('Got CMGS report', { data: data.toString() });
173 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString()); 175 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
174 } else if (isResultCodeIs(data, 'CMS ERROR')) { 176 } else if (isResultCodeIs(data, 'CMS ERROR')) {
175 logger.verbose('Got CMS ERROR report', { data: data.toString() }); 177 logger.verbose('Got CMS ERROR report', { data: data.toString() });
176 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString()); 178 if (typeof smsSentCallback === 'function') smsSentCallback(data.toString());
177 } 179 }
178 }); 180 });
179 181
180 const parserWaitForOkOrError = new ParserRegex({ regex: PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); 182 const parserWaitForOkOrError = new ParserRegex({ regex: PARSER_WAIT_FOR_OK_OR_ERROR_REGEX });
181 parserWaitForOkOrError.on('data', (data) => { 183 parserWaitForOkOrError.on('data', (data) => {
182 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserWaitForOkOrError' }); 184 logger.verbose('INCOMING', { data: data.toString(), parser: 'parserWaitForOkOrError' });
183 }); 185 });
184 186
185 187
186 exports.PARSER_READLINE_DELIMITER = PARSER_READLINE_DELIMITER; 188 exports.PARSER_READLINE_DELIMITER = PARSER_READLINE_DELIMITER;
187 exports.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = PARSER_WAIT_FOR_OK_OR_ERROR_REGEX; 189 exports.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX = PARSER_WAIT_FOR_OK_OR_ERROR_REGEX;
188 190
189 exports.parserReadline = parserReadline; 191 exports.parserReadline = parserReadline;
190 exports.parserWaitForOkOrError = parserWaitForOkOrError; 192 exports.parserWaitForOkOrError = parserWaitForOkOrError;
191 193