UNPKG

bluetooth-obd2

Version:

Package for communicating with a bluetooth OBD-II reader

362 lines 29.2 kB
(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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