Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
Brendan Whitfield committed Apr 4, 2016
2 parents c39ba5b + c633d04 commit 889ceea
Show file tree
Hide file tree
Showing 36 changed files with 2,466 additions and 1,229 deletions.
9 changes: 9 additions & 0 deletions docs/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ obd.commands.has_pid(1, 12) # True

<br>

# OBD-II adapter (ELM327 commands)

|PID | Name | Description |
|-----|-------------|-----------------------------------------|
| N/A | ELM_VERSION | OBD-II adapter version string |
| N/A | ELM_VOLTAGE | Voltage detected by OBD-II adapter |

<br>

# Mode 01

|PID | Name | Description |
Expand Down
96 changes: 91 additions & 5 deletions docs/Connections.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

After installing the library, simply `import obd`, and create a new OBD connection object. By default, python-OBD will scan for Bluetooth and USB serial ports (in that order), and will pick the first connection it finds. The port can also be specified manually by passing a connection string to the OBD constructor. You can also use the scanSerial helper retrieve a list of connected ports.
After installing the library, simply `import obd`, and create a new OBD connection object. By default, python-OBD will scan for Bluetooth and USB serial ports (in that order), and will pick the first connection it finds. The port can also be specified manually by passing a connection string to the OBD constructor. You can also use the `scan_serial` helper retrieve a list of connected ports.

```python
import obd
Expand All @@ -12,18 +12,36 @@ connection = obd.OBD("/dev/ttyUSB0") # create connection with USB 0

# OR

ports = obd.scanSerial() # return list of valid USB or RF ports
ports = obd.scan_serial() # return list of valid USB or RF ports
print ports # ['/dev/ttyUSB0', '/dev/ttyUSB1']
connection = obd.OBD(ports[0]) # connect to the first port in the list
```


<br>

### OBD(portstr=None, baudrate=38400, protocol=None, fast=True):

`portstr`: The UNIX device file or Windows COM Port for your adapter. The default value (`None`) will auto select a port.

`baudrate`: The baudrate at which to set the serial connection. This can vary from adapter to adapter. Typical values are: 9600, 38400, 19200, 57600, 115200

`protocol`: Forces python-OBD to use the given protocol when communicating with the adapter. See `protocol_id()` for possible values. The default value (`None`) will auto select a protocol.

`fast`: Allows commands to be optimized before being sent to the car. Python-OBD currently makes two such optimizations:

- Sends carriage returns to repeat the previous command.
- Appends a response limit to the end of the command, telling the adapter to return after it receives *N* responses (rather than waiting and eventually timing out). This feature can be enabled and disabled for individual commands.

Disabling fast mode will guarantee that python-OBD outputs the unaltered command for every request.

<br>

---

### query(command, force=False)

Sends an `OBDCommand` to the car, and returns a `OBDResponse` object. This function will block until a response is recieved from the car. This function will also check whether the given command is supported by your car. If a command is not marked as supported, it will not be sent to the car, and an empty `Response` will be returned. To force an unsupported command to be sent, there is an optional `force` parameter for your convenience.
Sends an `OBDCommand` to the car, and returns a `OBDResponse` object. This function will block until a response is received from the car. This function will also check whether the given command is supported by your car. If a command is not marked as supported, it will not be sent to the car, and an empty `Response` will be returned. To force an unsupported command to be sent, there is an optional `force` parameter for your convenience.

*For non-blocking querying, see [Async Querying](Async Connections.md)*

Expand All @@ -36,24 +54,92 @@ r = connection.query(obd.commands.RPM) # returns the response from the car

---

### status()

Returns a string value reflecting the status of the connection. These values should be compared against the `OBDStatus` class. The fact that they are strings is for human readability only. There are currently 3 possible states:

```python
from obd import OBDStatus

# no connection is made
OBDStatus.NOT_CONNECTED # "Not Connected"

# successful communication with the ELM327 adapter
OBDStatus.ELM_CONNECTED # "ELM Connected"

# successful communication with the ELM327 and the vehicle
OBDStatus.CAR_CONNECTED # "Car Connected"
```

The middle state, `ELM_CONNECTED` is mostly for diagnosing errors. When a proper connection is established, you will never encounter this value.

---

### is_connected()

Returns a boolean for whether a connection was established.
Returns a boolean for whether a connection was established with the vehicle. It is identical to writing:

```python
connection.status() == OBDStatus.CAR_CONNECTED
```

---

### get_port_name()
### port_name()

Returns the string name for the currently connected port (`"/dev/ttyUSB0"`). If no connection was made, this function returns `"Not connected to any port"`.

---

### get_port_name()

**Deprecated:** use `port_name()` instead

---

### supports(command)

Returns a boolean for whether a command is supported by both the car and python-OBD

---

### protocol_id()
### protocol_name()

Both functions return string names for the protocol currently being used by the adapter. Protocol *ID's* are the short values used by your adapter, whereas protocol *names* are the human-readable versions. The `protocol_id()` function is a good way to lookup which value to pass in the `protocol` field of the OBD constructor (though, this is mainly for advanced usage). These functions do not make any serial requests. When no connection has been made, these functions will return empty strings. The possible values are:

|ID | Name |
|---|--------------------------|
| 1 | SAE J1850 PWM |
| 2 | SAE J1850 VPW |
| 3 | AUTO, ISO 9141-2 |
| 4 | ISO 14230-4 (KWP 5BAUD) |
| 5 | ISO 14230-4 (KWP FAST) |
| 6 | ISO 15765-4 (CAN 11/500) |
| 7 | ISO 15765-4 (CAN 29/500) |
| 8 | ISO 15765-4 (CAN 11/250) |
| 9 | ISO 15765-4 (CAN 29/250) |
| A | SAE J1939 (CAN 29/250) |

---

<!--
### ecus()
Returns a list of identified "Engine Control Units" visible to the adapter. Each value in the list is a constant representing that ECU's function. These constants are found in the `ECU` class:
```python
from obd import ECU
ECU.UNKNOWN
ECU.ENGINE
```
Python-OBD can currently only detect the engine computer, but future versions may extend this capability.
-->

### close()

Closes the connection.
Expand Down
108 changes: 84 additions & 24 deletions docs/Custom Commands.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,102 @@

If the command you need is not in python-OBDs tables, you can create a new `OBDCommand` object. The constructor accepts the following arguments (each will become a property).

| Argument | Type | Description |
|----------------------|----------|--------------------------------------------------------------------------|
| name | string | (human readability only) |
| desc | string | (human readability only) |
| mode | string | OBD mode (hex) |
| pid | string | OBD PID (hex) |
| bytes | int | Number of bytes expected in response |
| decoder | callable | Function used for decoding the hex response |
| supported (optional) | bool | Flag to prevent the sending of unsupported commands (`False` by default) |

*When the command is sent, the `mode` and `pid` properties are simply concatenated. For unusual codes that don't follow the `mode + pid` structure, feel free to use just one, while setting the other to an empty string.*
| Argument | Type | Description |
|----------------------|----------|----------------------------------------------------------------------------|
| name | string | (human readability only) |
| desc | string | (human readability only) |
| command | string | OBD command in hex (typically mode + PID |
| bytes | int | Number of bytes expected in response |
| decoder | callable | Function used for decoding messages from the OBD adapter |
| ecu (optional) | ECU | ID of the ECU this command should listen to (`ECU.ALL` by default) |
| fast (optional) | bool | Allows python-OBD to alter this command for efficieny (`False` by default) |

The `decoder` argument is a function of following form.

Example
-------

```python
def <name>(_hex):
...
return (<value>, <unit>)
from obd import OBDCommand
from obd.protocols import ECU
from obd.utils import bytes_to_int

def rpm(messages):
d = messages[0].data
v = bytes_to_int(d) / 4.0 # helper function for converting byte arrays to ints
return (v, Unit.RPM)

c = OBDCommand("RPM", \ # name
"Engine RPM", \ # description
"010C", \ # command
2, \ # number of return bytes to expect
rpm, \ # decoding function
ECU.ENGINE, \ # (optional) ECU filter
True) # (optional) allow a "01" to be added for speed
```

The `_hex` argument is the data recieved from the car, and is guaranteed to be the size of the `bytes` property specified in the OBDCommand.
By default, custom commands will be treated as "unsupported by the vehicle". There are two ways to handle this:

For example:
```python
# use the `force` parameter when querying
o = obd.OBD()
o.query(c, force=True)
```

or

```python
from obd import OBDCommand
from obd.utils import unhex
# add your command to the set of supported commands
o = obd.OBD()
o.supported_commands.add(c)
o.query(c)
```

<br>

Here are some details on the less intuitive fields of an OBDCommand:

def rpm(_hex):
v = unhex(_hex) # helper function to convert hex to int
v = v / 4.0
return (v, obd.Unit.RPM)
---

### OBDCommand.decoder

The `decoder` argument is a function of following form.

c = OBDCommand("RPM", "Engine RPM", "01", "0C", 2, rpm)
```python
def <name>(<list_of_messages>):
...
return (<value>, <unit>)
```

Decoders are given a list of `Message` objects as an argument. If your decoder is called, this list is garaunteed to have at least one message object. Each `Message` object has a `data` property, which holds a parsed byte array, and is also garauteed to have the number of bytes specified by the command.

*NOTE: If you are transitioning from an older version of Python-OBD (where decoders were given raw hex strings as arguments), you can use the `Message.hex()` function as a patch.*

```python
def <name>(messages):
_hex = messages[0].hex()
...
return (<value>, <unit>)
```

*You can also access the original string sent by the adapter using the `Message.raw()` function.*

---

### OBDCommand.ecu

The `ecu` argument is a constant used to filter incoming messages. Some commands may listen to multiple ECUs (such as DTC decoders), where others may only be concerned with the engine (such as RPM). Currently, python-OBD can only distinguish the engine, but this list may be expanded over time:

- `ECU.ALL`
- `ECU.ALL_KNOWN`
- `ECU.UNKNOWN`
- `ECU.ENGINE`

---

### OBDCommand.fast

The `fast` argument tells python-OBD whether it is safe to append a `"01"` to the end of the command. This will instruct the adapter to return the first response it recieves, rather than waiting for more (and eventually reaching a timeout). This can speed up requests significantly, and is enabled for most of python-OBDs internal commands. However, for unusual commands, it is safest to leave this disabled.

---

<br>
4 changes: 2 additions & 2 deletions docs/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ This is likely a problem with the serial connection between the OBD-II adapter a
- you are connecting to the right port in `/dev` (or that there is any port at all)
- you have the correct permissions to write to the port

You can use the `scanSerial()` helper function to determine which ports are available for writing.
You can use the `scan_serial()` helper function to determine which ports are available for writing.

```python
import obd

ports = obd.scanSerial() # return list of valid USB or RF ports
ports = obd.scan_serial() # return list of valid USB or RF ports
print ports # ['/dev/ttyUSB0', '/dev/ttyUSB1']
```

Expand Down
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Install the latest release from pypi:
$ pip install obd
```

If you are using a bluetooth adapter on Debian-based linux, you will need to install the following packages:
*Note: If you are using a Bluetooth adapter on Linux, you may also need to install and configure your Bluetooth stack. On Debian-based systems, this usually means installing the following packages:*

```shell
$ sudo apt-get install bluetooth bluez-utils blueman
Expand All @@ -35,6 +35,8 @@ print(response.value)
print(response.unit)
```

OBD connections operate in a request-reply fashion. To retrieve data from the car, you must send commands that query for the data you want (e.g. RPM, Vehicle speed, etc). In python-OBD, this is done with the `query()` function. The commands themselves are represented as objects, and can be looked up by name or value in `obd.commands`. The `query()` function will return a response object with parsed data in its `value` and `unit` properties.

<br>

# License
Expand Down
Loading

0 comments on commit 889ceea

Please sign in to comment.