├── .npmignore ├── NOTICE ├── .gitignore ├── LICENSE ├── package.json ├── example └── test.js ├── README.md ├── tests └── jasmine │ └── obd.spec.js └── lib ├── obd.js └── obdInfo.js /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | resources/ 3 | .idea/ 4 | 5 | *.csv 6 | log.json 7 | *.iml 8 | *.log 9 | output.md 10 | 11 | tests/ 12 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The bluetooth-obd pacakage is released under the Apache License, Version 2.0. 2 | 3 | It uses and delivers, as part of the project, 4 | the following 3rd party software: 5 | 6 | * bluetooth-serial-port : FreeBSD license, https://npmjs.org/package/bluetooth-serial-port 7 | * Author: Eelco Cramer -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build stuff 2 | build 3 | out 4 | *.node 5 | linker.lock 6 | *.o 7 | *.class 8 | 9 | # node_modules 10 | /node_modules/ 11 | 12 | # generated certificates 13 | *.pem 14 | *.csr 15 | *.crl 16 | *.srl 17 | 18 | # target folders 19 | target 20 | 21 | # reports folders 22 | reports 23 | 24 | # intellij idea project files 25 | .idea 26 | *.iml 27 | *.ipr 28 | *.iws 29 | *.log 30 | output.md 31 | 32 | # waf lock files 33 | .lock-wscript 34 | 35 | # backup files 36 | *~ 37 | 38 | # vi swap file 39 | *.swp 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | * 14 | * (C) Copyright 2013, TNO 15 | * Author: Eric Smekens 16 | */ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bluetooth-obd", 3 | "version": "0.2.5", 4 | "description": "Package for communicating with a bluetooth OBD-II reader", 5 | "author": { 6 | "name": "Eric Smekens", 7 | "email": "ericsmekens@msn.com", 8 | "url": "http://github.com/EricSmekens" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/EricSmekens/node-bluetooth-obd.git" 13 | }, 14 | "directories": { 15 | "lib": "./lib" 16 | }, 17 | "main": "./lib/obd.js", 18 | "dependencies": { 19 | "bluetooth-serial-port": "~2.2.7" 20 | }, 21 | "devDependencies": {}, 22 | "engines": { 23 | "node" : ">= 8.x" 24 | , "npm" : ">= 5.x" 25 | }, 26 | "scripts": { 27 | "install": "" 28 | }, 29 | "keywords": [ 30 | "obd", 31 | "car", 32 | "bluetooth", 33 | "rfcomm", 34 | "ecu" 35 | ], 36 | "license": "Apache-2.0" 37 | } 38 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | * 14 | * (C) Copyright 2013, TNO 15 | * Author: Eric Smekens 16 | */ 17 | 18 | var OBDReader = require('../lib/obd.js'); 19 | var btOBDReader = new OBDReader(); 20 | 21 | // Specify the car communications protocol rather than autodetect 22 | // http://www.obdtester.com/elm-usb-commands 23 | // e.g. 24 | // 'ISO 15765-4 CAN (11 bit ID, 500 kbaud)' 25 | //btOBDReader.setProtocol(6); 26 | 27 | btOBDReader.on('dataReceived', function (data) { 28 | var currentDate = new Date(); 29 | console.log(currentDate.getTime()); 30 | console.log(data); 31 | }); 32 | 33 | btOBDReader.on('connected', function () { 34 | this.addPoller("vss"); 35 | this.addPoller("rpm"); 36 | this.addPoller("temp"); 37 | this.addPoller("load_pct"); 38 | this.addPoller("map"); 39 | this.addPoller("frp"); 40 | 41 | this.startPolling(1500); 42 | }); 43 | 44 | btOBDReader.on('error', function (data) { 45 | console.log('Error: ' + data); 46 | }); 47 | 48 | btOBDReader.on('debug', function (data) { 49 | console.log('Debug: ' + data); 50 | }); 51 | 52 | // Use first device with 'obd' in the name 53 | btOBDReader.autoconnect('obd'); 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM](https://nodei.co/npm/bluetooth-obd.png?downloads=true&stars=true)](https://nodei.co/npm/bluetooth-obd/) 2 | 3 | bluetooth-obd - 0.2.5 4 | =============== 5 | 6 | # Bluetooth communication for OBD-II ELM327 devices. 7 | This node module lets you communicate over a bluetooth serial port with OBD-II ELM327 Connectors using Node.js. 8 | # Limitations 9 | * Only tested on ELM327 devices. 10 | * Not all OBD-II Commands are implemented yet. 11 | 12 | # Pre-Requirements 13 | * If it's a Bluetooth ELM327, then it should already be paired! If this hasn't been done, it will cause a connection error. 14 | * bluetooth-serial-port (module that is used by this module, thanks to Eelco) requires libbluetooth-dev package: 15 | `$ sudo apt-get install libbluetooth-dev` 16 | 17 | # Serial 18 | * If you're looking for serial RS23 connection, look into serial-obd. 19 | 20 | # Install 21 | `npm install bluetooth-obd` 22 | 23 | # Documentation 24 | 25 | ## Basic usage 26 | 27 | ```javascript 28 | var OBDReader = require('bluetooth-obd'); 29 | var btOBDReader = new OBDReader(); 30 | var dataReceivedMarker = {}; 31 | 32 | btOBDReader.on('connected', function () { 33 | //this.requestValueByName("vss"); //vss = vehicle speed sensor 34 | 35 | this.addPoller("vss"); 36 | this.addPoller("rpm"); 37 | this.addPoller("temp"); 38 | this.addPoller("load_pct"); 39 | this.addPoller("map"); 40 | this.addPoller("frp"); 41 | 42 | this.startPolling(1000); //Request all values each second. 43 | }); 44 | 45 | btOBDReader.on('dataReceived', function (data) { 46 | console.log(data); 47 | dataReceivedMarker = data; 48 | }); 49 | 50 | // Use first device with 'obd' in the name 51 | btOBDReader.autoconnect('obd'); 52 | ``` 53 | ## API 54 | 55 | ### OBDReader 56 | 57 | #### Event: ('dataReceived', data) 58 | 59 | Emitted when data is read from the OBD-II connector. 60 | 61 | * data - the data that was read and parsed to a reply object 62 | 63 | #### Event: ('connected') 64 | 65 | Emitted when the connection is set up (port is open). 66 | 67 | #### Event: ('error', message) 68 | 69 | Emitted when an error is encountered. 70 | 71 | #### Event: ('debug', message) 72 | 73 | Emitted with debugging information. 74 | 75 | #### OBDReader() 76 | 77 | Creates an instance of OBDReader. 78 | 79 | #### getPIDByName(Name) 80 | 81 | Find a PID-value by name. 82 | 83 | ##### Params: 84 | 85 | * **name** *Name* of the PID you want the hexadecimal (in ASCII text) value of. 86 | 87 | ##### Return: 88 | 89 | * **string** PID in hexadecimal ASCII 90 | 91 | #### parseOBDCommand(hexString) 92 | 93 | Parses a hexadecimal string to a reply object. Uses PIDS. (obdInfo.js) 94 | 95 | ##### Params: 96 | 97 | * **string** *hexString* Hexadecimal value in string that is received over the serialport. 98 | 99 | ##### Return: 100 | 101 | * **Object** reply - The reply. 102 | * **string** reply.value - The value that is already converted. This can be a PID converted answer or "OK" or "NO DATA". 103 | * **string** reply.name - The name. --! Only if the reply is a PID. 104 | * **string** reply.mode - The mode of the PID. --! Only if the reply is a PID. 105 | * **string** reply.pid - The PID. --! Only if the reply is a PID. 106 | 107 | #### autoconnect(query) 108 | 109 | Attempt discovery of the device based on a query string, and call connect() on the first match. 110 | 111 | ##### Params: 112 | 113 | * **string** *query* (Optional) string to be matched against address/channel (fuzzy-ish) 114 | 115 | #### connect(address, channel) 116 | 117 | Connect/Open the serial port and add events to serialport. Also starts the intervalWriter that is used to write the queue. 118 | 119 | ##### Params: 120 | 121 | * **string** *address* MAC-address of device that will be connected to. 122 | * **number** *channel* Channel that the serial port service runs on. 123 | 124 | #### disconnect() 125 | 126 | Disconnects/closes the port. 127 | 128 | #### write(message, replies) 129 | 130 | Writes a message to the port. (Queued!) All write functions call this function. 131 | 132 | ##### Params: 133 | 134 | * **string** *message* The PID or AT Command you want to send. Without \r or \n! 135 | * **number** *replies* The number of replies that are expected. Default = 0. 0 --> infinite 136 | 137 | #### requestValueByName(name) 138 | 139 | Writes a PID value by entering a pid supported name. 140 | 141 | ##### Params: 142 | 143 | * **string** *name* Look into obdInfo.js for all PIDS. 144 | 145 | #### addPoller(name) 146 | 147 | Adds a poller to the poller-array. 148 | 149 | ##### Params: 150 | 151 | * **string** *name* Name of the poller you want to add. 152 | 153 | #### removePoller(name) 154 | 155 | Removes an poller. 156 | 157 | ##### Params: 158 | 159 | * **string** *name* Name of the poller you want to remove. 160 | 161 | #### removeAllPollers() 162 | 163 | Removes all pollers. 164 | 165 | #### writePollers() 166 | 167 | Writes all active pollers. 168 | 169 | #### startPolling() 170 | 171 | Starts polling. Lower interval than activePollers * 50 will probably give buffer overflows. 172 | 173 | ##### Params: 174 | 175 | * **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. 176 | 177 | #### stopPolling() 178 | 179 | Stops polling. 180 | 181 | # LICENSE 182 | 183 | This module is available under a [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html), see also the [LICENSE file](https://raw.github.com/EricSmekens/node-bluetooth-obd/master/LICENSE) for details. 184 | -------------------------------------------------------------------------------- /tests/jasmine/obd.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | * 14 | * (C) Copyright 2013, TNO 15 | * Author: Eric Smekens 16 | * 17 | * This code should be ran with jasmine-node. 18 | ******************************************************************************/ 19 | 20 | describe("node-serial-obd", function () { 21 | 'use strict'; 22 | var OBDReader, btOBDReader, dataReceivedMarker; 23 | OBDReader = require('../../lib/obd.js'); 24 | btOBDReader = new OBDReader(); 25 | dataReceivedMarker = {}; //New object 26 | 27 | btOBDReader.on('dataReceived', function (data) { 28 | console.log(data); 29 | dataReceivedMarker = data; 30 | }); 31 | 32 | it("should be defined", function () { 33 | expect(btOBDReader).toBeDefined(); 34 | }); 35 | 36 | it("has the necessary init properties as btOBDReader object", function () { 37 | //Functions 38 | expect(btOBDReader.autoconnect).toEqual(jasmine.any(Function)); 39 | expect(btOBDReader.connect).toEqual(jasmine.any(Function)); 40 | expect(btOBDReader.disconnect).toEqual(jasmine.any(Function)); 41 | expect(btOBDReader.write).toEqual(jasmine.any(Function)); 42 | expect(btOBDReader.on).toEqual(jasmine.any(Function)); //Has events 43 | //TODO: check different events 44 | //Vars 45 | expect(btOBDReader.connected).toEqual(false); 46 | }); 47 | 48 | it("can connect to a bluetooth serial port", function () { 49 | btOBDReader.connect('D8:0D:E3:80:19:B4', 14); 50 | waitsFor(function () { 51 | return btOBDReader.connected; 52 | }, "It took too long to connect.", 20000); 53 | runs(function () { 54 | expect(btOBDReader.connected).toEqual(true); 55 | waits(5000); //Waiting for init strings to be sent and received! 56 | }); 57 | 58 | 59 | }); 60 | 61 | describe("the write function", function () { 62 | it("can write ascii to the obd-module", function () { 63 | dataReceivedMarker = false; 64 | btOBDReader.write('010C'); //010C stands for RPM 65 | }); 66 | 67 | it("can receive and convert the RPM-hex value to something right", function () { 68 | waitsFor(function () { 69 | return dataReceivedMarker; 70 | }, "Receiving time expired", 4000); 71 | runs(function () { 72 | expect(dataReceivedMarker).toEqual(jasmine.any(Object)); 73 | expect(dataReceivedMarker.mode).toEqual(jasmine.any(String)); 74 | expect(dataReceivedMarker.pid).toEqual(jasmine.any(String)); 75 | expect(dataReceivedMarker.name).toEqual(jasmine.any(String)); 76 | expect(!isNaN(dataReceivedMarker.value));//Number somehow crashes. 77 | }); 78 | 79 | 80 | }); 81 | 82 | it("can retrieve a value by name", function () { 83 | dataReceivedMarker = false; 84 | btOBDReader.requestValueByName("vss"); //vss = vehicle speed sensor 85 | 86 | waitsFor(function () { 87 | return dataReceivedMarker; 88 | }, "Receiving time expired", 4000); 89 | runs(function() { 90 | expect(dataReceivedMarker).toEqual(jasmine.any(Object)); 91 | expect(dataReceivedMarker.mode).toEqual(jasmine.any(String)); 92 | expect(dataReceivedMarker.pid).toEqual(jasmine.any(String)); 93 | expect(dataReceivedMarker.name).toEqual("vss"); 94 | expect(!isNaN(dataReceivedMarker.value));//Number somehow crashes. 95 | }); 96 | 97 | }); 98 | }); 99 | 100 | describe("pollers", function () { 101 | it("are defined", function () { 102 | //expect(btOBDReader.activePollers).toBeDefined(); //Not visible outside class. 103 | expect(btOBDReader.addPoller).toBeDefined(); 104 | expect(btOBDReader.removePoller).toBeDefined(); 105 | expect(btOBDReader.removeAllPollers).toBeDefined(); 106 | expect(btOBDReader.startPolling).toBeDefined(); 107 | expect(btOBDReader.stopPolling).toBeDefined(); 108 | }); 109 | it("can be added", function () { 110 | dataReceivedMarker = false; 111 | btOBDReader.addPoller("vss"); 112 | btOBDReader.startPolling(1000); 113 | waitsFor(function () { 114 | return dataReceivedMarker; 115 | }, "Receiving time expired", 12000); 116 | runs(function () { 117 | expect(dataReceivedMarker).toEqual(jasmine.any(Object)); 118 | expect(dataReceivedMarker.mode).toEqual(jasmine.any(String)); 119 | expect(dataReceivedMarker.pid).toEqual(jasmine.any(String)); 120 | expect(dataReceivedMarker.name).toEqual("vss"); 121 | expect(!isNaN(dataReceivedMarker.value));//Number somehow crashes. 122 | 123 | dataReceivedMarker = false; 124 | }); 125 | 126 | //Wait second time without calling anything since last data reset. --> If data comes in, polling works. 127 | waitsFor(function () { 128 | return dataReceivedMarker; 129 | }, "Receiving time expired", 12000); //Time for polling. 130 | runs(function () { 131 | expect(dataReceivedMarker).toEqual(jasmine.any(Object)); 132 | expect(dataReceivedMarker.mode).toEqual(jasmine.any(String)); 133 | expect(dataReceivedMarker.pid).toEqual(jasmine.any(String)); 134 | expect(dataReceivedMarker.name).toEqual("vss"); 135 | expect(!isNaN(dataReceivedMarker.value)); 136 | }); 137 | }); 138 | it("can be removed", function () { 139 | runs(function () { 140 | btOBDReader.removePoller("vss"); 141 | btOBDReader.stopPolling(); 142 | waits(100); 143 | dataReceivedMarker = false; 144 | //Now, no data should come in. 145 | }); 146 | 147 | waits(5000); 148 | 149 | runs(function () { 150 | expect(dataReceivedMarker).toEqual(false); 151 | }); 152 | 153 | }); 154 | }); 155 | 156 | describe("DTC", function () { //Diagnostic trouble code 157 | it("can be counted", function () { 158 | dataReceivedMarker = false; 159 | btOBDReader.write('0101', 1); //Count DTC 160 | waitsFor(function () { 161 | return dataReceivedMarker; 162 | }, "Receiving time expired", 4000); 163 | runs(function () { 164 | expect(!isNaN(dataReceivedMarker.value.mil) || dataReceivedMarker.value === 'NO DATA'); 165 | expect(!isNaN(dataReceivedMarker.value.numberOfErrors) || dataReceivedMarker.value === 'NO DATA'); 166 | if(dataReceivedMarker.value !== 'NO DATA') 167 | expect(dataReceivedMarker.value.mil).toEqual(jasmine.any(Number)); 168 | dataReceivedMarker = false; 169 | }); 170 | }); 171 | it("can be requested/read", function () { 172 | dataReceivedMarker = false; 173 | btOBDReader.requestValueByName("requestdtc"); 174 | waitsFor(function () { 175 | return dataReceivedMarker; 176 | }, "Receiving time expired", 4000); 177 | runs(function () { 178 | expect(dataReceivedMarker.name).toEqual('requestdtc'); 179 | if(dataReceivedMarker.value !== 'NO DATA') 180 | expect(dataReceivedMarker.value.errors).toEqual(jasmine.any(Array)); 181 | dataReceivedMarker = false; 182 | }); 183 | waits(1000); 184 | }); 185 | it("can be cleared", function () { 186 | dataReceivedMarker = false; 187 | btOBDReader.requestValueByName("cleardtc"); 188 | waitsFor(function () { 189 | return dataReceivedMarker; 190 | }, "Receiving time expired", 4000); 191 | runs(function () { 192 | expect(dataReceivedMarker.value).toEqual(jasmine.any(String)); 193 | dataReceivedMarker = false; 194 | }); 195 | }); 196 | }); 197 | 198 | /* //Not supported with OBDsim. 199 | describe("can read the VIN", function() { //Vehicle Identification number 200 | it("can be sent", function(){ 201 | dataReceivedMarker = false; 202 | btOBDReader.requestValueByName("vin"); 203 | waitsFor(function () { 204 | return dataReceivedMarker; 205 | }, "Receiving time expired", 4000); 206 | runs(function() { 207 | expect(dataReceivedMarker).toEqual(jasmine.any(String)); 208 | dataReceivedMarker = false; 209 | }); 210 | }); 211 | });*/ 212 | 213 | 214 | 215 | it("can close the bluetooth serial port", function () { 216 | btOBDReader.disconnect(); 217 | waitsFor(function () { 218 | return !(btOBDReader.connected); 219 | }, "Disconnect time expired", 2500); //Time for disconnect. 220 | runs(function () { 221 | expect(btOBDReader.connected).toEqual(false); 222 | btOBDReader = undefined; 223 | }); 224 | 225 | }); 226 | 227 | }); 228 | -------------------------------------------------------------------------------- /lib/obd.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | * 14 | * (C) Copyright 2013, TNO 15 | * Author: Eric Smekens 16 | */ 17 | 18 | 'use strict'; 19 | //Used for event emitting. 20 | var EventEmitter = require('events').EventEmitter; 21 | var util = require('util'); 22 | 23 | /** 24 | * obdInfo.js for all PIDS. 25 | * @type {*} 26 | */ 27 | var PIDS = require('../lib/obdInfo.js'); 28 | 29 | /** 30 | * Constant for defining delay between writes. 31 | * @type {number} 32 | */ 33 | var writeDelay = 50; 34 | 35 | /** 36 | * Queue for writing 37 | * @type {Array} 38 | */ 39 | var queue = []; 40 | 41 | // Class OBDReader 42 | var OBDReader; 43 | 44 | /** 45 | * Creates an instance of OBDReader. 46 | * @constructor 47 | * @param {string} address MAC-address of device that will be connected to. 48 | * @param {number} channel Channel that the serial port service runs on. 49 | * @this {OBDReader} 50 | */ 51 | OBDReader = function () { 52 | EventEmitter.call(this); 53 | this.connected = false; 54 | this.receivedData = ""; 55 | this.protocol = '0' ; 56 | return this; 57 | }; 58 | util.inherits(OBDReader, EventEmitter); 59 | 60 | /** 61 | * Find a PID-value by name. 62 | * @param name Name of the PID you want the hexadecimal (in ASCII text) value of. 63 | * @return {string} PID in hexadecimal ASCII 64 | */ 65 | function getPIDByName(name) { 66 | var i; 67 | for (i = 0; i < PIDS.length; i++) { 68 | if (PIDS[i].name === name) { 69 | if (PIDS[i].pid !== undefined) { 70 | return (PIDS[i].mode + PIDS[i].pid); 71 | } 72 | //There are modes which don't require a extra parameter ID. 73 | return (PIDS[i].mode); 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * Parses a hexadecimal string to a reply object. Uses PIDS. (obdInfo.js) 80 | * @param {string} hexString Hexadecimal value in string that is received over the serialport. 81 | * @return {Object} reply - The reply. 82 | * @return {string} reply.value - The value that is already converted. This can be a PID converted answer or "OK" or "NO DATA". 83 | * @return {string} reply.name - The name. --! Only if the reply is a PID. 84 | * @return {string} reply.mode - The mode of the PID. --! Only if the reply is a PID. 85 | * @return {string} reply.pid - The PID. --! Only if the reply is a PID. 86 | */ 87 | function parseOBDCommand(hexString) { 88 | var reply, 89 | byteNumber, 90 | valueArray; //New object 91 | 92 | reply = {}; 93 | if (hexString === "NO DATA" || hexString === "OK" || hexString === "?" || hexString === "UNABLE TO CONNECT" || hexString === "SEARCHING...") { 94 | //No data or OK is the response, return directly. 95 | reply.value = hexString; 96 | return reply; 97 | } 98 | 99 | hexString = hexString.replace(/ /g, ''); //Whitespace trimming //Probably not needed anymore? 100 | valueArray = []; 101 | 102 | for (byteNumber = 0; byteNumber < hexString.length; byteNumber += 2) { 103 | valueArray.push(hexString.substr(byteNumber, 2)); 104 | } 105 | 106 | if (valueArray[0] === "41") { 107 | reply.mode = valueArray[0]; 108 | reply.pid = valueArray[1]; 109 | for (var i = 0; i < PIDS.length; i++) { 110 | if (PIDS[i].pid == reply.pid) { 111 | var numberOfBytes = PIDS[i].bytes; 112 | reply.name = PIDS[i].name; 113 | switch (numberOfBytes) { 114 | case 1: 115 | reply.value = PIDS[i].convertToUseful(valueArray[2]); 116 | break; 117 | case 2: 118 | reply.value = PIDS[i].convertToUseful(valueArray[2], valueArray[3]); 119 | break; 120 | case 4: 121 | reply.value = PIDS[i].convertToUseful(valueArray[2], valueArray[3], valueArray[4], valueArray[5]); 122 | break; 123 | case 8: 124 | reply.value = PIDS[i].convertToUseful(valueArray[2], valueArray[3], valueArray[4], valueArray[5], valueArray[6], valueArray[7], valueArray[8], valueArray[9]); 125 | break; 126 | } 127 | break; //Value is converted, break out the for loop. 128 | } 129 | } 130 | } else if (valueArray[0] === "43") { 131 | reply.mode = valueArray[0]; 132 | for (var i = 0; i < PIDS.length; i++) { 133 | if (PIDS[i].mode == "03") { 134 | reply.name = PIDS[i].name; 135 | reply.value = PIDS[i].convertToUseful(valueArray[1], valueArray[2], valueArray[3], valueArray[4], valueArray[5], valueArray[6]); 136 | } 137 | } 138 | } 139 | return reply; 140 | } 141 | 142 | /** 143 | * Set the protocol version number to use with your car. Defaults to 0 144 | * which is to autoselect. 145 | * 146 | * Uses the ATSP command - see http://www.obdtester.com/elm-usb-commands 147 | * 148 | * @default 0 149 | * 150 | */ 151 | OBDReader.prototype.setProtocol = function (protocol) { 152 | if(protocol.toString().search(/^[0-9]$/) === -1) { 153 | throw "setProtocol: Must provide a number between 0 and 9 - refer to ATSP section of http://www.obdtester.com/elm-usb-commands"; 154 | } 155 | this.protocol = protocol; 156 | } 157 | 158 | /** 159 | * Get the protocol version number set for this object. Defaults to 0 160 | * which is to autoselect. 161 | * 162 | * Uses the ATSP command - see http://www.obdtester.com/elm-usb-commands 163 | * 164 | */ 165 | OBDReader.prototype.getProtocol = function () { 166 | return this.protocol; 167 | } 168 | 169 | /** 170 | * Attempts discovery of and subsequent connection to Bluetooth device and channel 171 | * @param {string} query Query string to be fuzzy-ish matched against device name/address 172 | */ 173 | OBDReader.prototype.autoconnect = function (query) { 174 | var self = this; //Enclosure 175 | var btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort(); 176 | var search = new RegExp(query.replace(/\W/g, ''), 'gi'); 177 | 178 | btSerial.on('found', function (address, name) { 179 | var addrMatch = !query || address.replace(/\W/g, '').search(search) != -1; 180 | var nameMatch = !query || name.replace(/\W/g, '').search(search) != -1; 181 | 182 | if (addrMatch || nameMatch) { 183 | btSerial.removeAllListeners('finished'); 184 | btSerial.removeAllListeners('found'); 185 | self.emit('debug', 'Found device: ' + name + ' (' + address + ')'); 186 | 187 | btSerial.findSerialPortChannel(address, function (channel) { 188 | self.emit('debug', 'Found device channel: ' + channel); 189 | self.connect(address, channel); 190 | }, function (err) { 191 | console.log("Error finding serialport: " + err); 192 | }); 193 | } else { 194 | self.emit('debug', 'Ignoring device: ' + name + ' (' + address + ')'); 195 | } 196 | }); 197 | 198 | btSerial.on('finished', function () { 199 | self.emit('error', 'No suitable devices found'); 200 | }); 201 | 202 | btSerial.inquire(); 203 | } 204 | 205 | /** 206 | * Connect/Open the bluetooth serial port and add events to bluetooth-serial-port. 207 | * Also starts the intervalWriter that is used to write the queue. 208 | * @this {OBDReader} 209 | */ 210 | OBDReader.prototype.connect = function (address, channel) { 211 | var self = this; //Enclosure 212 | var btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort(); 213 | 214 | btSerial.connect(address, channel, function () { 215 | self.connected = true; 216 | 217 | self.write('ATZ'); 218 | //Turns off extra line feed and carriage return 219 | self.write('ATL0'); 220 | //This disables spaces in in output, which is faster! 221 | self.write('ATS0'); 222 | //Turns off headers and checksum to be sent. 223 | self.write('ATH0'); 224 | //Turns off echo. 225 | self.write('ATE0'); 226 | //Turn adaptive timing to 2. This is an aggressive learn curve for adjusting the timeout. Will make huge difference on slow systems. 227 | self.write('ATAT2'); 228 | //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. 229 | //self.write('ATST0A'); 230 | //http://www.obdtester.com/elm-usb-commands 231 | self.write('ATSP'+self.protocol); 232 | 233 | //Event connected 234 | self.emit('connected'); 235 | 236 | btSerial.on('data', function (data) { 237 | var currentString, arrayOfCommands; 238 | currentString = self.receivedData + data.toString('utf8'); // making sure it's a utf8 string 239 | 240 | arrayOfCommands = currentString.split('>'); 241 | 242 | var forString; 243 | if (arrayOfCommands.length < 2) { 244 | self.receivedData = arrayOfCommands[0]; 245 | } else { 246 | for (var commandNumber = 0; commandNumber < arrayOfCommands.length; commandNumber++) { 247 | forString = arrayOfCommands[commandNumber]; 248 | if (forString === '') { 249 | continue; 250 | } 251 | 252 | var multipleMessages = forString.split('\r'); 253 | for (var messageNumber = 0; messageNumber < multipleMessages.length; messageNumber++) { 254 | var messageString = multipleMessages[messageNumber]; 255 | if (messageString === '') { 256 | continue; 257 | } 258 | var reply; 259 | reply = parseOBDCommand(messageString); 260 | //Event dataReceived. 261 | self.emit('dataReceived', reply); 262 | self.receivedData = ''; 263 | } 264 | } 265 | } 266 | }); 267 | 268 | btSerial.on('failure', function (error) { 269 | self.emit('error', 'Error with OBD-II device: ' + error); 270 | }); 271 | 272 | }, function (err) { //Error callback! 273 | self.emit('error', 'Error with OBD-II device: ' + err); 274 | }); 275 | 276 | this.btSerial = btSerial; //Save the connection in OBDReader object. 277 | 278 | this.intervalWriter = setInterval(function () { 279 | if (queue.length > 0 && self.connected) 280 | try { 281 | self.btSerial.write(new Buffer(queue.shift(), "utf-8"), function (err, count) { 282 | if (err) 283 | self.emit('error', err); 284 | }); 285 | } catch (err) { 286 | self.emit('error', 'Error while writing: ' + err); 287 | self.emit('error', 'OBD-II Listeners deactivated, connection is probably lost.'); 288 | clearInterval(self.intervalWriter); 289 | self.removeAllPollers(); 290 | } 291 | }, writeDelay); //Updated with Adaptive Timing on ELM327. 20 queries a second seems good enough. 292 | 293 | return this; 294 | }; 295 | 296 | /** 297 | * Disconnects/closes the port. 298 | * 299 | * @param {Function} cb Callback function when the serial connection is closed 300 | * @this {OBDReader} 301 | */ 302 | OBDReader.prototype.disconnect = function (cb) { 303 | clearInterval(this.intervalWriter); 304 | queue.length = 0; //Clears queue 305 | if(typeof cb === 'function') { 306 | this.btSerial.on('closed',cb) ; 307 | } 308 | this.btSerial.close(); 309 | this.connected = false; 310 | }; 311 | 312 | /** 313 | * Writes a message to the port. (Queued!) All write functions call this function. 314 | * @this {OBDReader} 315 | * @param {string} message The PID or AT Command you want to send. Without \r or \n! 316 | * @param {number} replies The number of replies that are expected. Default = 0. 0 --> infinite 317 | * AT Messages --> Zero replies!! 318 | */ 319 | OBDReader.prototype.write = function (message, replies) { 320 | if (replies === undefined) { 321 | replies = 0; 322 | } 323 | if (this.connected) { 324 | if (queue.length < 256) { 325 | if (replies !== 0) { 326 | queue.push(message + replies + '\r'); 327 | } else { 328 | queue.push(message + '\r'); 329 | } 330 | } else { 331 | this.emit('error', 'Queue-overflow!'); 332 | } 333 | } else { 334 | this.emit('error', 'Bluetooth device is not connected.'); 335 | } 336 | }; 337 | /** 338 | * Writes a PID value by entering a pid supported name. 339 | * @this {OBDReader} 340 | * @param {string} name Look into obdInfo.js for all PIDS. 341 | */ 342 | OBDReader.prototype.requestValueByName = function (name) { 343 | this.write(getPIDByName(name)); 344 | }; 345 | 346 | var activePollers = []; 347 | /** 348 | * Adds a poller to the poller-array. 349 | * @this {OBDReader} 350 | * @param {string} name Name of the poller you want to add. 351 | */ 352 | OBDReader.prototype.addPoller = function (name) { 353 | var stringToSend = getPIDByName(name); 354 | activePollers.push(stringToSend); 355 | }; 356 | /** 357 | * Removes an poller. 358 | * @this {OBDReader} 359 | * @param {string} name Name of the poller you want to remove. 360 | */ 361 | OBDReader.prototype.removePoller = function (name) { 362 | var stringToDelete = getPIDByName(name); 363 | var index = activePollers.indexOf(stringToDelete); 364 | activePollers.splice(index, 1); 365 | }; 366 | /** 367 | * Removes all pollers. 368 | * @this {OBDReader} 369 | */ 370 | OBDReader.prototype.removeAllPollers = function () { 371 | activePollers.length = 0; //This does not delete the array, it just clears every element. 372 | }; 373 | /** 374 | * Writes all active pollers. 375 | * @this {OBDReader} 376 | */ 377 | OBDReader.prototype.writePollers = function () { 378 | var i; 379 | for (i = 0; i < activePollers.length; i++) { 380 | this.write(activePollers[i], 1); 381 | } 382 | }; 383 | 384 | var pollerInterval; 385 | /** 386 | * Starts polling. Lower interval than activePollers * 50 will probably give buffer overflows. See writeDelay. 387 | * @this {OBDReader} 388 | * @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. 389 | */ 390 | OBDReader.prototype.startPolling = function (interval) { 391 | if (interval === undefined) { 392 | interval = activePollers.length * (writeDelay * 2); //Double the delay, so there's room for manual requests. 393 | } 394 | 395 | var self = this; 396 | pollerInterval = setInterval(function () { 397 | self.writePollers(); 398 | }, interval); 399 | }; 400 | /** 401 | * Stops polling. 402 | * @this {OBDReader} 403 | */ 404 | OBDReader.prototype.stopPolling = function () { 405 | clearInterval(pollerInterval); 406 | }; 407 | 408 | var exports = module.exports = OBDReader; 409 | -------------------------------------------------------------------------------- /lib/obdInfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | * 14 | * (C) Copyright 2013, TNO 15 | * Author: Eric Smekens 16 | */ 17 | 'use strict'; 18 | 19 | function checkHex(n){ 20 | return/^[0-9A-Fa-f]{1,64}$/.test(n); 21 | } 22 | function Hex2Bin(n){ 23 | if(!checkHex(n)){ 24 | return 0; 25 | } 26 | return zeroFill(parseInt(n,16).toString(2),4); 27 | } 28 | function zeroFill( number, width ){ 29 | width -= number.toString().length; 30 | if ( width > 0 ){ 31 | return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; 32 | } 33 | return number + ""; // always return a string 34 | } 35 | function bitDecoder(byte) { 36 | return parseInt(byte, 2); 37 | } 38 | function convertPIDSupported(byteA, byteB, byteC, byteD) { 39 | var hexstring = byteA + byteB + byteC + byteD; 40 | var pidHex = hexstring.split(''); 41 | var pidStatus = []; 42 | pidHex.forEach(function(hex){ 43 | var hexPerm = Hex2Bin(hex).split(''); 44 | hexPerm.forEach(function(perm){ 45 | pidStatus.push( perm === "1" ? true : false ); 46 | }); 47 | }); 48 | return pidStatus; 49 | } 50 | function convertFuelSystem(byteA, byteB){ 51 | var reply = {}; 52 | reply.system1 = bitDecoder(byteA); 53 | if( byteB ){ 54 | reply.system2 = bitDecoder(byteB); 55 | } 56 | return reply; 57 | } 58 | function convertDTCCheck(byteA, byteB, byteC, byteD) { 59 | //ByteB, ByteC and ByteD are not read. These bytes are for testing purposes, which is not supported in this module. 60 | var byteValue, mil, numberOfDTCs, reply; 61 | byteValue = parseInt(byteA, 16); 62 | if ((byteValue >> 7) === 1) { 63 | mil = 1; 64 | } else { 65 | mil = 0; 66 | } 67 | numberOfDTCs = byteValue % 128; 68 | reply = {}; 69 | reply.numberOfErrors = numberOfDTCs; 70 | reply.mil = mil; 71 | return reply; 72 | } 73 | function convertDTCRequest(byteA, byteB, byteC, byteD, byteE, byteF) { 74 | var reply = {}; 75 | reply.errors = []; 76 | 77 | var decodeDTCCode = function (byte1, byte2) { 78 | var codeString = "", firstChar; 79 | 80 | //If 00 00 --> No code. 81 | if ((byte1 === '00') && (byte2 === '00')) { 82 | return '-'; 83 | } 84 | 85 | var firstByte = parseInt(byte1, 16); 86 | var firstCharBytes = firstByte >> 6; 87 | switch(firstCharBytes) { 88 | case 0: 89 | firstChar = 'P'; 90 | break; 91 | case 1: 92 | firstChar = 'C'; 93 | break; 94 | case 2: 95 | firstChar = 'B'; 96 | break; 97 | case 3: 98 | firstChar = 'U'; 99 | break; 100 | default: 101 | console.log('Error with DTC'); 102 | break; 103 | } 104 | var secondChar = (firstByte >> 4) % 4; 105 | var thirdChar = firstByte % 16; 106 | codeString = firstChar + secondChar + thirdChar + byte2; 107 | return codeString; 108 | }; 109 | 110 | reply.errors[0] = decodeDTCCode(byteA, byteB); 111 | reply.errors[1] = decodeDTCCode(byteC, byteD); 112 | reply.errors[2] = decodeDTCCode(byteE, byteF); 113 | return reply; 114 | } 115 | function convertLoad(byte) { 116 | return parseInt(byte, 16) * (100 / 256); 117 | } 118 | function convertTemp(byte) { 119 | return parseInt(byte, 16) - 40; 120 | } 121 | function convertFuelTrim(byte) { 122 | return (parseInt(byte, 16) - 128) * (100 / 128); 123 | } 124 | function convertFuelRailPressure(byte) { 125 | return parseInt(byte, 16) * 3; 126 | } 127 | function convertIntakePressure(byte) { 128 | return parseInt(byte, 16); 129 | } 130 | function convertRPM(byteA, byteB) { 131 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 4; 132 | } 133 | function convertSpeed(byte) { 134 | return parseInt(byte, 16); 135 | } 136 | function convertSparkAdvance(byte) { 137 | return (parseInt(byte, 16) / 2) - 64; 138 | } 139 | function convertAirFlowRate(byteA, byteB) { 140 | return ((parseInt(byteA, 16) * 256.0) + parseInt(byteB, 16)) / 100; 141 | } 142 | function convertThrottlePos(byte) { 143 | return (parseInt(byte, 16) * 100) / 255; 144 | } 145 | function convertOxygenSensorOutput(byte) { 146 | return parseInt(byte, 16) * 0.005; 147 | } 148 | function convertRuntime(byteA, byteB){ 149 | return (parseInt(byteA, 16) * 256.0) + parseInt(byteB, 16); 150 | } 151 | function convertfrpm(byteA, byteB){ 152 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 0.079; 153 | } 154 | function convertfrpd(byteA, byteB){ 155 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 10 156 | } 157 | function convertLambda(byteA, byteB, byteC, byteD){ 158 | var reply = {}; 159 | reply.ratio = ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 2 / 65535; 160 | reply.voltage = ((parseInt(byteC, 16) * 256) + parseInt(byteD, 16)) * 8 / 65535; 161 | return reply; 162 | } 163 | function convertPercentA(byte){ 164 | return parseInt(byte, 16) * 100 / 255; 165 | } 166 | function convertPercentB(byte){ 167 | return (parseInt(byte, 16) - 128) * 100 / 128; 168 | } 169 | function convertDistanceSinceCodesCleared(byteA, byteB){ 170 | return (parseInt(byteA, 16) * 256) + parseInt(byteB, 16); 171 | } 172 | function convertLambda2(byteA, byteB, byteC, byteD){ 173 | var reply = {}; 174 | reply.ratio = ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 32768; 175 | reply.voltage = ((parseInt(byteC, 16) * 256) + parseInt(byteD, 16)) / 256 - 128; 176 | return reply; 177 | } 178 | function convertCatalystTemperature(byteA, byteB){ 179 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 10 - 40; 180 | } 181 | function convertControlModuleVoltage(byteA, byteB){ 182 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 1000; 183 | } 184 | function convertAbsoluteLoad(byteA, byteB){ 185 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 100 / 255; 186 | } 187 | function convertLambda3(byteA, byteB){ 188 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 32768; 189 | } 190 | function convertAmbientAirTemp(byte){ 191 | return parseInt(byte, 16) - 40; 192 | } 193 | function convertMinutes(byteA, byteB){ 194 | return (parseInt(byteA, 16) * 256) + parseInt(byteB, 16); 195 | } 196 | function convertExternalTestEquipment(byteA, byteB, byteC, byteD){ 197 | var reply = {}; 198 | reply.te1 = bitDecoder(byteA); 199 | reply.te2 = bitDecoder(byteB); 200 | reply.te3 = bitDecoder(byteC); 201 | reply.te4 = bitDecoder(byteD) * 10; 202 | return reply; 203 | } 204 | function convertExternalTestEquipment2(byteA, byteB, byteC, byteD){ 205 | var reply = {}; 206 | reply.te1 = bitDecoder(byteA) * 10; 207 | reply.te2 = bitDecoder(byteB); 208 | reply.te3 = bitDecoder(byteC); 209 | reply.te4 = bitDecoder(byteD); 210 | return reply; 211 | } 212 | function convertAbsoluteVaporPressure(byteA, byteB){ 213 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) / 200; 214 | } 215 | function convertSystemVaporPressure(byteA, byteB){ 216 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) - 32767; 217 | } 218 | function convertShortOxygenSensorOutput(byteA, byteB){ 219 | var reply = {}; 220 | reply.bank1 = (parseInt(byteA, 16) - 128) * 100 / 128; 221 | reply.bank2 = (parseInt(byteB, 16) - 128) * 100 / 128; 222 | return reply; 223 | } 224 | function convertFuelRailPressureAbs(byteA, byteB) { 225 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 10; 226 | } 227 | function convertFuelInjectionTiming(byteA, byteB) { 228 | return (((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) - 26880) / 128; 229 | } 230 | function convertEngineFuelRate(byteA, byteB) { 231 | return ((parseInt(byteA, 16) * 256) + parseInt(byteB, 16)) * 0.05; 232 | } 233 | 234 | function convertEngineTorque(byte){ 235 | return parseInt(byte, 16) - 125; 236 | } 237 | 238 | function convertExhastGasTemperature(byteA, byteB){ 239 | return (parseInt(byteA, 16) * 256 + parseInt(byteB, 16)) / 10 - 40; 240 | } 241 | //DTC 242 | function notSupported() { 243 | console.log("There is no answer. This should not be happening."); 244 | return; 245 | } 246 | //VIN 247 | function convertVIN_count(byte) { 248 | return byte; 249 | } 250 | function convertVIN(byte) { 251 | byte = byte.split(""); 252 | var tmp=[], vin=""; 253 | for(var i in byte){ 254 | tmp[i] = parseInt(byte[i]); 255 | tmp[i] = parseInt(tmp[i], 16); 256 | vin += String.fromCharCode(tmp[i]); 257 | } 258 | return vin; 259 | } 260 | 261 | var responsePIDS; 262 | var modeRealTime = "01"; 263 | var modeRequestDTC = "03"; 264 | var modeClearDTC = "04"; 265 | var modeVin = "09"; 266 | 267 | responsePIDS = [ 268 | //Realtime data 269 | {mode: modeRealTime, pid: "00", bytes: 4, name: "pidsupp0", description: "PIDs supported 00-20", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertPIDSupported}, 270 | {mode: modeRealTime, pid: "01", bytes: 4, name: "dtc_cnt", description: "Monitor status since DTCs cleared", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertDTCCheck}, 271 | {mode: modeRealTime, pid: "02", bytes: 2, name: "dtcfrzf", description: "DTC that caused required freeze frame data storage", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 272 | {mode: modeRealTime, pid: "03", bytes: 2, name: "fuelsys", description: "Fuel system 1 and 2 status", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertFuelSystem}, 273 | {mode: modeRealTime, pid: "04", bytes: 1, name: "load_pct", description: "Calculated LOAD Value", min: 0, max: 100, unit: "%", convertToUseful: convertLoad}, 274 | {mode: modeRealTime, pid: "05", bytes: 1, name: "temp", description: "Engine Coolant Temperature", min: -40, max: 215, unit: "Celsius", convertToUseful: convertTemp}, 275 | {mode: modeRealTime, pid: "06", bytes: 1, name: "shrtft13", description: "Short Term Fuel Trim - Bank 1,3", min: -100, max: 99.22, unit: "%", convertToUseful: convertFuelTrim}, 276 | {mode: modeRealTime, pid: "07", bytes: 1, name: "longft13", description: "Long Term Fuel Trim - Bank 1,3", min: -100, max: 99.22, unit: "%", convertToUseful: convertFuelTrim}, 277 | {mode: modeRealTime, pid: "08", bytes: 1, name: "shrtft24", description: "Short Term Fuel Trim - Bank 2,4", min: -100, max: 99.22, unit: "%", convertToUseful: convertFuelTrim}, 278 | {mode: modeRealTime, pid: "09", bytes: 1, name: "longft24", description: "Long Term Fuel Trim - Bank 2,4", min: -100, max: 99.22, unit: "%", convertToUseful: convertFuelTrim}, 279 | {mode: modeRealTime, pid: "0A", bytes: 1, name: "frp", description: "Fuel Pressure", min: 0, max: 765, unit: "kPa", convertToUseful: convertFuelRailPressure}, 280 | {mode: modeRealTime, pid: "0B", bytes: 1, name: "map", description: "Intake Manifold Absolute Pressure", min: 0, max: 255, unit: "kPa", convertToUseful: convertIntakePressure}, 281 | {mode: modeRealTime, pid: "0C", bytes: 2, name: "rpm", description: "Engine RPM", min: 0, max: 16383.75, unit: "rev/min", convertToUseful: convertRPM}, 282 | {mode: modeRealTime, pid: "0D", bytes: 1, name: "vss", description: "Vehicle Speed Sensor", min: 0, max: 255, unit: "km/h", convertToUseful: convertSpeed}, 283 | {mode: modeRealTime, pid: "0E", bytes: 1, name: "sparkadv", description: "Ignition Timing Advance for #1 Cylinder", min: -64, max: 63.5, unit: "degrees relative to #1 cylinder", convertToUseful: convertSparkAdvance}, 284 | {mode: modeRealTime, pid: "0F", bytes: 1, name: "iat", description: "Intake Air Temperature", min: -40, max: 215, unit: "Celsius", convertToUseful: convertTemp}, 285 | {mode: modeRealTime, pid: "10", bytes: 2, name: "maf", description: "Air Flow Rate from Mass Air Flow Sensor", min: 0, max: 655.35, unit: "g/s", convertToUseful: convertAirFlowRate}, 286 | {mode: modeRealTime, pid: "11", bytes: 1, name: "throttlepos", description: "Absolute Throttle Position", min: 1, max: 100, unit: "%", convertToUseful: convertThrottlePos}, 287 | {mode: modeRealTime, pid: "12", bytes: 1, name: "air_stat", description: "Commanded Secondary Air Status", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 288 | {mode: modeRealTime, pid: "13", bytes: 1, name: "o2sloc", description: "Location of Oxygen Sensors", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 289 | {mode: modeRealTime, pid: "14", bytes: 2, name: "o2s11", description: "Bank 1 - Sensor 1/Bank 1 - Sensor 1 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 290 | {mode: modeRealTime, pid: "15", bytes: 2, name: "o2s12", description: "Bank 1 - Sensor 2/Bank 1 - Sensor 2 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 291 | {mode: modeRealTime, pid: "16", bytes: 2, name: "o2s13", description: "Bank 1 - Sensor 3/Bank 2 - Sensor 1 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 292 | {mode: modeRealTime, pid: "17", bytes: 2, name: "o2s14", description: "Bank 1 - Sensor 4/Bank 2 - Sensor 2 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 293 | {mode: modeRealTime, pid: "18", bytes: 2, name: "o2s21", description: "Bank 2 - Sensor 1/Bank 3 - Sensor 1 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 294 | {mode: modeRealTime, pid: "19", bytes: 2, name: "o2s22", description: "Bank 2 - Sensor 2/Bank 3 - Sensor 2 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 295 | {mode: modeRealTime, pid: "1A", bytes: 2, name: "o2s23", description: "Bank 2 - Sensor 3/Bank 4 - Sensor 1 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 296 | {mode: modeRealTime, pid: "1B", bytes: 2, name: "o2s24", description: "Bank 2 - Sensor 4/Bank 4 - Sensor 2 Oxygen Sensor Output Voltage / Short Term Fuel Trim", min: 0, max: 1.275, unit: "V", convertToUseful: convertOxygenSensorOutput}, 297 | {mode: modeRealTime, pid: "1C", bytes: 1, name: "obdsup", description: "OBD requirements to which vehicle is designed", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 298 | {mode: modeRealTime, pid: "1D", bytes: 1, name: "o2sloc2", description: "Location of oxygen sensors", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 299 | {mode: modeRealTime, pid: "1E", bytes: 1, name: "pto_stat", description: "Auxiliary Input Status", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 300 | {mode: modeRealTime, pid: "1F", bytes: 2, name: "runtm", description: "Time Since Engine Start", min: 0, max: 65535, unit: "seconds", convertToUseful: convertRuntime}, 301 | {mode: modeRealTime, pid: "20", bytes: 4, name: "piddsupp2", description: "PIDs supported 21-40", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertPIDSupported}, 302 | {mode: modeRealTime, pid: "21", bytes: 2, name: "mil_dist", description: "Distance Travelled While MIL is Activated", min: 0, max: 65535, unit: "km", convertToUseful: convertRuntime}, 303 | {mode: modeRealTime, pid: "22", bytes: 2, name: "frpm", description: "Fuel Rail Pressure relative to manifold vacuum", min: 0, max: 5177.265, unit: "kPa", convertToUseful: convertfrpm}, 304 | {mode: modeRealTime, pid: "23", bytes: 2, name: "frpd", description: "Fuel Rail Pressure (diesel)", min: 0, max: 655350, unit: "kPa", convertToUseful: convertfrpd}, 305 | {mode: modeRealTime, pid: "24", bytes: 4, name: "lambda11", description: "Bank 1 - Sensor 1/Bank 1 - Sensor 1 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 306 | {mode: modeRealTime, pid: "25", bytes: 4, name: "lambda12", description: "Bank 1 - Sensor 2/Bank 1 - Sensor 2 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 307 | {mode: modeRealTime, pid: "26", bytes: 4, name: "lambda13", description: "Bank 1 - Sensor 3 /Bank 2 - Sensor 1(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 308 | {mode: modeRealTime, pid: "27", bytes: 4, name: "lambda14", description: "Bank 1 - Sensor 4 /Bank 2 - Sensor 2(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 309 | {mode: modeRealTime, pid: "28", bytes: 4, name: "lambda21", description: "Bank 2 - Sensor 1 /Bank 3 - Sensor 1(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 310 | {mode: modeRealTime, pid: "29", bytes: 4, name: "lambda22", description: "Bank 2 - Sensor 2 /Bank 3 - Sensor 2(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 311 | {mode: modeRealTime, pid: "2A", bytes: 4, name: "lambda23", description: "Bank 2 - Sensor 3 /Bank 4 - Sensor 1(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 312 | {mode: modeRealTime, pid: "2B", bytes: 4, name: "lambda24", description: "Bank 2 - Sensor 4 /Bank 4 - Sensor 2(wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Voltage", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda}, 313 | {mode: modeRealTime, pid: "2C", bytes: 1, name: "egr_pct", description: "Commanded EGR", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 314 | {mode: modeRealTime, pid: "2D", bytes: 1, name: "egr_err", description: "EGR Error", min: -100, max: 99.22, unit: "%", convertToUseful: convertPercentB}, 315 | {mode: modeRealTime, pid: "2E", bytes: 1, name: "evap_pct", description: "Commanded Evaporative Purge", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 316 | {mode: modeRealTime, pid: "2F", bytes: 1, name: "fli", description: "Fuel Level Input", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 317 | {mode: modeRealTime, pid: "30", bytes: 1, name: "warm_ups", description: "Number of warm-ups since diagnostic trouble codes cleared", min: 0, max: 255, unit: "", convertToUseful: bitDecoder}, 318 | {mode: modeRealTime, pid: "31", bytes: 2, name: "clr_dist", description: "Distance since diagnostic trouble codes cleared", min: 0, max: 65535, unit: "km", convertToUseful: convertDistanceSinceCodesCleared}, 319 | // <-- pending 320 | {mode: modeRealTime, pid: "32", bytes: 2, name: "evap_vp", description: "Evap System Vapour Pressure", min: -8192, max: 8192, unit: "Pa", convertToUseful: bitDecoder}, 321 | // pending --> 322 | {mode: modeRealTime, pid: "33", bytes: 1, name: "baro", description: "Barometric Pressure", min: 0, max: 255, unit: "kPa", convertToUseful: bitDecoder}, 323 | {mode: modeRealTime, pid: "34", bytes: 4, name: "lambdac11", description: "Bank 1 - Sensor 1/Bank 1 - Sensor 1 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 324 | {mode: modeRealTime, pid: "35", bytes: 4, name: "lambdac12", description: "Bank 1 - Sensor 2/Bank 1 - Sensor 2 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 325 | {mode: modeRealTime, pid: "36", bytes: 4, name: "lambdac13", description: "Bank 1 - Sensor 3/Bank 2 - Sensor 1 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 326 | {mode: modeRealTime, pid: "37", bytes: 4, name: "lambdac14", description: "Bank 1 - Sensor 4/Bank 2 - Sensor 2 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 327 | {mode: modeRealTime, pid: "38", bytes: 4, name: "lambdac21", description: "Bank 2 - Sensor 1/Bank 3 - Sensor 1 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 328 | {mode: modeRealTime, pid: "39", bytes: 4, name: "lambdac22", description: "Bank 2 - Sensor 2/Bank 3 - Sensor 2 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 329 | {mode: modeRealTime, pid: "3A", bytes: 4, name: "lambdac23", description: "Bank 2 - Sensor 3/Bank 4 - Sensor 1 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 330 | {mode: modeRealTime, pid: "3B", bytes: 4, name: "lambdac24", description: "Bank 2 - Sensor 4/Bank 4 - Sensor 2 (wide range O2S) Oxygen Sensors Equivalence Ratio (lambda) / Current", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda2}, 331 | {mode: modeRealTime, pid: "3C", bytes: 2, name: "catemp11", description: "Catalyst Temperature Bank 1 / Sensor 1", min: -40, max: 6513.5, unit: "Celsius", convertToUseful: convertCatalystTemperature}, 332 | {mode: modeRealTime, pid: "3D", bytes: 2, name: "catemp21", description: "Catalyst Temperature Bank 2 / Sensor 1", min: -40, max: 6513.5, unit: "Celsius", convertToUseful: convertCatalystTemperature}, 333 | {mode: modeRealTime, pid: "3E", bytes: 2, name: "catemp12", description: "Catalyst Temperature Bank 1 / Sensor 2", min: -40, max: 6513.5, unit: "Celsius", convertToUseful: convertCatalystTemperature}, 334 | {mode: modeRealTime, pid: "3F", bytes: 2, name: "catemp22", description: "Catalyst Temperature Bank 2 / Sensor 2", min: -40, max: 6513.5, unit: "Celsius", convertToUseful: convertCatalystTemperature}, 335 | 336 | {mode: modeRealTime, pid: "40", bytes: 4, name: "piddsupp4", description: "PIDs supported 41-60", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertPIDSupported}, 337 | // <-- pending 338 | {mode: modeRealTime, pid: "41", bytes: 4, name: "monitorstat", description: "Monitor status this driving cycle", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 339 | // pending --> 340 | {mode: modeRealTime, pid: "42", bytes: 2, name: "vpwr", description: "Control module voltage", min: 0, max: 65535, unit: "V", convertToUseful: convertControlModuleVoltage}, 341 | {mode: modeRealTime, pid: "43", bytes: 2, name: "load_abs", description: "Absolute Load Value", min: 0, max: 25700, unit: "%", convertToUseful: convertAbsoluteLoad}, 342 | {mode: modeRealTime, pid: "44", bytes: 2, name: "lambda", description: "Fuel/air Commanded Equivalence Ratio", min: 0, max: 2, unit: "(ratio)", convertToUseful: convertLambda3}, 343 | {mode: modeRealTime, pid: "45", bytes: 1, name: "tp_r", description: "Relative Throttle Position", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 344 | {mode: modeRealTime, pid: "46", bytes: 1, name: "aat", description: "Ambient air temperature", min: -40, max: 215, unit: "Celsius", convertToUseful: convertAmbientAirTemp}, 345 | {mode: modeRealTime, pid: "47", bytes: 1, name: "tp_b", description: "Absolute Throttle Position B", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 346 | {mode: modeRealTime, pid: "48", bytes: 1, name: "tp_c", description: "Absolute Throttle Position C", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 347 | {mode: modeRealTime, pid: "49", bytes: 1, name: "app_d", description: "Accelerator Pedal Position D", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 348 | {mode: modeRealTime, pid: "4A", bytes: 1, name: "app_e", description: "Accelerator Pedal Position E", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 349 | {mode: modeRealTime, pid: "4B", bytes: 1, name: "app_f", description: "Accelerator Pedal Position F", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 350 | {mode: modeRealTime, pid: "4C", bytes: 1, name: "tac_pct", description: "Commanded Throttle Actuator Control", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 351 | {mode: modeRealTime, pid: "4D", bytes: 2, name: "mil_time", description: "Time run by the engine while MIL activated", min: 0, max: 65535, unit: "minutes", convertToUseful: convertMinutes}, 352 | {mode: modeRealTime, pid: "4E", bytes: 2, name: "clr_time", description: "Time since diagnostic trouble codes cleared", min: 0, max: 65535, unit: "minutes", convertToUseful: convertMinutes}, 353 | {mode: modeRealTime, pid: "4F", bytes: 4, name: "exttest1", description: "External Test Equipment Configuration #1", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertExternalTestEquipment}, 354 | {mode: modeRealTime, pid: "50", bytes: 4, name: "exttest2", description: "External Test Equipment Configuration #2", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: convertExternalTestEquipment2}, 355 | {mode: modeRealTime, pid: "51", bytes: 1, name: "fuel_type", description: "Fuel Type", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 356 | {mode: modeRealTime, pid: "52", bytes: 1, name: "alch_pct", description: "Ethanol fuel %", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 357 | {mode: modeRealTime, pid: "53", bytes: 2, name: "abs_vp", description: "Absolute Evap system Vapor Pressure", min: 0, max: 327675, unit: "kPa", convertToUseful: convertAbsoluteVaporPressure}, 358 | {mode: modeRealTime, pid: "54", bytes: 2, name: "system_vp", description: "Evap system vapor pressure", min: -32767, max: 32767, unit: "Pa", convertToUseful: convertSystemVaporPressure}, 359 | {mode: modeRealTime, pid: "55", bytes: 2, name: "s02b13", description: "Short term secondary oxygen sensor trim bank 1 and bank 3", min: -100, max: 99.22, unit: "%", convertToUseful: convertShortOxygenSensorOutput}, 360 | {mode: modeRealTime, pid: "56", bytes: 2, name: "l02b13", description: "Long term secondary oxygen sensor trim bank 1 and bank 3", min: -100, max: 99.22, unit: "%", convertToUseful: convertShortOxygenSensorOutput}, 361 | {mode: modeRealTime, pid: "57", bytes: 2, name: "s02b24", description: "Short term secondary oxygen sensor trim bank 2 and bank 4", min: -100, max: 99.22, unit: "%", convertToUseful: convertShortOxygenSensorOutput}, 362 | {mode: modeRealTime, pid: "58", bytes: 2, name: "l02b24", description: "Long term secondary oxygen sensor trim bank 2 and bank 4", min: -100, max: 99.22, unit: "%", convertToUseful: convertShortOxygenSensorOutput}, 363 | {mode: modeRealTime, pid: "59", bytes: 2, name: "frp_abs", description: "Fuel rail pressure (absolute)", min: 0, max: 655350, unit: "kPa", convertToUseful: convertFuelRailPressureAbs}, 364 | {mode: modeRealTime, pid: "5A", bytes: 1, name: "pedalpos", description: "Relative accelerator pedal position", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 365 | {mode: modeRealTime, pid: "5B", bytes: 1, name: "hybridlife", description: "Hybrid battery pack remaining life", min: 0, max: 100, unit: "%", convertToUseful: convertPercentA}, 366 | {mode: modeRealTime, pid: "5C", bytes: 1, name: "engineoilt", description: "Engine oil temperature", min: -40, max: 210, unit: "°C", convertToUseful: convertTemp}, 367 | {mode: modeRealTime, pid: "5D", bytes: 2, name: "finjtiming", description: "Fuel injection timing", min: -210.00, max: 301.992, unit: "°", convertToUseful: convertFuelInjectionTiming}, 368 | {mode: modeRealTime, pid: "5E", bytes: 2, name: "enginefrate", description: "Engine fuel rate", min: 0, max: 3212.75, unit: "L/h", convertToUseful: convertEngineFuelRate}, 369 | {mode: modeRealTime, pid: "5F", bytes: 1, name: "emmissionreq", description: "Emission requirements to which vehicle is designed", min: 0, max: 0, unit: "Bit Encoded", convertToUseful: bitDecoder}, 370 | 371 | //added some new pid entries 372 | {mode: modeRealTime, pid: "62", bytes: 1, name: "aet", description: "Actual engine - percent torque", min: -125, max: 125, unit: "%", convertToUseful: convertEngineTorque}, 373 | {mode: modeRealTime, pid: "67", bytes: 3, name: "ect", description: "Engine coolant temperature", min: -40, max: 215, unit: "Celsius"}, 374 | {mode: modeRealTime, pid: "6B", bytes: 5, name: "egrt", description: "Exhaust gas recirculation temperature", min: -40, max: 215, unit: "Celsius"}, 375 | {mode: modeRealTime, pid: "6D", bytes: 6, name: "fpc", description: "Fuel pressure control system", min: -40, max: 215, unit: "Celsius"}, 376 | {mode: modeRealTime, pid: "6E", bytes: 5, name: "ipct", description: "Injection pressure control system", min: -40, max: 215, unit: "Celsius"}, 377 | {mode: modeRealTime, pid: "73", bytes: 5, name: "ep", description: "Exhaust pressure", min: -40, max: 215, unit: "Celsius"}, 378 | {mode: modeRealTime, pid: "78", bytes: 9, name: "egt", description: "Exhaust Gas temperature Bank 1", min: -40, max: 215, unit: "Celsius", convertToUseful: convertExhastGasTemperature}, 379 | 380 | 381 | 382 | //DTC's 383 | {mode: modeRequestDTC, pid: undefined, bytes: 6, name: "requestdtc", description: "Requested DTC", convertToUseful: convertDTCRequest}, //n*6 --> For each code, 6 bytes. 384 | {mode: modeClearDTC, pid: undefined, bytes: 0, name: "cleardtc", description: "Clear Trouble Codes (Clear engine light)", convertToUseful: notSupported}, 385 | 386 | //VIN 387 | {mode: modeVin, pid: "00", bytes: 4, name: "vinsupp0", description: "Vehicle Identification Number", convertToUseful: bitDecoder}, 388 | {mode: modeVin, pid: "01", bytes: 1, name: "vin_mscout", description: "VIN message count", convertToUseful: convertVIN_count}, 389 | {mode: modeVin, pid: "02", bytes: 1, name: "vin", description: "Vehicle Identification Number", convertToUseful: convertVIN} 390 | ]; 391 | 392 | var exports = module.exports = responsePIDS; --------------------------------------------------------------------------------