Tutorial: OBD-II

This tutorial demonstrates how to use the OBD-II plugin to access standard information from a vehicle. There are a small set of standard PIDs that are automatically created by the plugin and thus can be accessed immediately, however most PIDs must be defined first before they can be used. This method works for defining other standard PIDs as well as proprietary PIDs.

To read the value of a PID, obdII.OBDIIPID#get is used. This method starts the process of reading the PID and then returns right away. The read will then finish sometime later and uses events to indicate what has happened. The events obdII.OBDIIPID#event:onComplete and obdII.OBDIIPID#event:onFailed are used to indicate whether the read has finished successfully, or failed. If successful, the value is included in the event.

So, to be able to read the value of a PID, the events must first be connected to (the built-in PID for VIN is used here, so there is no need to create it):

$obd.pids.vInfo.vin.onComplete.connect({id: "vin"}, gotPID);
$obd.pids.vInfo.vin.onFailed.connect({id: "vin"}, pidFailed);

In this case, the result is simply printed to the screen, so generic functions are used to handle the result:

function gotPID(value)
{
   document.getElementById(this.id).innerHTML = value;
}

function pidFailed()
{
   document.getElementById(this.id).innerHTML = "FAILED";
}

Once OBD is enabled using obdII.OBDIICAN#enabled, whenever the PID needs to be read, the obdII.OBDIIPID#get method is used:

$obd.pids.vInfo.vin.get();

Other standard PIDs that are not defined can be created by first creating the mode (if it does not already exist, using obdII.OBDIIPIDs#createMode), then creating the PID from the mode (using obdII.OBDIIMode#createNumberPID, or similar methods):

// the 'current' mode already exists, if it didn't it would be created using var currentMode = $obd.pids.createMode(0x01, 'current' true);
var currentMode = $obd.pids.current;
var fuelTrim = currentMode.createNumberPID(0x7DF, 0x06, 1, 100/128, -100, false, false, 'shortTermFuelTrim1');
fuelTrim.onComplete.connect({id: "fuelTrim"}, gotPID);
fuelTrim.onFailed.connect({id: "fuelTrim"}, pidFailed);

The createNumberPID method has quite a few parameters.

The first is the ID of the target, which is typically an ECU address. In this case, OBD-II is used via CAN, but the exact address of the ECU is not known, so the CAN broadcast address (0x7DF) is used. Any ECU that understands the request should then response with the result.

The second argument is the PID. In this case the short term fuel trim for bank 1 is desired, which is mode 0x01, PID 0x06.

The third argument is the number of bytes in the number. Valid values are 8, 4, 2, 1. In this case, the value is a single byte.

The fourth argument is a scale factor that is applied to the raw number received. This allows the result to be returned in the proper units so that the application does not have to convert it.

The fifth argument is the offset, and is related to the scale in that is it used to adjust the raw value before returning it. To convert the fuel trim, the formula is (val * 100/128) - 100. So the scale is set to 100/128 and the offset to -100.

The sixth argument defines if the number is little or big endian. For a single byte value, this does not matter.

The seventh argument defines whether the raw value is a signed or unsigned value. The fuel trim is ultimately a signed value after the conversion, but the conversion assumes that the raw value is unsigned (range is 0 to 255, not -128 to 127), so false is used here.

The last argument is optional, but since it is specified, the PID can now be accessed from the 'current' mode later by name:

var fuelTrim = $obd.pids.current.shortTermFuelTrim1;

String PIDs can also be created using the obdII.OBDIIMode#createStringPID. If neither of these match the required format of the PID, then obdII.OBDIIMode#createRawPID can be used as a last resort, in which case the result would need to be interpreted by the application. Note that once created, PIDs will not be deleted automatically until the page is refreshed. If PIDs are to be re-created for some reason by the application without refreshing, the obdII.OBDIIMode#deletePID should be used to remove unused PIDs.

Here is the full example:

<html>
<head>
<title>For testing OBD-II</title>
<script type="text/javascript">
var $obd;
if (typeof($wat) != 'undefined') {
   $obd = $wat.load('obd-II')[0];
}
if (!$obd) {
   debugger;
   throw new Error("This example must be run on a WAT device with OBD-II");
}

function gotPID(value) {
   document.getElementById(this.id).innerHTML = value;
}

function pidFailed() {
   document.getElementById(this.id).innerHTML = "FAILED";
}

function initOBD() {
   $obd.can.baud = 500;
   $obd.pids.vInfo.vin.onComplete.connect({id: "vin"}, gotPID);
   $obd.pids.vInfo.vin.onFailed.connect({id: "vin"}, pidFailed);

   var currentMode = $obd.pids.current;
   var fuelTrim = currentMode.createNumberPID(0x7DF, 0x06, 1, 100/128, -100, false, false, 'shortTermFuelTrim1');
   fuelTrim.onComplete.connect({id: "fuelTrim"}, gotPID);
   fuelTrim.onFailed.connect({id: "fuelTrim"}, pidFailed);
}

function readState() {
   if ($obd.can.enabled) {
      document.getElementById("enabled").innerHTML = "ENABLED";

      $obd.pids.vInfo.vin.get();
      $obd.pids.current.shortTermFuelTrim1.get();
   } else {
      document.getElementById("enabled").innerHTML = "NOT ENABLED";
   }
}

function toggleEnable() {
   $obd.can.enabled = !$obd.can.enabled;
   readState();
}

function onLoad() {
   initOBD();
   readState();
}
</script>

</head>
<body onload="onLoad();">
   <input type="button" value="OBD Enable" onclick="toggleEnable();"
      style="height: 50px" />
   <div id="enabled"></div>
   <table>
      <tr>
         <td>VIN</td>
         <td id="vin"></td>
      </tr>
      <tr>
         <td>Fuel Trim (%)</td>
         <td id="fuelTrim"></td>
      </tr>
   </table>
</body>
</html>