Commit beba81d88625590172223aff46fb008136030ad4
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 |