Clarify when we are using int vs float and move decoders up a level.
[apps/low-level-can-service.git] / README.mkd
index 96b969e..8f9574f 100644 (file)
-OBD-II Support Library in C
-=============================
+Unified Diagnostic Services (UDS) Support Library in C
+======================================================
+
+This is a platform agnostic C library that implements the Unified Diagnostics
+Services protocol for automotive electronics. UDS is documented in ISO 14229 and
+is the underpinning for the more well-known On-board Diagnostics (OBD) standard.
+The library currently supports UDS running over CAN (ISO 15765-4), which uses
+the ISO-TP (ISO 15765-2) protocol for message framing.
+
+This library doesn't assume anything about the source of your diagnostic message
+requests or underlying interface to the CAN bus. It uses dependency injection to
+give you complete control.
+
+## Usage
+
+First, create some shim functions to let this library use your lower level
+system:
+
+    // required, this must send a single CAN message with the given arbitration
+    // ID (i.e. the CAN message ID) and data. The size will never be more than 8
+    // bytes.
+    bool send_can(const uint16_t arbitration_id, const uint8_t* data,
+            const uint8_t size) {
+        ...
+    }
 
-TODO isotp needs to accept responses on an ID other that the request's arb id -
-or maybe we have 2 isotp instances, for rx and tx.
+    // optional, provide to receive debugging log messages
+    void debug(const char* format, ...) {
+        ...
+    }
 
-* Response's arb id is assigned ID plus 0x8
-* Functional daignostic request (Broadcast), 0x7df arb id
-* Standard ECU requests to 0x7e0 - 0x7e7
 
-* Some requests don't need PIDs - e.g. mode 3
-    * some responses don't have PIDs, just payload - e.g. mode 3
+    // not used in the current version
+    void set_timer(uint16_t time_ms, void (*callback)) {
+        ...
+    }
 
-* Service ID + 0x40 == positive response
-* Response includes PID echo - use this to match up request/response
-* Response ID, ISO-TP PCI, ISO-TP length, service response ID, PID echo, data
+With your shims in place, create a `DiagnosticShims` object to pass them around:
 
-I send the request and give a callback
-when the response arrives for the matchin service and PID - on any arb id, since
-this may be a broadcast - call my callback.
+    DiagnosticShims shims = diagnostic_init_shims(debug, send_can, set_timer);
 
+With your shims in hand, send a simple PID request to the standard broadcast
+address, `0x7df` (we use the constant `OBD2_FUNCTIONAL_BROADCAST_ID` here):
 
-TODO do you want to provide the callback to the Handler, or to each
-individual send?o
-ok, what are the use cases here.
+    // Optional: This is your callback that will be called the response to your
+    // diagnostic request is received.
+    void response_received_handler(const DiagnosticResponse* response) {
+        // You received a response! Do something with it.
+    }
 
-you're going to request a few PIDs over and over again at some frequency
-you're going to request DTCs once and read the response
-you're going to clear DTCs once
+    DiagnosticRequestHandle handle = diagnostic_request_pid(&shims,
+            DIAGNOSTIC_STANDARD_PID, // this is a standard PID request, not an extended or enhanced one
+            OBD2_FUNCTIONAL_BROADCAST_ID, // the request is going out to the broadcast arbitration ID
+            0x2, // we want PID 0x2
+            response_received_handler); // our callback (optional, use NULL if you don't have one)
+
+    if(handle.completed) {
+        if(!handle.success) {
+            // something happened and it already failed - possibly we aren't
+            // able to send CAN messages
+            return;
+        } else {
+            // this should never occur right away - you need to receive a fresh
+            // CAN message first
+        }
+    } else {
+        while(true) {
+            // Continue to read from CAN, passing off each message to the handle.
+            // This will return a 'completed' DiagnosticResponse when the when
+            // the request is completely sent and the response is received
+            // (which may take more than 1 CAN frames)
+            DiagnosticResponse response = diagnostic_receive_can_frame(&shims,
+                &handle, can_message_id, can_data, sizeof(can_data));
+
+            if(response.completed && handle.completed) {
+                if(handle.success) {
+                  if(response.success) {
+                      // The request was sent successfully, the response was
+                      // received successfully, and it was a positive response - we
+                      // got back some data!
+                  } else {
+                      // The request was sent successfully, the response was
+                      // received successfully, BUT it was a negative response
+                      // from the other node.
+                      printf("This is the error code: %d", response.negative_response_code);
+                  }
+                } else {
+                    // Some other fatal error ocurred - we weren't able to send
+                    // the request or receive the response. The CAN connection
+                    // may be down.
+                }
+            }
+        }
+    }
 
-i'd rather use the same diagnostic handler to send all of the differet messages
-rather than create one for each use. that way ai can
+### Requests for other modes
 
-but that does complicate the memory management because it'll need to create
-some internal data structures.
+If you want to do more besides PID requests on mode 0x1 and 0x22, there's a
+lower level API you can use. Here's how to make a mode 3 request to get DTCs.
 
-at the simplest, what does this handler have to do?
+    DiagnosticRequest request = {
+        arbitration_id: OBD2_FUNCTIONAL_BROADCAST_ID,
+        mode: OBD2_MODE_EMISSIONS_DTC_REQUEST
+    };
+    DiagnosticRequestHandle handle = diagnostic_request(&SHIMS, &request, NULL);
+
+    if(handle.completed) {
+        if(!handle.success) {
+            // something happened and it already failed - possibly we aren't
+            // able to send CAN messages
+            return;
+        } else {
+            // this should never occur right away - you need to receive a fresh
+            // CAN message first
+        }
+    } else {
+        while(true) {
+            // Continue to read from CAN, passing off each message to the handle.
+            // This will return a 'completed' DiagnosticResponse when the when
+            // the request is completely sent and the response is received
+            // (which may take more than 1 CAN frames)
+            DiagnosticResponse response = diagnostic_receive_can_frame(&shims,
+                &handle, can_message_id, can_data, sizeof(can_data));
+
+            if(response.completed && handle.completed) {
+                if(handle.success) {
+                  if(response.success) {
+                      // The request was sent successfully, the response was
+                      // received successfully, and it was a positive response - we
+                      // got back some data!
+                      printf("The DTCs are: ");
+                      for(int i = 0; i < response.payload_length; i++) {
+                        printf("0x%x ", response.payload[i]);
+                      }
+                  } else {
+                      // The request was sent successfully, the response was
+                      // received successfully, BUT it was a negative response
+                      // from the other node.
+                      printf("This is the error code: %d", response.negative_response_code);
+                  }
+                } else {
+                    // Some other fatal error ocurred - we weren't able to send
+                    // the request or receive the response. The CAN connection
+                    // may be down.
+                }
+            }
+        }
+    }
 
-* store the request arb id, mode, pid, and payload locally
-* send a can message
-* get all new can messages passed to it
-* Check the incoming can message to see if it matches one of the standard ECU
-  response IDs, or our arb ID + 0x8
-* if it matches, parse the diagnostic response and call the callback
+## Dependencies
 
-that seems pretty simple and not worth greatly increasing the internal state to
-handle multiple requests
+This library requires 2 dependencies:
 
-what we need is another layer on top of that to handle the repeated requests.
+* [isotp-c](https://github.com/openxc/isotp-c)
+* [bitfield-c](https://github.com/openxc/bitfield-c)
 
-## API
+## Testing
 
-    void my_callback(const DiagnosticResponse* response) {
-    }
+The library includes a test suite that uses the `check` C unit test library.
 
-    DiagnosticRequestHandler handler = diagnostic_init(my_callback);
-    DiagnosticRequest request = {
-        arbitratin_id: 0x7df,
-        mode: OBD2_MODE_POWERTRAIN_DIAGNOSTIC_REQUEST,
-        pid: 3
-    };
+    $ make test
 
-    diagnostic_send(&handler, &request);
-    while(true) {
-        diagnostic_handle_can_frame(&handler, 42, data, 8);
-    }
+You can also see the test coverage if you have `lcov` installed and the
+`BROWSER` environment variable set to your choice of web browsers:
 
-    diagnostic_request_pid(&handler, DIAGNOSTIC_STANDARD_PID, 42
+    $ BROWSER=google-chrome-stable make coverage
 
-    diagnostic_destroy(&handler);
+## OBD-II Basics
 
-## Testing
+TODO diagram out a request, response and error response
 
-The library includes a test suite that uses the `check` C unit test library.
+* store the request arb id, mode, pid, and payload locally
+* send a can message
+* get all new can messages passed to it
+* Check the incoming can message to see if it matches one of the standard ECU
+  response IDs, or our arb ID + 0x8
+* if it matches, parse the diagnostic response and call the callback
 
-    $ make test
+
+## Future Notes
+
+you're going to request a few PIDs over and over again at some frequency
+you're going to request DTCs once and read the response
+you're going to clear DTCs once
+
+we need another layer on top of that to handle the repeated requests.
 
 ## Authors