Friday Jun 28, 2013

The Raspberry Pi JavaFX In-Car System (Part 3)

Ras Pi car pt3 Having established communication between a laptop and the ELM327 it's now time to bring in the Raspberry Pi.

One of the nice things about the Raspberry Pi is the simplicity of it's power supply.  All we need is 5V at about 700mA, which in a car is as simple as using a USB cigarette lighter adapter (which is handily rated at 1A).  My car has two cigarette lighter sockets (despite being specified with the non-smoking package and therefore no actual cigarette lighter): one in the centre console and one in the rear load area.  This was convenient as my idea is to mount the Raspberry Pi in the back to minimise the disruption to the very clean design of the Audi interior.

The first task was to get the Raspberry Pi to communicate using Wi-Fi with the ELM 327.  Initially I tried a cheap Wi-Fi dongle from Amazon, but I could not get this working with my home Wi-Fi network since it just would not handle the WPA security no matter what I did.  I upgraded to a Wi Pi from Farnell and this works very well.

The ELM327 uses Ad-Hoc networking, which is point to point communication.  Rather than using a wireless router each connecting device has its own assigned IP address (which needs to be on the same subnet) and uses the same ESSID.  The settings of the ELM327 are fixed to an IP address of 192.168.0.10 and useing the ESSID, "Wifi327".  To configure Raspbian Linux to use these settings we need to modify the /etc/network/interfaces file.  After some searching of the web and a few false starts here's the settings I came up with:

auto lo eth0 wlan0

iface lo inet loopback

iface eth0 inet static
    address 10.0.0.13
    gateway 10.0.0.254
    netmask 255.255.255.0

iface wlan0 inet static
    address 192.168.0.1
    netmask 255.255.255.0
    wireless-essid Wifi327
    wireless-mode ad-ho0


After rebooting, iwconfig wlan0 reported that the Wi-Fi settings were correct.  However, ifconfig showed no assigned IP address.  If I configured the IP address manually using ifconfig wlan0 192.168.0.1 netmask 255.255.255.0 then everything was fine and I was able to happily ping the IP address of the ELM327.  I tried numerous variations on the interfaces file, but nothing I did would get me an IP address on wlan0 when the machine booted.  Eventually I decided that this was a pointless thing to spend more time on and so I put a script in /etc/init.d and registered it with update-rc.d.  All the script does (currently) is execute the ifconfig line and now, having installed the telnet package I am able to telnet to the ELM327 via the Raspberry Pi.  Not nice, but it works.

Here's a picture of the Raspberry Pi in the car for testing

In Car

In the next part we'll look at running the Java code on the Raspberry Pi to collect data from the car systems.






Friday Jun 14, 2013

Java and the Raspberry Pi Camera (Part 1)

Using the Raspberry Pi Camera with Java I've always liked the idea of computer vision and on the very long list of things I'd like to spend more time exploring is the OpenCV libraries which have a handy set of Java bindings.  In the past I've experimented with, and used some of the other frameworks that are available for image capture in Java, specifically the Java Media Framework (JMF) and the Freedom for Media in Java (FMJ), mostly around the idea of integrating images from a webcam into an application like a security monitoring system.  Sadly, JMF has grown a little dusty over time with the last release being way back in 2002 (you have to be amused when you see that the hardware requirements for this are a 166MHz Pentium processor and 32Mb of RAM).  FMJ is a little more modern, but was last updated in 2007.

The Raspberry Pi Foundation recently announced the launch of a camera that plugs into one of the two ribbon cable connectors on the board (as shown below):

Raspberry Pi Camera

I thought it would be an interesting idea to see how easy it would be to get this working with a Java or JavaFX application.

There are three utilities that are available for testing the camera: raspistill, raspiyuv and raspivid.  These allow you to grab a frame or video from the camera and store it in a file.  This seemed to be a good starting point for figuring out how to use the camera and get the frame data into a Java application, ideally as a BufferedImage (I decided to start with simple image capture and look at video streams later).

I downloaded the code from github and started looking at what it does and how it works.  Initially I thought it would make to sense to use a toolchain to cross compile the code on my quad-core Linux box.  However, having spent a day working on this and failed to get the code to compile cleanly (even using the download of the Raspberry Pi org's toolchain) I decided it might be slower on the Raspberry Pi, but at least it worked.

I also found a useful post from Tasanakorn Phaipool who had created a couple of sample applications that made use of the camera and linked to the OpenCV libraries.  This provided a good starting point as it simplified things compared to the raspistill application and enabled me to figure out a relatively simple build environment (I don't have time right now to climb the learning curve required for cmake).

Getting the code to compile and run was really quite challenging.  I will confess it's been a while since I've done any C coding, but more of the issues I experieced were to do with getting the build process to work correctly.  I used an iterative approach to creating a Makefile, simply resolving issues as I found them, gradually adding header file references and libraries until the code compiled cleanly.  To use the camera we need the multi-media abstraction layer (MMAL) API.  Broadcom have very kindly made this available as source, but documentation-wise you pretty much have to dig through the source code (there is a big comment at the top of the mmal.h file which is the best documentation I've found so far).  Once I'd got the code to compile and link it still would not run, which puzzled me for quite some time until, by comparing the raspistill executable to the one I'd built, I found that I needed to include the libmmal_vc_client.so in the list of libraries to link.  (This really does confuse me because this library is not required to resolve any function references so the code compiles and links correctly, but without it the necessary camera configuration is not registered and the call to mmal_component_create() will fail).

At this point I have some code that will talk to the camera and display the preview image on the video output (HDMI).  Next I need to modify this so it can be used with JNI and integrate this with a new subclass of ImageInputStream which can then be used to create a BufferedImage in a Java application.

One other thing that is interesting is that when I run the simple test program the preview is displayed and very shortly after the network stops working (all the LEDs on the Pi except the power light go out).  I assume that is a bug somewhere.  Fortunately, I have a serial console connected so can still access the Pi via PuTTY.

I will update my blog as I make more progress on this.

Wednesday Jun 12, 2013

The Raspberry Pi JavaFX In-Car System (Part 2)

Raspberry Pi JavaFX Car Pt2 In my last post (which was rather further back in time than I had planned) I described the ideas behind my in-car Raspberry Pi JavaFX system.  Now it's time to get started on the technical stuff.

First, we need a short review of modern car electronics.  Things have certainly moved on from my first car, which was a 1971 Mini Clubman.  This didn't even have electronics in it (unless you count the radio), as everything was electro-mechanical (anyone remember setting the gap for the points on the distributor?)  Today, in Europe at least, things like anti-lock brakes (ABS) and stability control (ESC) which require complex sensors and electronics are mandated by law.  Also, since 2001, all petrol driven vehicles have to be fitted with an EOBD (European On-Board Diagnostics) interface.  This conforms to the OBD-II standard which is where the ELM327 interface from my first blog entry comes in. 

As a standard, OBD-II mandates some parts while other parts are optional.  That way certain basic facilities are guaranteed to be present (mainly those that are related to the measuring of exhaust emission performance) and then each car manufacturer can implement the optional parts that make sense for the vehicle they're building. 

There are five signal protocols that can be used with the OBD-II interface:
  • SAE J1850 PWM (Pulse-width modulation, used by Ford)
  • SAE J1850 VPW (Variable pulse-width, used by General Motors)
  • ISO 9141-2 (which is a bit like RS-232)
  • ISO 14230
  • ISO 15765 (also referred to as Controller Area Network, or CAN bus)
You can think of this as the transport layer, which can be changed by the car manufacturer to suit their needs.  The message protocol which uses the signal protocol is defined by the OBD-II standard.  The format of these commands is pretty straightforward requiring a sequence  of pairs of hexadecimal digits.  The first pair indicates the 'mode' (of which there are 10); the second, and possibly third, pair indicates the 'parameter identification' or PID being sent.  The mode and PID combination defines the command that you are sending to the vehicle.  Results are returned as a sequence of bytes that form a string containing pairs of hexadecimal digits encoding the data.

For my current vehicle, which is an Audi S3, the protocol is ISO 15765 as the car has multiple CAN buses for communication between the various control units (we'll come back to this in more detail later).

So where to start?

The first thing that is necessary is to establish communication between a Java application and the ELM327.  One of the great things about using Java for an application like this is that the development can easily be done on a laptop and the production code moved easily to the target hardware.  No cross compilation tool chains needed here, thank you.

My ELM327 interface communicates via 802.11 (Wi-Fi).  The address of my interface is 192.168.0.11 (which seems pretty common for these devices) and uses port 35000 for all communication.  To test that things are working I set my MacBook to use a static IP address on Wi-Fi and then connected directly to the ELM327 which appeared in the list of available Wi-Fi devices.  Having established communication at the IP level I could then telnet into the ELM327.  If you want to start playing with this it's best to get hold of the documentation, which is really well written and complete.  The ELM327 essentially uses two modes of communication:
  • AT commands for talking to the interface itself
  • OBD commands that conform to the description above.  The ELM327 does all the hard work of  converting this to the necessary packet format, adding headers, checksums and so on as well as unmarshalling the  response data.
To start with I just used the AT I command which reports back the version of the interface and AT RV which gives the current car battery voltage.  These worked fine via telnet, so it was time to start developing the Java code. 

To keep things simple I wrote a class that would encapsulate the connection to the ELM327.  Here's the code that initialises the connection so that we can read and write bytes, as required

  /* Copyright © 2013, Oracle and/or its affiliates. All rights reserved. */

  private static final String ELM327_IP_ADDRESS = "192.168.0.10";
  private static final int ELM327_IP_PORT = 35000;
  private static final byte OBD_RESPONSE = (byte)0x40;
  private static final String CR = "\n";
  private static final String LF = "\r";
  private static final String CR_LF = "\n\r";
  private static final String PROMPT = ">";

  private Socket elmSocket;
  private OutputStream elmOutput;
  private InputStream elmInput;
  private boolean debugOn = false;
  private int debugLevel = 5;
  private byte[] rawResponse = new byte[1024];
  protected byte[] responseData = new byte[1024];

  /**
   * Common initialisation code
   *
   * @throws IOException If there is a communications problem
   */
  private void init() throws IOException {
    /* Establish a socket to the port of the ELM327 box and create
     * input and output streams to it
     */
    try {
      elmSocket = new Socket(ELM327_IP_ADDRESS, ELM327_IP_PORT);
      elmOutput = elmSocket.getOutputStream();
      elmInput = elmSocket.getInputStream();
    } catch (UnknownHostException ex) {
      System.out.println("ELM327: Unknown host, [" + ELM327_IP_ADDRESS + "]");
      System.exit(1);
    } catch (IOException ex) {
      System.out.println("ELM327: IO error talking to car");
      System.out.println(ex.getMessage());
      System.exit(2);
    }

    /* Ensure we have an input and output stream */
    if (elmInput == null || elmOutput == null) {
      System.out.println("ELM327: input or output to device is null");
      System.exit(1);
    }

    /* Lastly send a reset command to and turn character echo off
     * (it's not clear that turning echo off has any effect)
     */
    resetInterface();
    sendATCommand("E0");
    debug("ELM327: Connection established.", 1);
  }


Having got a connection we then need some methods to provide a simple interface for sending commands and getting back the results.  Here's the common methods for sending messages.

  /**
   * Send an AT command to control the ELM327 interface
   *
   * @param command The command string to send
   * @return The response from the ELM327
   * @throws IOException If there is a communication error
   */
  protected String sendATCommand(String command) throws IOException {
    /* Construct the full command string to send.  We must remember to
     * include a carriage return (ASCII 0x0D)
     */
    String atCommand = "AT " + command + CR_LF;
    debug("ELM327: Sending AT command [AT " + command + "]", 1);

    /* Send it to the interface */
    elmOutput.write(atCommand.getBytes());
    debug("ELM327: Command sent", 1);
    String response = getResponse();

    /* Delete the command, which may be echoed back */
    response = response.replace("AT " + command, "");
    return response;
  }

  /**
   * Send an OBD command to the car via the ELM327.
   *
   * @param command The command as a string of hexadecimal values
   * @return The number of bytes returned by the command
   * @throws IOException If there is a problem communicating
   */
  protected int sendOBDCommand(String command)
      throws IOException, ELM327Exception {
    byte[] commandBytes = byteStringToArray(command);

    /* A valid OBD command must be at least two bytes to indicate the mode
     * and then the information request
     */
    if (commandBytes.length < 2)
      throw new ELM327Exception("ELM327: OBD command must be at least 2 bytes");

    byte obdMode = commandBytes[0];

    /* Send the command to the ELM327 */
    debug("ELM327: sendOBDCommand: [" + command + "], mode = " + obdMode, 1);
    elmOutput.write((command + CR_LF).getBytes());
    debug("ELM327: Command sent", 1);

    /* Read the response */
    String response = getResponse();

    /* Remove the original command in case that gets echoed back */
    response = response.replace(command, "");
    debug("ELM327: OBD response = " + response, 1);

    /* If there is NO DATA, there is no data */
    if (response.compareTo("NO DATA") == 0)     
      return 0;

    /* Trap error message from CAN bus */
    if (response.compareTo("CAN ERROR") == 0)
      throw new ELM327Exception("ELM327: CAN ERROR detected");

    rawResponse = byteStringToArray(response);
    int responseDataLength = rawResponse.length;

    /* The first byte indicates a response for the request mode and the
     * second byte is a repeat of the PID.  We test these to ensure that
     * the response is of the correct format
     */
    if (responseDataLength < 2)
      throw new ELM327Exception("ELM327: Response was too short");

    if (rawResponse[0] != (byte)(obdMode + OBD_RESPONSE))
      throw new ELM327Exception("ELM327: Incorrect response [" +
          String.format("%02X", responseData[0]) + " != " +
          String.format("%02X", (byte)(obdMode + OBD_RESPONSE)) + "]");

    if (rawResponse[1] != commandBytes[1])
      throw new ELM327Exception("ELM327: Incorrect command response [" +
          String.format("%02X", responseData[1]) + " != " +
          String.format("%02X", commandBytes[1]));

    debug("ELM327: byte count = " + responseDataLength, 1);

    for (int i = 0; i < responseDataLength; i++)
      debug(String.format("ELM327: byte %d = %02X", i, rawResponse[i]), 1);

    responseData = Arrays.copyOfRange(rawResponse, 2, responseDataLength);

    return responseDataLength - 2;
  }

  /**
   * Send an OBD command to the car via the ELM327. Test the length of the
   * response to see if it matches an expected value
   *
   * @param command The command as a string of hexadecimal values
   * @param expectedLength The expected length of the response
   * @return The length of the response
   * @throws IOException If there is a communication error or wrong length
   */
  protected int sendOBDCommand(String command, int expectedLength)
      throws IOException, ELM327Exception {
    int responseLength = this.sendOBDCommand(command);

    if (responseLength != expectedLength)     
      throw new IOException("ELM327: sendOBDCommand: bad reply length ["
          + responseLength + " != " + expectedLength + "]");

    return responseLength;
  }


and the method for reading back the results.

  /**
   * Get the response to a command, having first cleaned it up so it only
   * contains the data we're interested in.
   *
   * @return The response data
   * @throws IOException If there is a communications problem
   */
  private String getResponse() throws IOException {
    boolean readComplete = false;
    StringBuilder responseBuilder = new StringBuilder();

    /* Read the response.  Sometimes timing issues mean we only get part of
     * the message in the first read.  To ensure we always get all the intended
     * data (and therefore do not get confused on the the next read) we keep
     * reading until we see a prompt character in the data.  That way we know
     * we have definitely got all the response.
     */
    while (!readComplete) {
      int readLength = elmInput.read(rawResponse);
      debug("ELM327: Response received, length = " + readLength, 1);

      String data = new String(Arrays.copyOfRange(rawResponse, 0, readLength));
      responseBuilder.append(data);

      /* Check for the prompt */
      if (data.contains(PROMPT)) {
        debug("ELM327: Got a prompt", 1);
        break;
      }
    }

    /* Strip out newline, carriage return and the prompt */
    String response = responseBuilder.toString();
    response = response.replace(CR, "");
    response = response.replace(LF, "");
    response = response.replace(PROMPT, "");
    return response;
  }


Using these methods it becomes pretty simple to implement methods that start to expose the OBD protocol.  For example to get the version information about the interface we just need this simple method:

  /**
   * Get the version number of the ELM327 connected
   *
   * @return The version number string
   * @throws IOException If there is a communications problem
   */
  public String getInterfaceVersionNumber() throws IOException {
    return sendATCommand("I");
  }


Another very useful method is one that returns the details about which of the PIDs are supported for a given mode.

  /**
   * Determine which PIDs for OBDII are supported. The OBD standards docs are
   * required for a fuller explanation of these.
   *
   * @param pid Determines which range of PIDs support is reported for
   * @return An array indicating which PIDs are supported
   * @throws IOException If there is a communication error
   */
  public boolean[] getPIDSupport(byte pid) throws IOException, ELM327Exception {
    int dataLength = sendOBDCommand("01 " + String.format("%02X", pid));

    /* If we get zero bytes back then we assume that there are no
     * supported PIDs for the requested range
     */
    if (dataLength == 0)
      return null;

    int pidCount = dataLength * 8;
    debug("ELM327: pid count = " + pidCount, 1);
    boolean[] pidList = new boolean[pidCount];
    int p = 0;

    /* Now decode the bit map of supported PIDs */
    for (int i = 2; i < dataLength; i++)
      for (int j = 0; j < 8; j++) {
        if ((responseData[i] & (1 << j)) != 0)
          pidList[p++] = true;
        else
          pidList[p++] = false;
      }

    return pidList;
  }


The PIDs 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0 and 0xC0 of mode 1 will report back the supported PIDs for the following 31 values as a four byte bit map.  There appear to only be definitions for commands up to 0x87 in the specification I found.

In the next part we'll look at how we can start to use this class to get some real data from the car.

About

A blog covering aspects of Java SE, JavaFX and embedded Java that I find fun and interesting and want to share with other developers. As part of the Developer Outreach team at Oracle I write a lot of demo code and I use this blog to highlight useful tips and techniques I learn along the way.

Search

Categories
Archives
« June 2013 »
SunMonTueWedThuFriSat
      
1
2
3
4
5
6
7
8
9
10
11
13
15
16
17
18
19
20
21
22
23
24
25
26
27
29
30
      
Today