sim800
Version:
A modern and opiniated module for SIM800 GSM modems ( SIM800 / SIM800L ).
299 lines • 14.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sim800Client = void 0;
const rxjs_1 = require("rxjs");
const serialport_1 = require("serialport");
const stream_1 = __importDefault(require("stream"));
const at_command_1 = require("./classes/at-command");
const cmgda_command_1 = require("./classes/cmgda-command");
const cmgf_command_1 = require("./classes/cmgf-command");
const cmgl_command_1 = require("./classes/cmgl-command");
const cnmi_command_1 = require("./classes/cnmi-command");
const cpin_status_command_1 = require("./classes/cpin-status-command");
const creg_status_command_1 = require("./classes/creg-status-command");
const pin_unlock_command_1 = require("./classes/pin-unlock-command");
const sim800_sms_1 = require("./classes/sim800-sms");
const sim800_data_handler_1 = require("./handlers/sim800.data.handler");
const sim800_error_handler_1 = require("./handlers/sim800.error.handler");
const sim800_open_handler_1 = require("./handlers/sim800.open.handler");
const attach_serial_listeners_1 = require("./helpers/attach-serial-listeners");
const sim800_client_state_enum_1 = require("./interfaces/sim800-client-state.enum");
const sim800_command_enums_1 = require("./interfaces/sim800-command.enums");
const sim800_pin_state_enum_1 = require("./interfaces/sim800-pin-state.enum");
const completed_command_subscriber_1 = require("./subscribers/completed-command-subscriber");
const delivery_report_input_subscriber_1 = require("./subscribers/delivery-report-input-subscriber");
const delivery_report_stream_subscriber_1 = require("./subscribers/delivery-report-stream-subscriber");
const new_sms_subscriber_1 = require("./subscribers/new-sms-subscriber");
const sms_stream_subscriber_1 = require("./subscribers/sms-stream-subscriber");
const interfaces_1 = require("./interfaces");
class Sim800Client {
constructor({ port, baudRate, delimiter, logger, pin, preventWipe, noInit }) {
this.eventEmitter = new stream_1.default();
this.nextJob$ = new rxjs_1.Subject();
this.buffer = [];
this.cnmi = '2,1,2,1,0';
this.ready$ = new rxjs_1.AsyncSubject();
this.network$ = new rxjs_1.AsyncSubject();
this.smsBusy$ = new rxjs_1.AsyncSubject();
this.stream$ = new rxjs_1.Subject();
this.inputStream$ = new rxjs_1.Subject();
this.smsStream$ = new rxjs_1.Subject();
this.deliveryReportStream$ = new rxjs_1.Subject();
this.smsQueuePing$ = new rxjs_1.Subject();
this.state = sim800_client_state_enum_1.Sim800ClientState.Idle;
this.incomingSms = [];
this.outboxSpooler = [];
this.receivingDeliveryReport = false;
this.smsQueue = [];
this.handleInputData = (data) => {
const bufferCopy = [...this.buffer];
// Copying buffer to prevent side effects of a command being removed from the buffer while the function is running
// Is data part of command?
if (!((bufferCopy.length && bufferCopy[0].isDataPartOfRunningCommand(data)) || data === 'OK' || data === 'ERROR')) {
this.eventEmitter.emit('input', data);
this.inputStream$.next(data);
}
};
this.port = port;
this.preventWipe = preventWipe || false;
this.pin = pin;
this.noInit = noInit || false;
this.baudRate = baudRate || 115200;
this.delimiter = delimiter || '\r\n';
this.logger = logger || console;
this.parser = new serialport_1.ReadlineParser({
delimiter: this.delimiter,
});
this.serial = new serialport_1.SerialPort({ path: this.port, baudRate: this.baudRate });
this.serial.pipe(this.parser);
(0, attach_serial_listeners_1.attachSerialListeners)(this.serial, this.parser, {
open: () => {
this.eventEmitter.emit('deviceReady');
(0, sim800_open_handler_1.sim800OpenHandler)(this.ready$);
},
data: (data) => (0, sim800_data_handler_1.sim800DataHandler)(data, this.stream$),
error: (error) => (0, sim800_error_handler_1.sim800ErrorHandler)(error, logger),
});
this.nextJob$.subscribe(async () => {
await this.awaitDevice();
const command = this.buffer[0];
command.send(this.stream$, this.serial);
});
this.stream$.subscribe(this.handleInputData);
this.inputStream$.subscribe((0, new_sms_subscriber_1.newSmsSubscriberFactory)(this, this.logger));
this.inputStream$.subscribe((0, delivery_report_input_subscriber_1.deliveryReportInputSubscriberFactory)(this, this.logger));
this.deliveryReportStream$.subscribe((0, delivery_report_stream_subscriber_1.deliveryReportStreamSubscriberFactory)(this, this.logger));
this.smsStream$.subscribe((0, sms_stream_subscriber_1.smsStreamSubscriberFactory)(this));
this.smsQueuePing$.subscribe(() => {
var _a, _b, _c, _d, _e, _f;
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.verbose) === null || _b === void 0 ? void 0 : _b.call(_a, 'an sms job has ended, shifting queue...');
this.smsQueue.shift();
if (this.smsQueue.length) {
(_d = (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug) === null || _d === void 0 ? void 0 : _d.call(_c, 'sending next SMS in line...');
this.smsQueue[0].execute();
}
else {
(_f = (_e = this.logger) === null || _e === void 0 ? void 0 : _e.debug) === null || _f === void 0 ? void 0 : _f.call(_e, 'no more SMS in queue, waiting...');
}
});
if (!this.noInit) {
this.init();
}
}
async init() {
var _a, _b;
try {
await (0, rxjs_1.lastValueFrom)((0, rxjs_1.from)(this.awaitDevice()).pipe((0, rxjs_1.timeout)(5000)));
// Checking if device is an AT modem
await this.send(new at_command_1.AtCommand());
// Handling Pin Status
try {
await this.handlePinState((await this.send(new cpin_status_command_1.CpinStatusCommand())));
}
catch (err) {
this.eventEmitter.emit('error', new Error('Error while handling PIN code'));
throw new Error(`Error while handling PIN code, please check that the pin is correct before trying again to prevent sim card lock`);
}
this.state = sim800_client_state_enum_1.Sim800ClientState.Initialized;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.log('Sim800Client Initialized, waiting for network');
this.monitorNetworkUntilReady(this.network$);
// Change SIM Mode
await this.send(new cnmi_command_1.CnmiCommand(this.cnmi));
// Change to PDU
await this.send(new cmgf_command_1.CmgfCommand(sim800_command_enums_1.CmgfMode.Pdu));
if (!this.preventWipe) {
this.deleteAllStoredSms();
}
}
catch (error) {
if (typeof error === 'object' && error && 'message' in error) {
this.eventEmitter.emit('error', error);
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.error(`Sim800Client init error : ${error.message}`);
}
}
}
awaitDevice() {
return (0, rxjs_1.lastValueFrom)(this.ready$);
}
isNetworkReady() {
return (0, rxjs_1.lastValueFrom)(this.network$);
}
async send(command, options) {
var _a, _b;
// we subscribe to the completed observable
const subscription = command.completed$.subscribe((0, completed_command_subscriber_1.completedCommandSubscriberFactory)(command, this.buffer, this.nextJob$, this.logger));
// we add the command to the buffer
this.buffer.push(command);
// if the buffer has only one command, we execute it
if (this.buffer.length === 1) {
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.verbose) === null || _b === void 0 ? void 0 : _b.call(_a, `Executing "${command.command}" with PID ${command.pid}`);
this.nextJob$.next();
}
await (0, rxjs_1.lastValueFrom)(command.completed$);
subscription.unsubscribe();
if (command.error) {
this.eventEmitter.emit('error', command.error);
throw command.error;
}
if (options === null || options === void 0 ? void 0 : options.raw) {
return command.raw;
}
return command.result;
}
async sendSms(number, text, deliveryReport = false) {
var _a, _b, _c, _d, _e, _f, _g;
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug) === null || _b === void 0 ? void 0 : _b.call(_a, `sendSms called for number: "${number}", waiting line free and network ready`);
const networkReady = await this.isNetworkReady();
if (!networkReady) {
throw new Error('network stalled, please reset the device and try again');
}
const sms = new sim800_sms_1.Sim800Sms(this, { number, text, deliveryReport });
this.smsQueue.push(sms);
if (this.smsQueue.length === 1) {
// if the queue is now of length 1, it means that the sms is the only one in the queue
// we can send it right away
(_d = (_c = this.logger) === null || _c === void 0 ? void 0 : _c.verbose) === null || _d === void 0 ? void 0 : _d.call(_c, `sms queue was empty, sending sms right away`);
sms.execute();
}
else {
(_f = (_e = this.logger) === null || _e === void 0 ? void 0 : _e.verbose) === null || _f === void 0 ? void 0 : _f.call(_e, `sms is #${this.smsQueue.length} in queue, waiting for the line to be free`);
}
try {
const compositeId = await (0, rxjs_1.lastValueFrom)(sms.result$);
this.eventEmitter.emit('sms-sent', compositeId, new Date());
return compositeId;
}
catch (error) {
this.eventEmitter.emit('error', error);
(_g = this.logger) === null || _g === void 0 ? void 0 : _g.error('ERROR', error);
}
}
async handlePinState(status) {
var _a;
switch (status) {
case sim800_pin_state_enum_1.Sim800PinState.PinRequired:
if (this.pin) {
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('Pin required, unlocking sim card with provided pin');
return this.send(new pin_unlock_command_1.PinUnlockCommand(this.pin));
}
else {
throw new Error('Pin required but not provided');
}
case sim800_pin_state_enum_1.Sim800PinState.PukRequired:
throw new Error('Sim locked, puk required, please use a mobile phone to unlock the sim card');
case sim800_pin_state_enum_1.Sim800PinState.Ready:
break;
}
}
/**
*
* @deprecated will be removed from the next minor release
*/
checkNetwork(network$) {
this.monitorNetworkUntilReady(network$);
}
monitorNetworkUntilReady(network$) {
(0, rxjs_1.interval)(5000)
.pipe((0, rxjs_1.takeUntil)(network$))
.subscribe(async () => {
const networkResult = (await this.send(new creg_status_command_1.CregStatusCommand()));
this.handleNetworkResult(networkResult);
});
}
async getNetworkStatus() {
const result = (await this.send(new creg_status_command_1.CregStatusCommand()));
return result.slice(-1) || interfaces_1.NetworkStatus.Unknown;
}
setNetworkReady() {
this.eventEmitter.emit('networkReady');
this.network$.next(true);
this.network$.complete();
}
setNetworkNotReady() {
this.network$.next(false);
this.network$.complete();
this.network$.unsubscribe();
this.network$ = new rxjs_1.AsyncSubject();
// check CREG or maybe COPS, reset and call back init if not pin error
}
handleNetworkResult(result) {
var _a;
switch (result.slice(-1)) {
case '1':
this.setNetworkReady();
break;
case '5':
this.setNetworkReady();
break;
case '3':
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('Network registration denied');
this.setNetworkNotReady();
break;
}
}
reset(emptyBuffers = false, gracePeriodMs = 10000) {
var _a, _b;
if (emptyBuffers) {
this.buffer = [];
this.incomingSms = [];
this.outboxSpooler = [];
this.smsQueue = [];
}
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('Sim800Client reset called');
this.setNetworkNotReady();
this.serial.write(String.fromCharCode(27));
this.serial.write('AT+CFUN=1,1\r\n');
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.warn(`Sim800Client reset done, waiting ${gracePeriodMs}ms before reinitializing`);
if (!this.noInit) {
setTimeout(() => {
this.init();
}, gracePeriodMs);
}
}
async deleteAllStoredSms() {
// By default, wipe every SMS if there is any, we must wait for a network ready indication
// and wait for another 2-3 seconds for the SIM to be ready
await this.isNetworkReady();
setTimeout(async () => {
var _a;
try {
const result = await this.send(new cmgl_command_1.CmglCommand(), { raw: true });
if (result === null || result === void 0 ? void 0 : result.length) {
await this.send(new cmgda_command_1.CmgdaCommand());
}
}
catch (error) {
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn("Couldn't perform the CMGL command properly, maybe SIM not yet fully initialized");
this.eventEmitter.emit('error', error);
}
}, 2000);
}
on(event, listener) {
return this.eventEmitter.on(event, listener);
}
}
exports.Sim800Client = Sim800Client;
//# sourceMappingURL=sim800.client.js.map