Git Product home page Git Product logo

elmobd's Introduction

README

https://img.shields.io/badge/status-active-green.svg https://travis-ci.org/rzetterberg/elmobd.svg?branch=master https://goreportcard.com/badge/github.com/rzetterberg/elmobd?status.svg https://godoc.org/github.com/rzetterberg/elmobd?status.svg

Version
0.8.0

Go library for communicating with cars OBD-II system using ELM327 based USB-devices.

To make this library as good as possible - feedback, bug reports and feature requests are very welcome in the GitHub issues of this project.

How it works

There are more than 10 different OBD-II signal protocol variations used by the various cars that exist. To avoid having to handle all the details of these protocols the ELM327 exists. The ELM327 acts a facade between the computer and the car. You talk to the ELM327 using a simple text based protocol similar to the Hayes command set and the ELM327 takes care of the communication details of the car.

docs/assets/overview-diagram.png

As shown in the diagram above this library connects to a serial device of the operating system. The library is not concerned with what is connected to that serial device, whether it’s a bluetooth USB-dongle with a ELM327 at the other end or a ELM327 connected directly via an USB-cable.

Communicating with the ELM327 is similar to communicating with a web server. You make a request and wait for a response. However, in this context we are calling a command and waiting for one or more responses.

This library is designed to be used in a way that resembles the way you physically use the device. You have a type called Device that represents a ELM327 device connected to the computer. This Device then has a function called RunCommand that sends a command to the actual device and then waits for a response.

This library aims to be as type safe as possible, which means that you don’t deal with raw text commands, instead you have different command types.

All command types need to implement the OBDCommand interface to be able to be run on the device. Since there are A LOT of OBD commands, you can easily extend this library, by just implementing the OBDCommand interface of your commands.

Let’s start by looking at some example of how you use the library.

Example usage

Note: these examples are performed on Linux. If you are using another platform there should be minimal changes, but they are not documented yet. Go ahead and put a 👍 on issue #11 if you think this should be prioritized.

First of all, you need to plug in your ELM327 device into your computer and get the path to the device. You can plugin the device and check dmesg, this is what I get on my computer:

$ dmesg | tail
[359720.858480] usb 6-2: Manufacturer: FTDI
[359720.858482] usb 6-2: SerialNumber: A503GJEX
[359720.897717] usbcore: registered new interface driver usbserial
[359720.897733] usbcore: registered new interface driver usbserial_generic
[359720.897748] usbserial: USB Serial support registered for generic
[359720.901755] usbcore: registered new interface driver ftdi_sio
[359720.901767] usbserial: USB Serial support registered for FTDI USB Serial Device
[359720.901839] ftdi_sio 6-2:1.0: FTDI USB Serial Device converter detected
[359720.901913] usb 6-2: Detected FT232RL
[359720.904481] usb 6-2: FTDI USB Serial Device converter now attached to ttyUSB0

Now that I know that the device is available at /dev/ttyUSB0 I can use the library to connect to the device and check the ELM327 version of the device:

example1.go

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewTestDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	version, err := dev.GetVersion()

	if err != nil {
		fmt.Println("Failed to get version", err)
		return
	}

	fmt.Println("Device has version", version)
}

Note: These examples uses the function NewTestDevice, which uses a mocked ELM327 device. To use a real ELM327 device, you instead use NewDevice. The reason why a mocked device is used is because the examples should be runnable without using a real device.

$ go run example.go
Device has version OBDII by [email protected]

The next step is to run some OBD commands on the device. For this we need to plug in the ELM327 into our car and turn on the ignition.

Like mentioned before you use the function RunCommand that accepts a OBDCommand to run. A OBDCommand has 3 responsibilities:

  • Tell the ELM327 what command to run
  • Store the value
  • Convert the value to a common format

So you start out by creating a new OBDCommand that does not contain a value. You then take that OBDCommand and call the RunCommand function with it. RunCommand will then return the OBDCommand with the value from the car.

Let’s try this out by checking the RPM of the engine. There is a OBDCommand for that defined in the library already, called EngineRPM. We start by creating a new EngineRPM that we call RunCommand with:

example2.go

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewTestDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	rpm, err := dev.RunOBDCommand(elmobd.NewEngineRPM())

	if err != nil {
		fmt.Println("Failed to get rpm", err)
		return
	}

	fmt.Printf("Engine spins at %s RPMs\n", rpm.ValueAsLit())
}

There are more than 180 different OBD commands, and cars have different support for these commands. So to avoid sending OBD commands to the car that it does not support we can check what commands the car support:

example3.go

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewTestDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	supported, err := dev.CheckSupportedCommands()

	if err != nil {
		fmt.Println("Failed to check supported commands", err)
		return
	}

	rpm := elmobd.NewEngineRPM()

	if supported.IsSupported(rpm) {
		fmt.Println("The car supports checking RPM")
	} else {
		fmt.Println("The car does NOT supports checking RPM")
	}
}

The supported here is a SupportedCommands which is a special type that stores the raw lookup table and exposes two helper functions that reads this table:

IsSupported
Check if given command is supported
FilterSupported
Filters out supported commands from given list

For simplicity there’s a function called GetSensorCommands which gives you a list of all the commands defined in the library. You can use this list of commands and filter out what commands are supported on by car:

example4.go

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewTestDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	supported, err := dev.CheckSupportedCommands()

	if err != nil {
		fmt.Println("Failed to check supported commands", err)
		return
	}

	allCommands := elmobd.GetSensorCommands()
	carCommands := supported.FilterSupported(allCommands)

	fmt.Printf("%d of %d commands supported:\n", len(carCommands), len(allCommands))

	for _, cmd := range carCommands {
		fmt.Printf("- %s supported\n", cmd.Key())
	}
}

Besides checking sensor values, you can also check whether the MIL is on and if there are any DTCs:

example5.go

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewTestDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	cmd, err := dev.RunOBDCommand(elmobd.NewMonitorStatus())

	if err != nil {
		fmt.Println("Failed to get monitor status", err)
		return
	}

        status := cmd.(*elmobd.MonitorStatus)

	fmt.Printf("MIL is on: %t, DTCamount: %d\n", status.MilActive, status.DtcAmount)
}

Please see the godocs for a more detailed explanation of the library and it’s structure.

Features

  • [X] Reading sensor data
  • [ ] Reading trouble codes
  • [ ] Resetting Check Engine Light
  • [ ] Reading freezed sensor data

Roadmap

The project uses quarterly milestones to plan upcoming changes. The current quarter will focus on implementing new features. To see the details of what will be done see the milestone 2018 Q3.

Changes of the library are tracked in the CHANGELOG.

Compability

Platforms

The library has been built and tested on the following platforms:

Operating systemGo version
Linux 4.9.25 x86_641.9

Cars

The library has been used successfully on the following cars:

CarLibrary versionTester
Lexus IS200 Manual 20040.3.0@rzetterberg
Ford Ka 20110.5.0@Enrico204
Ford Transit Automat 20190.6.0@mikspec

elmobd's People

Contributors

andrerenaud avatar enrico204 avatar hamburgertrain avatar rzetterberg avatar samifruit514 avatar stv0g avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

elmobd's Issues

Check supported commands results in error

Description

When issuing the command CheckSupportedCommands(), an error is returned: 'NO DATA' received, timeout from elm device?. This is the log with the debug set:

=======================================
 Ran command "ATSP0" in 20.79919ms
 Spent 69.911µs writing
 Spent 20.703157ms reading
=======================================
[2018-08-13T19:37:43.783+02:00] [OBD] Opened new device, path /dev/rfcomm0
=======================================
 Ran command "AT@1" in 21.307076ms
 Spent 672.293µs writing
 Spent 20.604472ms reading
=======================================
[2018-08-13T19:37:43.805+02:00] [OBD] Device has version: OBDII to RS232 Interpreter
=======================================
 Ran command "01001" in 354.113666ms
 Spent 175.093µs writing
 Spent 353.912592ms reading
=======================================
=======================================
 Ran command "01201" in 30.687266ms
 Spent 63.975µs writing
 Spent 30.597589ms reading
=======================================
=======================================
 Ran command "01401" in 30.944003ms
 Spent 359.893µs writing
 Spent 30.556803ms reading
=======================================
=======================================
 Ran command "01601" in 217.68309ms
 Spent 219.861µs writing
 Spent 217.437458ms reading
=======================================
[2018-08-13T19:37:44.441+02:00] [OBD] Check supported command error: 'NO DATA' received, timeout from elm device?

(rows with timestamp are added by me)

Environment

  • Operating system: Linux 4.9.0 x86
  • Go version: 1.10.3 linux/386
  • Git commit: 657bb55
  • ELM327 device version: OBDII to RS232 Interpreter
  • Car make: Ford
  • Car model: Ka
  • Car production year: 2011

EL327 wifi

is that any future update to connect EL327 Wifi dongle?

Support connection over TCP

I have an ELM dongle which provides connectivity to the ELM chip via Wifi + TCP.
It would be nice to have a new device type which connects to a TCP or Unix domain socket.
This would greatly increase compatibility with other ELM based tooling.

ISSupported function in device.go incorrect for PID's outside part 1

Description

Please describe what you were trying to do and what happened. For example:

IsSupported function is incorrect for PID's outside of part1. Specifically, subtracting the pid from 32 for each part is wrong - the value should increase by 32 for each part i.e. for part 2 commands it should be 64-pid, part 3 96-pid etc.

I've rewritten the function as follows:

// IsSupported checks if the given OBDCommand is supported.
//
// It does this by comparing the PID of the OBDCommand against the lookup table.
func (sc *SupportedCommands) IsSupported(cmd OBDCommand) bool {
	if cmd.ParameterID() == 0 {
		return true
	}

	pid := cmd.ParameterID()

	var supported uint32 = 0
	switch pidrange:= pid; {
	case pidrange < 32:
		supported = (sc.Part1 >> uint32(32-pid)) & 1
	case pidrange < 64:
		supported = (sc.Part2 >> uint32(64-pid)) & 1
	case pidrange < 96:
		supported = (sc.Part3 >> uint32(96-pid)) & 1
	case pidrange < 128:
		supported = (sc.Part4 >> uint32(128-pid)) & 1
	case pidrange < 160:
		supported = (sc.Part5 >> uint32(160-pid)) & 1
	}

	return (supported == 1) 
}

Environment

Please fill in the information below and check the checkboxes, either by
writing an X inside each [ ] or by clicking the checkboxes in the
preview tab.

  • [Raspbian (debian) ] Operating system: ?
  • [ 1.12.6 ] Go version: ?
  • [ c7e4c91] Git commit: ?
  • [Reports as "OBDII to RS232 Interpreter" ] ELM327 device version: ?
  • [ Fiat ] Car make: ?
  • [ 124 Spider Abarth] Car model: ?
  • [ 2017] Car production year: ?

ELM327 cable sends different response payload that is not treated on elmobd

While trying a cheap chinese ELM327 cable, I got: "Failed to get rpm Expected at least 3 OBD literals: SEARCHING..."

This is happens because parseOBDResponse on device.go is receiving 2 arguments, the first is "SEARCHING..." and the other are the OBD literals. So, when it calls NewResult(payload), it passes only the first argument wich, in this case, is "SEARCHING...".

This is the call sequence I got while testing in a car:

runs command to get engine RPM

  • <rawdevice.go>:processResult:
    dev.outputs = [ELM327 v1.5]

  • <rawdevice.go>:processResult:
    dev.outputs = [OK]

  • <rawdevice.go>:processResult:
    dev.outputs = [SEARCHING... 41 0C 0B F4]

  • <device.go>:RunOBDCommand:
    outputs len = 2
    outputs content = [SEARCHING... 41 0C 0B F4]

  • <device.go>:NewResult:
    Failed to get rpm Expected at least 3 OBD literals: SEARCHING...

I made a pull request (b6e3ddf) with a workaround that solved my problem

Add documentation that explains USB/bluetooth usage

A common question that has come up on email has been whether you can use this library with bluetooth devices. Since this library only expect a RS232 serial connection with the device it doesn't matter whether if it's USB or bluetooth device.

This should be added to the README so that people can find this information quicker.

Second example in the README.org

While testing example 2 from README.org with go 1.9.2, I got the following error:
./main.go:16:53: undefined: EngineRPM

It is needed to define the package for this command, suggest changing
rpm, err := dev.RunOBDCommand(NewEngineRPM())
To:
rpm, err := dev.RunOBDCommand(elmobd.NewEngineRPM())

"UNABLE TO CONNECT" is given when same command is run twice

Description

This issue is created from the PR #29, to separate the investigation of the root cause of the problem that the PR is trying to solve.

The following code is used to build an executable:

package main

import (
	"flag"
	"fmt"
	"github.com/rzetterberg/elmobd"
)

func main() {
	serialPath := flag.String(
		"serial",
		"/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewDevice(*serialPath, false)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}

	rpm, err := dev.RunOBDCommand(elmobd.NewEngineRPM())

	if err != nil {
		fmt.Println("Failed to get rpm", err)
		return
	}

	fmt.Printf("Engine spins at %s RPMs\n", rpm.ValueAsLit())
}

The following steps is performed:

  1. ELM327-device is plugged into the computer
  2. Car ignition is turned on (engine is not started)
  3. ELM327-device is plugged into the cars OBD2-plug
  4. The executable is run and outputs:
    Engine spins at 0 RPMs
    
  5. The executable is run again directly and outputs:
    Failed to get rpm
    'UNABLE TO CONNECT' received, is the ignition on?
    

The expected result should have been:

Engine spins at 0 RPMs

Environment

  • Operating system: ?
  • ELM327 device version: Device has version OBDII to RS232 Interpreter
  • ELM327 device: Revesun ELM327 USB OBD2
  • Car make: Hyundai
  • Car model: Accent
  • Car production year: 2004

Add "see communication log" feature

Feature: See raw device communication log 
  In order to easier debug and share problems encountered
  As a developer
  I want to see a raw log of what is sent/received to/from the ELM327 device

  Scenario: Seeing engine load command log
    Given I am connected to a running car
      And I have turned on debugging
      And the actual engine load is 0.0
     When I run the "EngineLoad" command
      And the command spent 10 ms writing
      And the command spent 20 ms reading
     Then I see the following text in the console
       """
       =======================================
       Ran command "EngineLoad"
         - Spent 10 ms writing
         - Spent 20 ms reading
       ---------------------------------------
       Req: "010401\r\n"
       Res: "> 41 04 00\r\n"
       =======================================
       """

Byte response quantity doesn't match when running coolant_temperature / short_term_fuel_trim_bank1

Description

When trying to run the command coolant_temperature or short_term_fuel_trim_bank1, the error
"Expected 3 bytes, found 6"
was shown.

Environment

  • [ X] Operating system: Darwin 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 x86_64
  • [ X] Go version: go1.10 darwin/amd64
  • [ X] Git commit: 12b615d
  • [ X] ELM327 device version: Mock device
  • [ X] Car make: Mock device
  • [ X] Car model: Mock device
  • [ X] Car production year: Mock device

Does this library work with ELM327-emulator?

Description

I'm trying to test this library against https://github.com/Ircama/ELM327-emulator. I got this error after doing the following:

justin@justin-3900x:~/git/ELM327-emulator$ python3 -m elm
2020-03-22 10:22:00,993 - root - INFO - 

ELM327 OBD-II adapter emulator started

Welcome to the ELM327 OBDII adapter emulator.
ELM327-emulator is running on /dev/pts/3
Type help or ? to list commands.

CMD> run 
Error executing command: name 'run' is not defined
CMD> help

Available commands include the following list (type help <topic>
for more information on each command). Besides, any Python
command is accepted. Autocompletion is fully allowed.
================================================================
color     default  engineoff  history  pause   quit   resume    wait
counters  delay    help       merge    prompt  reset  scenario

CMD> scenario 
AT         car        default    engineoff  
CMD> scenario car
Emulator scenario switched to 'car'
ATZ> ATZ

------
func main() {
	serialPath := flag.String(
		"serial",
		"/dev/pts/3", // "/dev/ttyUSB0",
		"Path to the serial device to use",
	)

	flag.Parse()

	dev, err := elmobd.NewDevice(*serialPath, true)

	if err != nil {
		fmt.Println("Failed to create new device", err)
		return
	}



justin@justin-3900x:~/git/smartcar/containers/obdii$ ./obdii 
Failed to create new device Write echo mismatch: "ATZ" not suffix of ""
justin@justin-3900x:~/git/smartcar/containers/obdii$ 

# Environment
- [ ] **Operating system:** Ubuntu 19.10
- [ ] **Go version**: 1.13
- [ ] **Git commit**: 334e700512ddaaf69fcd31a95542b31a01de8218
- [ ] **ELM327 device version**: https://github.com/Ircama/ELM327-emulator 

Add faster sensor reading

Description

As noted in CHANGELOG.md:

Reading sensor data from my Lexus IS200 -04 was fairly slow. At worst it took almost ~2.5 seconds, and at best it took ~1.7 seconds

This is the total time it took to read 7 sensor values in series. The reason this happens is because by default the ELM327 waits 200 ms before concluding that all data has been received. To speed this up you can tell the ELM327 how much data to expect, and return as soon as that amount has been reached.

The reason why we're not seeing 7 * 200 ms all the time is because by default the ELM327 uses adaptive timing.

More information about the specifics can be found in the data shet: https://www.elmelectronics.com/wp-content/uploads/2017/01/ELM327DS.pdf

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.