-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b8d435b
Showing
9 changed files
with
998 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
node_modules | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# NodeJS GSM API | ||
|
||
A simple and easy to use NodeJS API to communicate with serial GSM Modems. | ||
|
||
## Features | ||
* Read device information: serial, manufacturer, model. | ||
* Read network information: Carrier, signal strength, subscriber id. | ||
* Read and send SMS Messages. | ||
* Add, Read and modify SIM card contacts. | ||
* Make calls (without audio). | ||
|
||
## Requirements | ||
* Access to a serial USB GSM Modem | ||
* Working SIM Card | ||
* NodeJS 11 and later | ||
|
||
## Usage | ||
```JavaScript | ||
const GSM = require("gsm") | ||
const gsm = new GSM("/dev/gsmmodem") | ||
await gsm.connect() | ||
|
||
let manufacturer = await gsm.getManufacturerInformation()) | ||
console.log(manufacturer) // QUALCOMM INCORPORATED | ||
|
||
let unreadMessages = await gsm.readSMS(GSM.MessageStorage.sim, GSM.MessageFilter.unread) | ||
console.log(unreadMessages) // List of unread SMS messages | ||
|
||
await gsm.sendSMS("+31111222333","Hello from NodeJS") | ||
``` | ||
|
||
## Dependencies | ||
* [serialport](https://www.npmjs.com/package/serialport) | ||
|
||
## Resources | ||
* [ | ||
Send and Receive SMS Messages Using Raspberry Pi and Python | ||
](https://hristoborisov.com/index.php/projects/turning-the-raspberry-pi-into-a-sms-center-using-python/) | ||
* [AT Commands Reference Guide ](https://www.sparkfun.com/datasheets/Cellular%20Modules/AT_Commands_Reference_Guide_r0.pdf) ([Local Copy](docs/AT_Commands_Reference_Guide_r0.pdf)) | ||
* [ | ||
Introduction to AT commands and its uses | ||
](https://www.codeproject.com/Articles/85636/Introduction-to-AT-commands-and-its-uses) | ||
|
||
## Testing the modem | ||
### Method 1: Using regular bash | ||
Terminal window 1 will read | ||
```bash | ||
$ cat /dev/gsmmodem | ||
|
||
OK | ||
|
||
Manufacturer: QUALCOMM INCORPORATED | ||
Model: +CGMM:HSPA MODEM | ||
Revision: +CGMR:V1.2 | ||
IMEI: 869478036086138 | ||
+GCAP: +CGSM,+DS,+ES | ||
|
||
OK | ||
``` | ||
|
||
Terminal window 2 will write | ||
```bash | ||
$ echo "AT" > /dev/gsmmodem | ||
$ echo "ATI" > /dev/gsmmodem | ||
``` | ||
|
||
### Method 2: Using cu | ||
```bash | ||
$ apt install cu | ||
$ cu -l /dev/gsmmodem | ||
|
||
AT | ||
|
||
OK | ||
|
||
ATI | ||
|
||
Manufacturer: QUALCOMM INCORPORATED | ||
Model: +CGMM:HSPA MODEM | ||
Revision: +CGMR:V1.2 | ||
IMEI: 869478036086138 | ||
+GCAP: +CGSM,+DS,+ES | ||
|
||
OK | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
const GSM = require('./gsm') | ||
const gsm = new GSM('/dev/gsmmodem') | ||
|
||
async function main() { | ||
|
||
// Connect | ||
console.log("Connecting to serial modem...") | ||
await gsm.connect() | ||
console.log("Connected to serial modem") | ||
await gsm.check() | ||
console.log("Modem OK\n") | ||
|
||
// Print modem info | ||
console.log("Modem Information:") | ||
console.log(` Manufacturer: ${await gsm.getManufacturerInformation()}`) | ||
console.log(` Model ID: ${await gsm.getModelIdentification()}`) | ||
console.log(` Version: ${await gsm.getRevisionIdentification()}`) | ||
console.log(` Serial: ${await gsm.getSerialNumber()}`) | ||
console.log("") | ||
|
||
// Print network info (Works only with a working SIM card and GSM signal) | ||
console.log("Network Information:") | ||
console.log(` Carrier: ${await gsm.getCurrentOperator()}`) | ||
console.log(` Signal: ${(await gsm.getSignalQuality()).description }`) | ||
console.log(` Subscriber ID: ${await gsm.getSubscriberId()}`) | ||
console.log(` Phone Number: ${await getPhoneNumber(gsm) || "Unknown"}`) | ||
|
||
// Polling on the list of unread messages and write all messages to stdout | ||
while(true) { | ||
let unreadMessages = await gsm.readSMS(GSM.MessageStorage.sim,GSM.MessageFilter.unread) | ||
console.log(`Unread messages: ${unreadMessages.length}`) | ||
if(unreadMessages.length > 0) { | ||
for(let msg of unreadMessages) { | ||
console.log(`From ${msg.sender} at ${msg.time.toISOString()}:`) | ||
console.log(`${msg.text}\n\n`) | ||
} | ||
await gsm.deleteAllMessages(GSM.MessageStorage.sim, GSM.MessageDeleteFilter.readSentAndUnsent) | ||
} | ||
await timeout(10000) | ||
} | ||
} | ||
|
||
async function getPhoneNumber(gsm) { | ||
try { | ||
return await gsm.getSubscriberNumber() | ||
} | ||
catch { | ||
// Some operators store the subscriber phone number in the own number phone book. But it may fail | ||
try { | ||
const result = await gsm.readPhoneBook(GSM.PhoneBookStorage.ownNumber,1,1) | ||
return result[0].number | ||
} | ||
catch { | ||
return undefined | ||
} | ||
} | ||
} | ||
|
||
const timeout = (ms) => { | ||
return new Promise(resolve => { | ||
setTimeout(resolve,ms) | ||
}) | ||
} | ||
|
||
main().then(() => console.log("Bye")).catch(console.error).finally(() => { gsm.disconnect() }) |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
ReturnCode = Object.freeze({ | ||
ok: "OK", | ||
connect: "CONNECT", | ||
ring: "RING", | ||
noCarrier: "NO CARRIER", | ||
error: "ERROR", | ||
noDialtone: "NO DIALTONE", | ||
busy: "BUSY", | ||
noAnswer: "NO ANSWER" | ||
}) | ||
|
||
ServiceClass = Object.freeze({ | ||
data: 0, | ||
fax: 1, | ||
voice: 8 | ||
}) | ||
|
||
CharacterSet = Object.freeze({ | ||
IRA: "IRA", | ||
GSM: "GSM", | ||
UCS2: "UCS2" | ||
}) | ||
|
||
PhoneBookStorage = Object.freeze({ | ||
sim: "SM", | ||
fixedDialing: "FD", | ||
dialedCalls: "DC", | ||
missedCalls: "MC", | ||
receivedCalls: "RC", | ||
ownNumber: "ON", | ||
mobileEquipment: "ME", | ||
emergencyNumbers: "EN", | ||
lastDialed: "LD" | ||
}) | ||
|
||
PhoneNumberType = Object.freeze({ | ||
national: 129, // national numbering scheme | ||
international: 145, // international numbering scheme (contains the character "+") | ||
text: 208 // Text based ex: 'Vodafone' | ||
}) | ||
|
||
MessageStorage = Object.freeze({ | ||
internal: "ME", | ||
sim: "SM", | ||
statusReport: "SR", | ||
all: "MT" | ||
}) | ||
|
||
MessageFormat = Object.freeze({ | ||
PDU: 0, | ||
text: 1 | ||
}) | ||
|
||
MessageFilter = Object.freeze({ | ||
unread: { pdu: 0, text: "REC UNREAD" }, | ||
read: { pdu: 1, text: "REC READ" }, | ||
storedUnsent: { pdu: 2, text: "STO UNSENT" }, | ||
storedSent: { pdu: 3, text: "STO SENT" }, | ||
all: { pdu: 4, text: "ALL" } | ||
}) | ||
|
||
MessageDeleteFilter = Object.freeze({ | ||
/** | ||
* Delete all read messages from storage, leaving unread messages and stored | ||
* mobile originated messages (whether sent or not) untouched | ||
*/ | ||
read: 1, | ||
|
||
/** | ||
* Delete all read messages from storage and sent mobile originated messages, | ||
* leaving unread messages and unsent mobile originated messages untouched | ||
*/ | ||
readAndSent: 2, | ||
|
||
/** | ||
* Delete all read messages from storage, sent and unsent mobile originated | ||
* messages, leaving unread messages untouched | ||
*/ | ||
readSentAndUnsent: 3, | ||
|
||
/** | ||
* Delete all message from storage | ||
*/ | ||
all: 4 | ||
}) | ||
|
||
module.exports = { | ||
ReturnCode, | ||
ServiceClass, | ||
CharacterSet, | ||
PhoneBookStorage, | ||
PhoneNumberType, | ||
MessageStorage, | ||
MessageFormat, | ||
MessageFilter, | ||
MessageDeleteFilter | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
|
||
/** | ||
* ME Error Result Codes | ||
*/ | ||
const MEError = Object.freeze({ | ||
0: "phone failure", | ||
1: "No connection to phone", | ||
2: "phone-adaptor link reserved", | ||
3: "operation not allowed", | ||
4: "operation not supported", | ||
5: "PH-SIM PIN required", | ||
10:"SIM not inserted", | ||
11: "SIM PIN required", | ||
12: "SIM PUK required", | ||
13: "SIM failure", | ||
14: "SIM busy", | ||
15: "SIM wrong", | ||
16: "incorrect password", | ||
17: "SIM PIN2 required", | ||
18: "SIM PUK2 required", | ||
20: "memory full", | ||
21: "invalid index", | ||
22: "not found", | ||
23: "memory failure", | ||
24: "text string too long", | ||
25: "invalid characters in text string", | ||
26: "dial string too long", | ||
27: "invalid characters in dial string", | ||
30: "no network service", | ||
31: "network timeout", | ||
32: "network not allowed - emergency calls only", | ||
40: "network personalization PIN required", | ||
41: "network personalization PUK required", | ||
42: "network subset personalization PIN required", | ||
43: "network subset personalization PUK required", | ||
44: "service provider personalization PIN required", | ||
45: "service provider personalization PUK required", | ||
46: "corporate personalization PIN required", | ||
47: "corporate personalization PUK require", | ||
|
||
// Easy CAMERA® related errors | ||
50: "Camera not found", | ||
51: "Camera Initialization Error", | ||
52: "Camera not Supported", | ||
53: "No Photo Taken", | ||
54: "NET BUSY...Camera TimeOut", | ||
55: "Camera Error", | ||
|
||
// General purpose error | ||
100: "Unknown error", | ||
|
||
// GPRS related errors to a failure to perform an Attach: | ||
103: "Illegal MS (#3)*", | ||
106: "Illegal ME (#6)*", | ||
107: "GPRS service not allowed (#7)*", | ||
111: "PLMN not allowed (#11)*", | ||
112: "Location area not allowed (#12)*", | ||
113: "Roaming not allowed in this location area (#13)*", | ||
|
||
// GPRS related errors to a failure to Activate a Context and others: | ||
132: "service option not supported (#32)*", | ||
133: "requested service option not subscribed (#33)*", | ||
134: "service option temporarily out of order (#34)*", | ||
148: "unspecified GPRS error", | ||
149: "PDP authentication failure", | ||
150: "invalid mobile class", | ||
|
||
// Network survey errors: | ||
257: "Network survey error (No Carrier)*", | ||
258: "Network survey error (Busy)*", | ||
259: "Network survey error (Wrong request)*", | ||
260: "Network survey error (Aborted)* ", | ||
|
||
// Easy GPRS® related errors: | ||
400: "generic undocumented error", | ||
401: "wrong state", | ||
402: "wrong mode", | ||
403: "context already activated", | ||
404: "stack already active", | ||
405: "activation failed", | ||
406: "context not opened", | ||
407: "cannot setup socket", | ||
408: "cannot resolve DN", | ||
409: "timeout in opening socket", | ||
410: "cannot open socket", | ||
411: "remote disconnected or timeout", | ||
412: "connection failed", | ||
413: "tx error", | ||
414: "already listening", | ||
|
||
// FTP related errors: | ||
420: "ok", | ||
421: "connect", | ||
422: "disconnect", | ||
423: "error", | ||
424: "wrong state", | ||
425: "can not activate", | ||
426: "can not resolve name", | ||
427: "can not allocate control socket", | ||
428: "can not connect control socket", | ||
429: "bad or no response from server", | ||
430: "not connected", | ||
431: "already connected", | ||
432: "context down", | ||
433: "no photo available", | ||
434: "can not send photo" | ||
}) | ||
|
||
/** | ||
* Message Service Failure Result Codes | ||
*/ | ||
const MSError = Object.freeze({ | ||
300: "ME failure", | ||
301: "SMS service of ME reserved", | ||
302: "operation not allowed", | ||
303: "operation not supported", | ||
304: "invalid PDU mode parameter", | ||
305: "invalid text mode parameter", | ||
310: "SIM not inserted", | ||
311: "SIM PIN required", | ||
312: "PH-SIM PIN required", | ||
313: "SIM failure", | ||
314: "SIM busy", | ||
315: "SIM wrong", | ||
316: "SIM PUK required", | ||
317: "SIM PIN2 required", | ||
318: "SIM PUK2 required", | ||
320: "memory failure", | ||
321: "invalid memory index", | ||
322: "memory full", | ||
330: "SMSC address unknown", | ||
331: "no network service", | ||
332: "network timeout", | ||
340: "no +CNMA acknowledgement expected", | ||
500: "Unknown error", | ||
}) | ||
|
||
module.exports = { MEError, MSError } |
Oops, something went wrong.