Commit 0ea228ab658abc5acbb5ce20c5a799de4ee67d1f
1 parent
ea7f35320e
Exists in
master
sms and ussd
Showing 6 changed files with 180 additions and 46 deletions Side-by-side Diff
lib/modem-commands/index.js
... | ... | @@ -4,10 +4,14 @@ exports.MUTEX_COMMAND = MUTEX_COMMAND; |
4 | 4 | const MUTEX_SUBCOMMAND = 'SUBCOMMAND'; |
5 | 5 | exports.MUTEX_SUBCOMMAND = MUTEX_SUBCOMMAND; |
6 | 6 | |
7 | +const CTRLZ = '\u001a'; | |
8 | + | |
7 | 9 | const pdu = require('node-pdu'); |
10 | +const uuidv1 = require('uuid/v1'); | |
8 | 11 | |
9 | 12 | const ParserReadline = require('@serialport/parser-readline'); |
10 | 13 | const ParserRegex = require('@serialport/parser-regex'); |
14 | +const ParserReady = require('@serialport/parser-ready'); | |
11 | 15 | |
12 | 16 | const logger = require('komodo-sdk/logger'); |
13 | 17 | const mutex = require('../mutex-common'); |
... | ... | @@ -27,8 +31,9 @@ function writeToPort(data) { |
27 | 31 | }); |
28 | 32 | }); |
29 | 33 | } |
34 | +exports.writeToPort = writeToPort; | |
30 | 35 | |
31 | -exports.writeToPortAndWaitForReadline = function writeToPortAndWaitForReadline(cmd, lockName) { | |
36 | +function writeToPortAndWaitForReadline(cmd, lockName) { | |
32 | 37 | let resolved = false; |
33 | 38 | |
34 | 39 | return new Promise(async (resolve) => { |
... | ... | @@ -46,9 +51,10 @@ exports.writeToPortAndWaitForReadline = function writeToPortAndWaitForReadline(c |
46 | 51 | port.pipe(parser); |
47 | 52 | await writeToPort(cmd); |
48 | 53 | }); |
49 | -}; | |
54 | +} | |
55 | +exports.writeToPortAndWaitForReadline = writeToPortAndWaitForReadline; | |
50 | 56 | |
51 | -exports.writeToPortAndWaitForOkOrError = function writeToPortAndWaitForOkOrError(cmd, lockName) { | |
57 | +function writeToPortAndWaitForOkOrError(cmd, lockName) { | |
52 | 58 | return new Promise(async (resolve) => { |
53 | 59 | const parser = new ParserRegex({ regex: /(?:OK|ERROR)\r\n/ }); |
54 | 60 | parser.on('data', (data) => { |
... | ... | @@ -61,22 +67,23 @@ exports.writeToPortAndWaitForOkOrError = function writeToPortAndWaitForOkOrError |
61 | 67 | port.pipe(parser); |
62 | 68 | await writeToPort(cmd); |
63 | 69 | }); |
64 | -}; | |
70 | +} | |
71 | +exports.writeToPortAndWaitForOkOrError = writeToPortAndWaitForOkOrError; | |
65 | 72 | |
66 | -exports.sleep = function sleep(ms) { | |
73 | +function sleep(ms) { | |
67 | 74 | return new Promise((resolve) => { |
68 | 75 | setTimeout(() => { |
69 | 76 | resolve(); |
70 | 77 | }, ms || 0); |
71 | 78 | }); |
72 | -}; | |
73 | - | |
79 | +} | |
80 | +exports.sleep = sleep; | |
74 | 81 | |
75 | 82 | exports.setPort = function setPort(val) { |
76 | 83 | port = val; |
77 | 84 | }; |
78 | 85 | |
79 | -exports.querySignalQuality = function querySignalQuality() { | |
86 | +function querySignalQuality() { | |
80 | 87 | return new Promise(async (resolve) => { |
81 | 88 | if (!mutex.tryLock(MUTEX_COMMAND, 'querySignalQuality')) { |
82 | 89 | resolve(false); |
... | ... | @@ -87,18 +94,20 @@ exports.querySignalQuality = function querySignalQuality() { |
87 | 94 | mutex.unlock(MUTEX_COMMAND, 'querySignalQuality'); |
88 | 95 | resolve(true); |
89 | 96 | }); |
90 | -}; | |
97 | +} | |
98 | +exports.querySignalQuality = querySignalQuality; | |
91 | 99 | |
92 | -exports.queryCOPS = function queryCOPS(lockName) { | |
100 | +function queryCOPS(lockName) { | |
93 | 101 | return new Promise(async (resolve) => { |
94 | 102 | await mutex.lock(lockName || MUTEX_COMMAND, 'queryCOPS'); |
95 | 103 | await writeToPort('AT+COPS?\r'); |
96 | 104 | mutex.unlock(lockName || MUTEX_COMMAND, 'queryCOPS'); |
97 | 105 | resolve(true); |
98 | 106 | }); |
99 | -}; | |
107 | +} | |
108 | +exports.queryCOPS = queryCOPS; | |
100 | 109 | |
101 | -exports.queryCOPSAndSignalQuality = function queryCOPSAndSignalQuality(skipOnLocked) { | |
110 | +function queryCOPSAndSignalQuality(skipOnLocked) { | |
102 | 111 | return new Promise(async (resolve) => { |
103 | 112 | if (!skipOnLocked) { |
104 | 113 | await mutex.lock(MUTEX_COMMAND); |
... | ... | @@ -107,15 +116,16 @@ exports.queryCOPSAndSignalQuality = function queryCOPSAndSignalQuality(skipOnLoc |
107 | 116 | return; |
108 | 117 | } |
109 | 118 | |
110 | - await this.writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND); | |
111 | - await this.writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND); | |
119 | + await writeToPortAndWaitForOkOrError('AT+COPS?\r', MUTEX_SUBCOMMAND); | |
120 | + await writeToPortAndWaitForOkOrError('AT+CSQ\r', MUTEX_SUBCOMMAND); | |
112 | 121 | |
113 | 122 | mutex.unlock(MUTEX_COMMAND, 'queryCopsAndSignalQuality'); |
114 | 123 | resolve(true); |
115 | 124 | }); |
116 | -}; | |
125 | +} | |
126 | +exports.queryCOPSAndSignalQuality = queryCOPSAndSignalQuality; | |
117 | 127 | |
118 | -exports.queryIMEI = function queryIMEI(lockName) { | |
128 | +function queryIMEI(lockName) { | |
119 | 129 | return new Promise(async (resolve) => { |
120 | 130 | const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); |
121 | 131 | parser.on('data', (data) => { |
... | ... | @@ -132,9 +142,10 @@ exports.queryIMEI = function queryIMEI(lockName) { |
132 | 142 | port.pipe(parser); |
133 | 143 | await writeToPort('AT+CGSN\r'); |
134 | 144 | }); |
135 | -}; | |
145 | +} | |
146 | +exports.queryIMEI = queryIMEI; | |
136 | 147 | |
137 | -exports.queryIMSI = function queryIMSI(lockName) { | |
148 | +function queryIMSI(lockName) { | |
138 | 149 | return new Promise(async (resolve) => { |
139 | 150 | const parser = new ParserRegex({ regex: parsers.PARSER_WAIT_FOR_OK_OR_ERROR_REGEX }); |
140 | 151 | parser.on('data', (data) => { |
... | ... | @@ -151,13 +162,14 @@ exports.queryIMSI = function queryIMSI(lockName) { |
151 | 162 | port.pipe(parser); |
152 | 163 | await writeToPort('AT+CIMI\r'); |
153 | 164 | }); |
154 | -}; | |
165 | +} | |
166 | +exports.queryIMSI = queryIMSI; | |
155 | 167 | |
156 | 168 | exports.queryIMEIAndIMSI = async function queryIMEIAndIMSI() { |
157 | 169 | await mutex.lock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); |
158 | 170 | |
159 | - const imei = await this.queryIMEI(MUTEX_SUBCOMMAND); | |
160 | - const imsi = await this.queryIMSI(MUTEX_SUBCOMMAND); | |
171 | + const imei = await queryIMEI(MUTEX_SUBCOMMAND); | |
172 | + const imsi = await queryIMSI(MUTEX_SUBCOMMAND); | |
161 | 173 | |
162 | 174 | await mutex.unlock(MUTEX_COMMAND, 'queryIMEIAndIMSI'); |
163 | 175 | return { imei, imsi }; |
... | ... | @@ -201,16 +213,94 @@ exports.queryModel = function queryModel(lockName) { |
201 | 213 | }); |
202 | 214 | }; |
203 | 215 | |
204 | -exports.sendSMS = function sendSMS(destination, msg) { | |
205 | - return new Promise(async (resolve) => { | |
206 | - | |
207 | - }); | |
208 | -}; | |
216 | +async function sendCtrlZ() { | |
217 | + await writeToPort(CTRLZ); | |
218 | +} | |
219 | +exports.sendCtrlZ = sendCtrlZ; | |
209 | 220 | |
210 | -exports.initATCommands = async function initATCommands() { | |
221 | +async function initATCommands() { | |
211 | 222 | await mutex.lock(MUTEX_COMMAND, 'INIT MODEM'); |
212 | - await this.writeToPortAndWaitForOkOrError('\rATE0\r', MUTEX_SUBCOMMAND); | |
223 | + await this.writeToPortAndWaitForOkOrError(`${CTRLZ}ATE0\r`, MUTEX_SUBCOMMAND); | |
213 | 224 | await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); |
214 | 225 | await this.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r', MUTEX_SUBCOMMAND); |
215 | 226 | mutex.unlock(MUTEX_COMMAND, 'INIT MODEM'); |
227 | +} | |
228 | +exports.initATCommands = initATCommands; | |
229 | + | |
230 | +function sendCMGSPdu(pduLength) { | |
231 | + return new Promise((resolve) => { | |
232 | + const parser = new ParserReady({ delimiter: '>' }); | |
233 | + parser.on('data', () => { | |
234 | + logger.verbose('Got ">" message prompt, gonna to write PDU message'); | |
235 | + port.unpipe(parser); | |
236 | + mutex.unlock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); | |
237 | + resolve(true); | |
238 | + }); | |
239 | + | |
240 | + mutex.lock(MUTEX_SUBCOMMAND, 'sendSmsPduCommand'); | |
241 | + port.pipe(parser); | |
242 | + writeToPort(`AT+CMGS=${pduLength}\r`); | |
243 | + }); | |
244 | +} | |
245 | + | |
246 | +exports.sendSMS = function sendSMS(destination, msg) { | |
247 | + return new Promise(async (resolve) => { | |
248 | + await mutex.lock(MUTEX_COMMAND, 'sendSMS'); | |
249 | + | |
250 | + if (!destination || !destination.trim()) { | |
251 | + resolve(false); | |
252 | + return; | |
253 | + } | |
254 | + | |
255 | + if (!msg || !msg.trim()) { | |
256 | + resolve(false); | |
257 | + return; | |
258 | + } | |
259 | + | |
260 | + const correctedDestination = `+${destination.replace(/^0/, '62')}`.replace(/^\++/, '+'); | |
261 | + logger.verbose(`Sending sms to ${correctedDestination}`, { msg }); | |
262 | + | |
263 | + await this.writeToPortAndWaitForOkOrError('AT+CMGF=0\r', MUTEX_SUBCOMMAND); | |
264 | + | |
265 | + const submit = pdu.Submit(); | |
266 | + submit.setAddress(correctedDestination); | |
267 | + submit.setData(msg.trim()); | |
268 | + submit.getType().setSrr(0); | |
269 | + | |
270 | + await sendCMGSPdu(Math.floor(submit.toString().length / 2) - 1); | |
271 | + await writeToPort(`${submit.toString()}${CTRLZ}`); | |
272 | + | |
273 | + mutex.unlock(MUTEX_COMMAND, 'sendSMS'); | |
274 | + resolve(true); | |
275 | + }); | |
276 | +}; | |
277 | + | |
278 | +exports.executeUSSD = function executeUSSD(code, _includeCUSD2, _sessionId) { | |
279 | + return new Promise(async (resolve) => { | |
280 | + const includeCUSD2 = _includeCUSD2 || 0; | |
281 | + const sessionId = _sessionId || uuidv1(); | |
282 | + | |
283 | + async function responseHandler(data) { | |
284 | + logger.verbose('Processing USSD response', { data }); | |
285 | + parsers.setUssdCallback(null); | |
286 | + | |
287 | + if (includeCUSD2 === 1 || includeCUSD2 === 2) { | |
288 | + await writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); | |
289 | + } | |
290 | + | |
291 | + mutex.unlock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); | |
292 | + resolve(data); | |
293 | + } | |
294 | + | |
295 | + mutex.lock(MUTEX_COMMAND, `executeUSSD ${sessionId}`); | |
296 | + parsers.setUssdCallback(responseHandler); | |
297 | + | |
298 | + await this.writeToPortAndWaitForOkOrError(`${CTRLZ}AT+CMGF=0\r`, MUTEX_SUBCOMMAND); | |
299 | + | |
300 | + if (includeCUSD2 === -1 || includeCUSD2 === 2) { | |
301 | + await this.writeToPortAndWaitForOkOrError('AT+CUSD=2\r', MUTEX_SUBCOMMAND); | |
302 | + } | |
303 | + | |
304 | + await writeToPort(`AT+CUSD=1,"${code}",15\r`, MUTEX_SUBCOMMAND); | |
305 | + }); | |
216 | 306 | }; |
lib/modem-info.js
1 | -const MAX_LAST_DATA_AGE_MS = 2 * 60 * 1000; | |
2 | -const INTERVAL_BETWEEN_IDLE_CHECK_MS = 30 * 1000; | |
1 | +const DEFAULT_MAX_LAST_DATA_AGE_MS = 2 * 60 * 1000; | |
2 | +const DEFAULT_INTERVAL_BETWEEN_IDLE_CHECK_MS = 30 * 1000; | |
3 | 3 | |
4 | 4 | const config = require('komodo-sdk/config'); |
5 | 5 | const logger = require('komodo-sdk/logger'); |
... | ... | @@ -24,11 +24,11 @@ const modemInfo = { |
24 | 24 | if (!config.disable_idle_check) { |
25 | 25 | setInterval(() => { |
26 | 26 | const deltaMs = new Date() - Math.max(modemInfo.lastWriteTs, modemInfo.startTime); |
27 | - if (deltaMs >= (config.max_last_data_age_ms || MAX_LAST_DATA_AGE_MS)) { | |
27 | + if (deltaMs >= (config.max_last_data_age_ms || DEFAULT_MAX_LAST_DATA_AGE_MS)) { | |
28 | 28 | logger.warn(`Modem idle for ${deltaMs} ms. Modem stucked? Terminating!`); |
29 | 29 | process.exit(1); |
30 | 30 | } |
31 | - }, INTERVAL_BETWEEN_IDLE_CHECK_MS); | |
31 | + }, config.interval_beetwen_signal_strength_ms || DEFAULT_INTERVAL_BETWEEN_IDLE_CHECK_MS); | |
32 | 32 | } |
33 | 33 | |
34 | 34 | module.exports = modemInfo; |
lib/serialport-parsers.js
... | ... | @@ -23,11 +23,34 @@ exports.getPort = function getPort() { |
23 | 23 | return port; |
24 | 24 | }; |
25 | 25 | |
26 | -function parsePdu(data) { | |
26 | +let ussdCallback = null; | |
27 | +function setUssdCallback(cb) { | |
28 | + ussdCallback = cb; | |
29 | +} | |
30 | +exports.setUssdCallback = setUssdCallback; | |
31 | + | |
32 | +function isAlphaNumeric(str) { | |
33 | + const len = str.length; | |
34 | + // eslint-disable-next-line no-plusplus | |
35 | + for (let i = 0; i < len; i++) { | |
36 | + const code = str.charCodeAt(i); | |
37 | + if (!(code > 47 && code < 58) // numeric (0-9) | |
38 | + && !(code > 64 && code < 91) // upper alpha (A-Z) | |
39 | + && !(code > 96 && code < 123)) { // lower alpha (a-z) | |
40 | + return false; | |
41 | + } | |
42 | + } | |
43 | + return true; | |
44 | +} | |
45 | + | |
46 | +function parsePdu(_data) { | |
47 | + const data = _data && _data.toString().trim().toUpperCase(); | |
48 | + | |
27 | 49 | if (!data) return null; |
50 | + if (!isAlphaNumeric(data)) return null; | |
28 | 51 | |
29 | 52 | try { |
30 | - const result = nodePdu.parse(data.toString().trim() || ''); | |
53 | + const result = nodePdu.parse(data); | |
31 | 54 | return result; |
32 | 55 | } catch (e) { |
33 | 56 | return null; |
... | ... | @@ -112,16 +135,14 @@ parserReadline.on('data', (data) => { |
112 | 135 | if (!data) return; |
113 | 136 | |
114 | 137 | const pduParsed = parsePdu(data); |
115 | - if (pduParsed) { | |
116 | - logger.verbose('PDU parsed', { type: (typeof pduParsed.getType === 'function') && pduParsed.getType() }); | |
117 | - } | |
118 | - | |
119 | 138 | if (pduParsed && pduParsed.constructor.name !== 'Deliver') { |
120 | - logger.warn('WARN-9DA32C41: Unknown PDU message type name. PLEASE REPORT IT TO DEVELOPER AT TEKTRANS', { typeName: pduParsed.constructor.name, type: pduParsed.getType(), data: data.toString().trim() }); | |
139 | + const pduType = pduParsed.getType(); | |
140 | + 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() }); | |
121 | 141 | } |
122 | 142 | |
123 | 143 | if (pduParsed && pduParsed.constructor.name === 'Deliver' && pduParsed.getData().getSize()) { |
124 | - logger.verbose('Got a PDU SMS-DELIVER', { type: pduParsed.getType() }); | |
144 | + const pduType = pduParsed.getType(); | |
145 | + logger.verbose('Got a PDU SMS-DELIVER', { pduType }); | |
125 | 146 | onPduDeliver(data, pduParsed); |
126 | 147 | } else if (isResultCodeIs(data, 'CSQ')) { |
127 | 148 | logger.verbose('Got a signal quality report', { data: data.toString() }); |
... | ... | @@ -133,6 +154,14 @@ parserReadline.on('data', (data) => { |
133 | 154 | logger.verbose('Got a new message report', { data: data.toString() }); |
134 | 155 | } else if (isResultCodeIs(data, 'CMTI')) { |
135 | 156 | logger.verbose('Got a new message notification report', { data: data.toString() }); |
157 | + } else if (isResultCodeIs(data, 'CUSD')) { | |
158 | + logger.verbose('Got a USSD command response', { data: data.toString() }); | |
159 | + if (ussdCallback && typeof ussdCallback === 'function') { | |
160 | + logger.verbose('Calling USSD callback'); | |
161 | + ussdCallback(data.toString()); | |
162 | + } else { | |
163 | + logger.verbose('Skip unwanted USSD response'); | |
164 | + } | |
136 | 165 | } |
137 | 166 | }); |
138 | 167 |
modem-tester.js
... | ... | @@ -20,7 +20,8 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er |
20 | 20 | process.exit(1); |
21 | 21 | } |
22 | 22 | |
23 | - await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r'); | |
23 | + await modemCommands.sendCtrlZ(); | |
24 | + // await modemCommands.writeToPortAndWaitForOkOrError('\rAT\r'); | |
24 | 25 | await modemCommands.writeToPortAndWaitForOkOrError('AT&FE0\r'); |
25 | 26 | await modemCommands.writeToPortAndWaitForOkOrError('AT+CMGF=0\r'); |
26 | 27 | await modemCommands.writeToPortAndWaitForOkOrError('AT+CNMI=1,2,0,1,0\r'); |
... | ... | @@ -32,6 +33,12 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er |
32 | 33 | await modemCommands.queryCOPSAndSignalQuality(); |
33 | 34 | logger.info('Modem state', modemInfo); |
34 | 35 | |
36 | + // await modemCommands.sendSMS('628128364883', `coba pakai pdu ${new Date()}`); | |
37 | + // await modemCommands.sendSMS('+6282210008543', `coba pakai pdu ${new Date()}`); | |
38 | + // await modemCommands.sendSMS('6281809903333', `coba pakai pdu ${new Date()}`); | |
39 | + // const ussdResponse = await modemCommands.executeUSSD('*888#', 2); | |
40 | + // logger.info('USSD RESPONSE', { ussdResponse }); | |
41 | + | |
35 | 42 | setInterval(async () => { |
36 | 43 | await modemCommands.initATCommands(); |
37 | 44 | await modemCommands.queryManufacturer(); |
... | ... | @@ -39,7 +46,7 @@ const port = new SerialPort(config.modem.device, { baudRate: 115200 }, async (er |
39 | 46 | await modemCommands.queryIMEIAndIMSI(); |
40 | 47 | await modemCommands.queryCOPSAndSignalQuality(); |
41 | 48 | logger.info('Modem state', modemInfo); |
42 | - }, (config && config.interval_beetwen_signal_strength_ms) || 30000); | |
49 | + }, config.interval_beetwen_signal_strength_ms || 30000); | |
43 | 50 | }); |
44 | 51 | |
45 | 52 | global.MODEM_PORT = port; |
package-lock.json
... | ... | @@ -4155,6 +4155,11 @@ |
4155 | 4155 | "is-fullwidth-code-point": "^2.0.0" |
4156 | 4156 | } |
4157 | 4157 | }, |
4158 | + "sms-pdu-node": { | |
4159 | + "version": "0.1.2", | |
4160 | + "resolved": "https://registry.npmjs.org/sms-pdu-node/-/sms-pdu-node-0.1.2.tgz", | |
4161 | + "integrity": "sha1-lyaFkBVvBYdOBNN4NpIZ6FlfhdM=" | |
4162 | + }, | |
4158 | 4163 | "snapdragon": { |
4159 | 4164 | "version": "0.8.2", |
4160 | 4165 | "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", |
... | ... | @@ -4844,9 +4849,9 @@ |
4844 | 4849 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" |
4845 | 4850 | }, |
4846 | 4851 | "uuid": { |
4847 | - "version": "3.3.2", | |
4848 | - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", | |
4849 | - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" | |
4852 | + "version": "3.3.3", | |
4853 | + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", | |
4854 | + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" | |
4850 | 4855 | }, |
4851 | 4856 | "validate-npm-package-license": { |
4852 | 4857 | "version": "3.0.4", |
package.json
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 | "@serialport/parser-delimiter": "^2.0.2", |
30 | 30 | "@serialport/parser-inter-byte-timeout": "^1.1.0", |
31 | 31 | "@serialport/parser-readline": "^2.0.2", |
32 | + "@serialport/parser-ready": "^2.0.2", | |
32 | 33 | "@serialport/parser-regex": "^2.0.2", |
33 | 34 | "express": "^4.17.1", |
34 | 35 | "komodo-sdk": "git+http://gitlab.kodesumber.com/komodo/komodo-sdk.git", |
... | ... | @@ -38,6 +39,8 @@ |
38 | 39 | "pdu": "^1.1.0", |
39 | 40 | "request": "^2.88.0", |
40 | 41 | "serialport": "^7.1.5", |
41 | - "serialport-gsm": "^3.2.0" | |
42 | + "serialport-gsm": "^3.2.0", | |
43 | + "sms-pdu-node": "^0.1.2", | |
44 | + "uuid": "^3.3.3" | |
42 | 45 | } |
43 | 46 | } |