4回答

1收藏

[教程] 基于树莓派 JavaFx 车载系统

Raspberry Pi Raspberry Pi 9044 人阅读 | 4 人回复 | 2013-06-17

本帖最后由 haothree 于 2013-6-17 09:43 编辑

基于树莓派 JavaFx 车载系统(第一部分)

作为我从事的嵌入式Java的工作的一部分,我不断寻找新的方式去给开发者演示嵌入式Java的使用是多么简单,以及它是如何的强大。最近我在eBay上找到一款有趣的设备,并且我觉得它非常有潜力。这是一款具有 ELM327 OBDII CAN bus 诊断接口的扫描仪。这是一个小盒子,可以插到汽车的服务端口,可以让软件与你汽车中的 电子控制单元 (ECUs) 通信。我买的这一款提供了WiFi 连接,也可以通过USB线进行连接。还有一些类似的设备可以提供蓝牙接口,不过我知道的是这些接口用起来并不方便。考虑到它只花费了30英镑多一些,在体验上它还是很值得。

这是这款设备的照片:


这是它插到我的汽车踏板附近的服务端口的照片。


唯一不爽的是由于接口的方向,导致插入后看不到设备的状态灯(至少在没有镜子的情况下是这样)。

我的初衷是看看能从汽车得到什么类型的数据,然后编写一些软件,能实时的显示一些现有的操作无法显示的数据。如果能够记录日志数据用于事后分析就更棒了,就像F1赛车通过大量自动记录的数据的分析让车手了解如何去提高。

由于我打算使用嵌入式Java,处理单元显然用 树莓派(Raspberry Pi) 比较合适一些。树莓派很便宜,我手上就有一堆,他们能提供相当强的计算能力。除此之外,树莓派还有如下优势:
低功耗 (12V的点烟器就能很容易跑起来)
通过 Chalkboard Electronics 生产的不错的触摸屏可以支持 JavaFX (这样的界面真的让人着迷)
易操作的 GPIO 针脚
最后一点让我期待还能为我的车载系统再加点什么功能。最近我的朋友兼同事 Angela Caicedo 在 Devoxx UK 做了一个专题,主题是 "超越之美: JavaFX, 视差, 触摸, 陀螺仪及其他"。专题的一部分包含了使用I2C接口连接树莓派上的运动传感器,她使用的这个独特的传感器来自 Sparkfun ,使用了InvenSense的非常酷的单芯片方案, MPC-6150。这个传感器能提供9轴的运动数据,也即X、Y和Z轴的加速度和角速度,就如同一个全方位工作的罗盘传感器。

根据以前大学学过的物理学 (如银河般久远) ,我依稀记得如果把与汽车质量相关的加速度数据和引擎速度之类的数据组合在一起,就可以计算出引擎产生的马力和扭力。这样一来,这个项目看上去更有趣了。

我最近偶尔发现的一个视频给了我更多灵感:

http://player.youku.com/player.php/sid/XNTcxNzU4Mjk2/v.swf

Tesla 也有一个有趣的东西,他们使用了一个17"的触摸显示屏作为中控终端。

在这篇博文接下来的部分我将详细介绍这个项目( 第二部分)。

转自:http://www.oschina.net/translate/the_raspberry_pi_javafx_in-car-system-part1

分享到:
回复

使用道具 举报

回答|共 4 个

倒序浏览

沙发

haothree

发表于 2013-6-17 09:00:40 | 只看该作者

本帖最后由 haothree 于 2013-6-17 09:17 编辑

基于树莓派 JavaFx 车载系统(第二部分)

在我的 上一篇文章 (按计划,这篇文章早就应该写了) 中我描述了关于车载 Raspberry Pi JavaFX 系统的想法。现在开始做技术方面的事情吧。

首先,我们先简单回顾一下现代汽车电子方面的事。我的第一辆车是1971年的 Mini Clubman.  这车基本没有电子产品(收音机除外),所有的东西基本都是电子机械结合的 (参见 distributor),现在的情况当然有了很大改观。如今,对于汽车,类似防抱死系统(ABS),电子平衡系统(ESC)等等需要用到复杂感应器和电子元件的系统,都是法律要求安装的,至少在欧洲是这样。同时,从2001年起,所有使用汽油的汽车,都必须安装EOBD(欧洲汽车诊断系统)接口。这样就满足了OBD-II标准,我上篇文章里面提到的 ELM327 接口就有了用武之地。

作为一个标准,OBD-II 规定了一些必需的部分,其他的都是可选的。那个就可以确保基本的部件都会安装好(主要是衡量废气排放性能的部件),汽车制造商可以根据各自的需要来制造和安装可选部件。

OBD-II 接口包含了5种信号协议:
SAE J1850 PWM (脉冲宽度调制,  Ford使用)
SAE J1850 VPW (可变脉冲宽度,  General Motors使用)
ISO 9141-2 (有点像 RS-232)
ISO 14230
ISO 15765 (也称为 Controller Area Network, 或 CAN 总线)

你可以把这个看作传输层,汽车制造商可以根据自己需要进行更改。使用信号协议的消息协议在 OBD-II标准中定义,这些命令的格式都是直接接受一系列的十六进制数字对。第一对数字标明了 'mode' (这里是 10); 第二对(有可能还有第三对)数字是 'parameter identification' 也即要发送的PID。mode和PID组合在一起定义了要发给车辆的命令。结果是string格式的字节序列,包含了十六进制数字对的数据编码。

对于我的汽车 Audi S3,协议是 ISO 15765,汽车有多个 CAN 总线,用于不同的控制单元之间的通信(之后会对此详细介绍)。

接下来从哪开始呢?

首先,需要在Java程序和ELM327之间建立通信。对于开发这样的Java程序来说,很不错的地方是可以在笔记本上轻松的开发出程序,然后同样轻松的将代码迁移到目标硬件上。这个过程根本不需要跨平台编译的工具链。

我的ELM327接口通过802.11 (Wi-Fi)来通信。我的接口的地址是192.168.0.11 (对于这类设备这很常见),并且使用端口35000 来进行通信。为了验证地址有效,我在我的MacBook上设置了WiFi网络的静态IP地址,然后直接与在WiFi的设备列表中的ELM327连接。在IP层建立连接好连接后,我就可以通过telnet与ELM327连接。如果你要试一下,建议最好看一下 这份文档,写的很好也很全。ELM327 大致会用到两种通信模式:
AT命令,用来与接口自身通信
OBD命令,遵循上面的说明。ELM327为你做了所有麻烦的事情,会把这些转换成必要的数据包格式,添加数据头和数据校验,然后组装成返回的数据。

为了着手工作,我只是用了返回接口版本的AT I命令和返回当前汽车电瓶电压的AT RV命令。他们通过telnet运行良好,因此是时候开始 开发Java代码了。

为了始终保持简单,我编写了封装到ELM327连接的类。初始化连接的代码见下面,这样我们可以根据需要进行字节的读写了。
  1. /*版权所有归Oracle和或它的分公司所有? 2013,保留所有权利。*/
  2. private static final String ELM327_IP_ADDRESS = "192.168.0.10";
  3.   private static final int ELM327_IP_PORT = 35000;
  4.   private static final byte OBD_RESPONSE = (byte)0x40;
  5.   private static final String CR = "\n";
  6.   private static final String LF = "\r";
  7.   private static final String CR_LF = "\n\r";
  8.   private static final String PROMPT = ">";
  9.   private Socket elmSocket;
  10.   private OutputStream elmOutput;
  11.   private InputStream elmInput;
  12.   private boolean debugOn = false;
  13.   private int debugLevel = 5;
  14.   private byte[] rawResponse = new byte[1024];
  15.   protected byte[] responseData = new byte[1024];
  16.   /**
  17.    * 常见的初始化代码
  18.    *
  19.    * @如果通讯有问题就抛出异常
  20.    */
  21.   private void init() throws IOException {
  22.     /* 建立一个到ELM327端口的套接字,然后创建到这个套接字
  23.      * 的输入和输出流
  24.      */
  25.     try {
  26.       elmSocket = new Socket(ELM327_IP_ADDRESS, ELM327_IP_PORT);
  27.       elmOutput = elmSocket.getOutputStream();
  28.       elmInput = elmSocket.getInputStream();
  29.     } catch (UnknownHostException ex) {
  30.       System.out.println("ELM327: Unknown host, [" + ELM327_IP_ADDRESS + "]");
  31.       System.exit(1);
  32.     } catch (IOException ex) {
  33.       System.out.println("ELM327: IO error talking to car");
  34.       System.out.println(ex.getMessage());
  35.       System.exit(2);
  36.     }
  37.     /* 确保我们已经拥有了输入和输出流 */
  38.     if (elmInput == null || elmOutput == null) {
  39.       System.out.println("ELM327: input or output to device is null");
  40.       System.exit(1);
  41.     }
  42.     /* 最后,发送重置命令道这个套接字,以关闭字符回显
  43.      * (关闭字符回显起什么作用不是很清楚)
  44.      */
  45.     resetInterface();
  46.     sendATCommand("E0");
  47.     debug("ELM327: Connection established.", 1);
  48.   }
复制代码
获得连接后,我们需要一些方法,它能提供用来发送命令和获得返回结果的简单接口。下面是发送信息的常见方法。
  1.   /**
  2.    * Send an AT command to control the ELM327 interface
  3.    *
  4.    * @param command The command string to send
  5.    * @return The response from the ELM327
  6.    * @throws IOException If there is a communication error
  7.    */
  8.   protected String sendATCommand(String command) throws IOException {
  9.     /* Construct the full command string to send.  We must remember to
  10.      * include a carriage return (ASCII 0x0D)
  11.      */
  12.     String atCommand = "AT " + command + CR_LF;
  13.     debug("ELM327: Sending AT command [AT " + command + "]", 1);

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

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

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

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

  37.     byte obdMode = commandBytes[0];

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

  42.     /* Read the response */
  43.     String response = getResponse();

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

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

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

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

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

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

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

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

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

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

  73.     return responseDataLength - 2;
  74.   }

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

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

  90.     return responseLength;
  91.   }

  92. and the method for reading back the results.

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

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

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

  114.       /* 检查提示符 */
  115.       if (data.contains(PROMPT)) {
  116.         debug("ELM327: Got a prompt", 1);
  117.         break;
  118.       }
  119.     }

  120.     /* 去掉换行符、回车符和提示符 */
  121.     String response = responseBuilder.toString();
  122.     response = response.replace(CR, "");
  123.     response = response.replace(LF, "");
  124.     response = response.replace(PROMPT, "");
  125.     return response;
  126.   }
复制代码
使用这些方法将使得开始使用OBD协议的实现方法非常简单。例如为了获得接口的版本信息,我们只需要下面简单的方法:
  1. /**
  2.   * 获得所连接的ELM327的版本号
  3.   *
  4.   * @返回版本号字符串
  5.   * @如果通信有问题就抛出异常
  6.   */
  7. public String getInterfaceVersionNumber() throws IOException {
  8.    return sendATCommand("I");
  9. }
复制代码
另一个非常有用的方法是返回支持给定模式的所有PID的详细信息的方法:
  1. /**
  2.    * Determine which PIDs for OBDII are supported. The OBD standards docs are
  3.    * required for a fuller explanation of these.
  4.    *
  5.    * @参数pid确定汇报的是哪个范围pid
  6.    * @返回一个指明所支持的PID的数组。
  7.    * @如果通信有错误就抛出异常
  8.    */
  9.   public boolean[] getPIDSupport(byte pid) throws IOException, ELM327Exception {
  10.     int dataLength = sendOBDCommand("01 " + String.format("%02X", pid));

  11.     /* 如果零个字节返回,那么我们假设请求的范围没有支持的PID
  12.      */
  13.     if (dataLength == 0)
  14.       return null;

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

  19.     /*现在解码支持的PID所对应的位图 */
  20.     for (int i = 2; i < dataLength; i++)
  21.       for (int j = 0; j < 8; j++) {
  22.         if ((responseData[i] & (1 << j)) != 0)
  23.           pidList[p++] = true;
  24.         else
  25.           pidList[p++] = false;
  26.       }

  27.     return pidList;
  28.   }
复制代码
对于mode为1,PID为0x00, 0x20, 0x40, 0x60, 0x80, 0xA0 和 0xC0,会返回接下来的31个数值是否支持的PID,返回值是4字节的bitmap(译者注:例如,0x20会返回0x21到0x3f这31个PID是否支持,结果放在bitmap的32个bits)。在规范中我只找到截止到0x87的命令。
下一个部分我们将开始用这个类去从汽车获取一些真实的数据。

转自:http://www.oschina.net/translate/the_raspberry_pi_javafx_in-car-system-part2
板凳

ukonline2000

发表于 2013-6-17 15:13:20 | 只看该作者

这个应用不错,不过现在OBDII的手机应用太多了,蓝牙的OBDII也很便宜了
博客站点:http://ukonline2000.com
网店:http://ukonline2000.taobao.com
地板

Tuma_3001931

发表于 2013-6-17 17:39:16 | 只看该作者

期待后续。。。和我的兴趣方向差不多呢。。。我在做外围的东西。。比如传感器的应用:行驶跟车速度提醒啦,变道提醒,还有瞌睡提醒。。。。。
5#

wyyyh

发表于 2013-8-2 20:21:31 | 只看该作者

正在郁闷蓝牙obd与python
您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条