bluetooth-obd2
Version:
Package for communicating with a bluetooth OBD-II reader
362 lines • 29.2 kB
JavaScript
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "tslib", "bluetooth-serial-port", "./obdInfo"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var EventEmitter = require('events').EventEmitter;
var bluetooth_serial_port_1 = require("bluetooth-serial-port");
var obdInfo_1 = require("./obdInfo");
var writeDelay = 50;
/**
* Queue for writing
* @type {Array}
*/
var queue = [];
var OBDReader = /** @class */ (function (_super) {
tslib_1.__extends(OBDReader, _super);
function OBDReader() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.connected = false;
_this.receivedData = '';
_this.protocol = '0';
_this.activePollers = [];
return _this;
}
/**
* Set the protocol version number to use with your car. Defaults to 0
* which is to autoselect.
*
* Uses the ATSP command - see http://www.obdtester.com/elm-usb-commands
*
* @default 0
*
*/
OBDReader.prototype.setProtocol = function (protocol) {
if (protocol.toString().search(/^[0-9]$/) === -1) {
throw "setProtocol: Must provide a number between 0 and 9 - refer to ATSP section of http://www.obdtester.com/elm-usb-commands";
}
this.protocol = protocol;
};
/**
* Get the protocol version number set for this object. Defaults to 0
* which is to autoselect.
*
* Uses the ATSP command - see http://www.obdtester.com/elm-usb-commands
*
*/
OBDReader.prototype.getProtocol = function () {
return this.protocol;
};
/**
* Attempts discovery of and subsequent connection to Bluetooth device and channel
* @param {string} query Query string to be fuzzy-ish matched against device name/address
*/
OBDReader.prototype.autoconnect = function (query) {
var self = this; //Enclosure
var btSerial = new bluetooth_serial_port_1.default.BluetoothSerialPort();
var search = new RegExp(query.replace(/\W/g, ''), 'gi');
btSerial.on('found', function (address, name) {
var addrMatch = !query || address.replace(/\W/g, '').search(search) != -1;
var nameMatch = !query || name.replace(/\W/g, '').search(search) != -1;
if (addrMatch || nameMatch) {
btSerial.removeAllListeners('finished');
btSerial.removeAllListeners('found');
self.emit('debug', 'Found device: ' + name + ' (' + address + ')');
btSerial.findSerialPortChannel(address, function (channel) {
self.emit('debug', 'Found device channel: ' + channel);
self.connect(address, channel);
}, function () {
console.log("Error finding serialport: ");
});
}
else {
self.emit('debug', 'Ignoring device: ' + name + ' (' + address + ')');
}
});
btSerial.on('finished', function () {
self.emit('error', 'No suitable devices found');
});
btSerial.inquire();
};
OBDReader.prototype.connect = function (address, channel) {
var _this = this;
// const self = this; //Enclosure
var btSerial = new bluetooth_serial_port_1.default.BluetoothSerialPort();
this.btSerial.connect(address, channel, function () {
_this.connected = true;
_this.write('ATZ');
//Turns off extra line feed and carriage return
_this.write('ATL0');
//This disables spaces in in output, which is faster!
_this.write('ATS0');
//Turns off headers and checksum to be sent.
_this.write('ATH0');
//Turns off echo.
_this.write('ATE0');
//Turn adaptive timing to 2. This is an aggressive learn curve for adjusting the timeout. Will make huge difference on slow systems.
_this.write('ATAT2');
//Set timeout to 10 * 4 = 40msec, allows +20 queries per second. This is the maximum wait-time. ATAT will decide if it should wait shorter or not.
//self.write('ATST0A');
//http://www.obdtester.com/elm-usb-commands
_this.write('ATSP' + _this.protocol);
//Event connected
_this.emit('connected');
_this.btSerial.on('data', function (data) {
var currentString, arrayOfCommands;
currentString = _this.receivedData + data.toString(); // making sure it's a utf8 string
arrayOfCommands = currentString.split('>');
var forString = '';
if (arrayOfCommands.length < 2) {
_this.receivedData = arrayOfCommands[0];
}
else {
for (var commandNumber = 0; commandNumber < arrayOfCommands.length; commandNumber++) {
forString = arrayOfCommands[commandNumber];
if (forString === '') {
continue;
}
var multipleMessages = forString.split('\r');
for (var messageNumber = 0; messageNumber < multipleMessages.length; messageNumber++) {
var messageString = multipleMessages[messageNumber];
if (messageString === '') {
continue;
}
var reply = parseOBDCommand(messageString);
//Event dataReceived.
_this.emit('dataReceived', reply);
_this.receivedData = '';
}
}
}
});
btSerial.on('failure', function (error) {
_this.emit('error', 'Error with OBD-II device: ' + error);
});
}, function (err) {
this.emit('error', 'Error with OBD-II device: ' + err);
});
this.btSerial = btSerial; //Save the connection in OBDReader object.
this.intervalWriter = setInterval(function () {
if (queue.length > 0 && _this.connected)
try {
_this.btSerial.write(new Buffer(queue.shift(), "utf-8"), function (err) {
if (err) {
_this.emit('error', err);
}
});
}
catch (err) {
_this.emit('error', 'Error while writing: ' + err);
_this.emit('error', 'OBD-II Listeners deactivated, connection is probably lost.');
clearInterval(_this.intervalWriter);
_this.removeAllPollers();
}
}, writeDelay); //Updated with Adaptive Timing on ELM327. 20 queries a second seems good enough.
return this;
};
;
/**
* Disconnects/closes the port.
*
* @param {Function} cb Callback function when the serial connection is closed
* @this {OBDReader}
*/
OBDReader.prototype.disconnect = function (cb) {
clearInterval(this.intervalWriter);
queue.length = 0; //Clears queue
if (typeof cb === 'function') {
this.btSerial.on('closed', cb);
}
this.btSerial.close();
this.connected = false;
};
;
/**
* Writes a message to the port. (Queued!) All write functions call this function.
* @this {OBDReader}
* @param {string} message The PID or AT Command you want to send. Without \r or \n!
* @param {number} replies The number of replies that are expected. Default = 0. 0 --> infinite
* AT Messages --> Zero replies!!
*/
OBDReader.prototype.write = function (message, replies) {
if (replies === undefined) {
replies = 0;
}
if (this.connected) {
if (queue.length < 256) {
if (replies !== 0) {
queue.push(message + replies + '\r');
}
else {
queue.push(message + '\r');
}
}
else {
this.emit('error', 'Queue-overflow!');
}
}
else {
this.emit('error', 'Bluetooth device is not connected.');
}
};
;
/**
* Writes a PID value by entering a pid supported name.
* @this {OBDReader}
* @param {string} name Look into obdInfo.js for all PIDS.
*/
OBDReader.prototype.requestValueByName = function (name) {
this.write(getPIDByName(name));
};
;
/**
* Adds a poller to the poller-array.
* @this {OBDReader}
* @param {string} name Name of the poller you want to add.
*/
OBDReader.prototype.addPoller = function (name) {
var stringToSend = getPIDByName(name);
this.activePollers.push(stringToSend);
};
;
/**
* Removes an poller.
* @this {OBDReader}
* @param {string} name Name of the poller you want to remove.
*/
OBDReader.prototype.removePoller = function (name) {
var stringToDelete = getPIDByName(name);
var index = this.activePollers.indexOf(stringToDelete);
this.activePollers.splice(index, 1);
};
;
/**
* Removes all pollers.
* @this {OBDReader}
*/
OBDReader.prototype.removeAllPollers = function () {
this.activePollers.length = 0; //This does not delete the array, it just clears every element.
};
;
/**
* Writes all active pollers.
* @this {OBDReader}
*/
OBDReader.prototype.writePollers = function () {
var i;
for (i = 0; i < this.activePollers.length; i++) {
this.write(this.activePollers[i], 1);
}
};
;
/**
* Starts polling. Lower interval than activePollers * 50 will probably give buffer overflows. See writeDelay.
* @this {OBDReader}
* @param {number} interval Frequency how often all variables should be polled. (in ms). If no value is given, then for each activePoller 75ms will be added.
*/
OBDReader.prototype.startPolling = function (interval) {
var _this = this;
if (interval === undefined) {
interval = this.activePollers.length * (writeDelay * 2); //Double the delay, so there's room for manual requests.
}
this.pollerInterval = setInterval(function () {
_this.writePollers();
}, interval);
};
;
/**
* Stops polling.
* @this {OBDReader}
*/
OBDReader.prototype.stopPolling = function () {
clearInterval(this.pollerInterval);
};
;
return OBDReader;
}(EventEmitter));
exports.OBDReader = OBDReader;
// util.inherits(OBDReader, EventEmitter);
/**
* Find a PID-value by name.
* @param name Name of the PID you want the hexadecimal (in ASCII text) value of.
* @return {string} PID in hexadecimal ASCII
*/
function getPIDByName(name) {
var i;
for (i = 0; i < obdInfo_1.default.length; i++) {
if (obdInfo_1.default[i].name === name) {
if (obdInfo_1.default[i].pid !== undefined) {
return (obdInfo_1.default[i].mode + obdInfo_1.default[i].pid);
}
//There are modes which don't require a extra parameter ID.
return (obdInfo_1.default[i].mode);
}
}
}
/**
* Parses a hexadecimal string to a reply object. Uses PIDS. (obdInfo.js)
* @param {string} hexString Hexadecimal value in string that is received over the serialport.
* @return {Object} reply - The reply.
* @return {string} reply.value - The value that is already converted. This can be a PID converted answer or "OK" or "NO DATA".
* @return {string} reply.name - The name. --! Only if the reply is a PID.
* @return {string} reply.mode - The mode of the PID. --! Only if the reply is a PID.
* @return {string} reply.pid - The PID. --! Only if the reply is a PID.
*/
function parseOBDCommand(hexString) {
var byteNumber;
var reply = {};
if (hexString === "NO DATA" || hexString === "OK" || hexString === "?" || hexString === "UNABLE TO CONNECT" || hexString === "SEARCHING...") {
//No data or OK is the response, return directly.
reply.value = hexString;
return reply;
}
hexString = hexString.replace(/ /g, ''); //Whitespace trimming //Probably not needed anymore?
var valueArray = [];
for (byteNumber = 0; byteNumber < hexString.length; byteNumber += 2) {
valueArray.push(hexString.substr(byteNumber, 2));
}
if (valueArray[0] === "41") {
reply.mode = valueArray[0];
reply.pid = valueArray[1];
for (var i = 0; i < obdInfo_1.default.length; i++) {
if (obdInfo_1.default[i].pid == reply.pid) {
var numberOfBytes = obdInfo_1.default[i].bytes;
reply.name = obdInfo_1.default[i].name;
switch (numberOfBytes) {
case 1:
reply.value = obdInfo_1.default[i].convertToUseful(valueArray[2]);
break;
case 2:
reply.value = obdInfo_1.default[i].convertToUseful(valueArray[2], valueArray[3]);
break;
case 4:
reply.value = obdInfo_1.default[i].convertToUseful(valueArray[2], valueArray[3], valueArray[4], valueArray[5]);
break;
case 8:
reply.value = obdInfo_1.default[i].convertToUseful(valueArray[2], valueArray[3], valueArray[4], valueArray[5], valueArray[6], valueArray[7], valueArray[8], valueArray[9]);
break;
}
break; //Value is converted, break out the for loop.
}
}
}
else if (valueArray[0] === "43") {
reply.mode = valueArray[0];
for (var i = 0; i < obdInfo_1.default.length; i++) {
if (obdInfo_1.default[i].mode == "03") {
reply.name = obdInfo_1.default[i].name;
reply.value = obdInfo_1.default[i].convertToUseful(valueArray[1], valueArray[2], valueArray[3], valueArray[4], valueArray[5], valueArray[6]);
}
}
}
return reply;
}
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2JkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vbGliL29iZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7SUFBQSxJQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsWUFBWSxDQUFDO0lBQ3BELCtEQUF3QztJQUV4QyxxQ0FBNkI7SUFFN0IsSUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDO0lBRXRCOzs7T0FHRztJQUNILElBQU0sS0FBSyxHQUFlLEVBQUUsQ0FBQztJQUU3QjtRQUErQixxQ0FBWTtRQUEzQztZQUFBLHFFQXFSQztZQXBSRyxlQUFTLEdBQVksS0FBSyxDQUFDO1lBQzNCLGtCQUFZLEdBQUcsRUFBRSxDQUFDO1lBQ2xCLGNBQVEsR0FBRyxHQUFHLENBQUM7WUErTWYsbUJBQWEsR0FBYSxFQUFFLENBQUM7O1FBbUVqQyxDQUFDO1FBOVFHOzs7Ozs7OztXQVFHO1FBQ0gsK0JBQVcsR0FBWCxVQUFZLFFBQWdCO1lBQ3hCLElBQUksUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRTtnQkFDOUMsTUFBTSx5SEFBeUgsQ0FBQzthQUNuSTtZQUNELElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQzdCLENBQUM7UUFFRDs7Ozs7O1dBTUc7UUFDSCwrQkFBVyxHQUFYO1lBQ0ksT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQ3pCLENBQUM7UUFFRDs7O1dBR0c7UUFDSCwrQkFBVyxHQUFYLFVBQVksS0FBYTtZQUNyQixJQUFNLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQyxXQUFXO1lBQzlCLElBQU0sUUFBUSxHQUFHLElBQUksK0JBQUcsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1lBQy9DLElBQU0sTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxFQUFF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