Quickly create a python-socketio server with events corresponding to the python-OBD API. Ideal for controlling an Async connection to a vehicle from a JavaScript socketio client. The module provides default events which correspond to python-OBD methods as well as JSON encoding for its types.
An example use case would be to use the server on a wireless hotspot enabled Raspberry Pi and be able to interact with the connection from another device in a browser. You can use the default events which will behave as python-OBD does or build your own custom websocket/OBD2 API.
pip install obd-socketio
import obdio
io = obdio.OBDio()
# Initiate connection to the vehicle
io.connect_obd()
# Create the server socket
io.create_server()
# Start the server
io.run_server()
Use connect_obd()
to initiate the serial connection with the vehicle. connect_obd()
can be called at anytime during the server's life and can also be used in the body of an event handler.
import obdio
io = obdio.OBDio()
io.connect_obd('/dev/ttyUSB0') # connect to specific serial port
See OBD connections for further usage.
OBDio currently uses a Uvicorn ASGI (Asynchronous Server Gateway Interface) socketio server for handling events asyncronously. Event handlers must be declared async
and emtting events must be called with the await
keyword.
To create and access the socketio server use create_server(**kwargs)
which passes its arguments to python-socketio's Server class giving you control over its behaviour as well as engineIO configuration.
import obdio
io = obdio.OBDio()
sio = io.create_server(cors_allowed_origins='*', json=obdio) # use obdio as the json module for obd support
The json parameter can be substituted for your own or the built-in (import json
) module though it cannot serialize some of the obd types.
After creating your server socket you can define as many events or background tasks as needed on top of what is already defined by the OBDio class then call run_server(**config)
to listen on your desired host:port.
Notable Parameters:
host
(str) - Use '0.0.0.0' to be accessible on your LAN.port
(int) - the port the server will listen on
More config args can be found on the Uvicorn settings page. Must be called after create_server
.
io.create_server()
io.run_server(host='0.0.0.0', port=48484)
Use serve_static(files)
to confugure the socketio server to serve a files by route, or a folder of static files. See python-socketio server static files for more info.
files
(dict) - a dictionary of static files to serve by route
io = create_server() # create the server first
io.server_static({
'/': 'index.html' # send some index for the route '/'
})
io.run_server() # run the server after defining the static files
When you create an OBDio instance, every OBD command added to the watch loop will be assigned a placeholder callback function called watch_callback
. It is left for you to define so that data can be handled in your program uniquely. For example, for each response the value could be cached into a dictionary then that entire object streamed over the socket at a lesser rate than watch_callback
may be fired.
Your custom watch callback will be passed an OBDResponse. If you use obdio
as your socket JSON serializer, the responses will be encoded correctly when you emit back to the client.
response
(obd.OBDResponse) - the obd value that triggered the callback
data = {}
def cache_values(response):
data[response.command.name] = response
io.watch_callback = cache_values
When you create and run an OBDio server events are defined for the public python-OBD API which itself is powerful. However based on your use case you may want to add more functionality to the server beyond what is defined. Also, you may wish to override the default implementation of the predefined API to suit your needs.
One way to create custom or override events is to use create_event(name, handler)
.
name
(string) - the name of a custom or overriden eventasync handler(sid, data)
(function) - the custom event handler - must be async
You are able to create custom events, or override the defaults with custom behaivour.
sio = io.create_server() # call create server first to access the socket
async def custom_handler(sid, data): # handlers must be async
await sio.emit('custom', data) # any emits must be awaited
io.create_event('custom_event', custom_handler)
io.run_server(port=3000) # lastly call run_server
Or, you can use the @sio.event decorator allowing you to create events as you would with an ASGI server.
import obd
""" Override the default watch event """
@sio.event
async def watch(sid, cmd):
io.connection.watch(obd.commands[cmd]) # access python-OBD connection and methods
await sio.emit('event') # emits must be awaited
io.run_server(port=48484)
On creation of an OBDio server most of the python-OBD API is exposed through events of the same name as its functions. The server will handle the event then respond with data (if any) using the same event name.
Event Name | Argument Type | Response Type |
---|---|---|
'status' | None | string |
'is_connected' | None | boolean |
'protocol_name' | None | string |
'protocol_id' | None | string |
'port_name' | None | string |
'supports' | string1 | boolean |
'supported_commands' | None | object |
'query' 2 | string1 | N/A |
'start' | None | N/A |
'watch' | string[]1 | N/A |
'unwatch' | string[]1 | N/A |
'unwatch_all' | None | N/A |
'has_name' | string1 | boolean |
'close' | None | null |
-
Arg is a list of OBD commands by name i.e. 'RPM', 'SPEED'. See the OBD Command Tables.
-
To query a command, it must be
watch
ed first
See the python-OBD documentation for expected behaviour of the default events.
Currently the JSON encodings for the python-OBD types provide minimal data excluding unnessacary and non-human readable values to keep payload size smaller when emitting back to a client.
name
(string) - human readable representation of a PID, all capsdesc
(string) - human readable description of the command
Example:
{
"name": "SPEED",
"desc": "Vehicle Speed"
}
value
(any) - the response value: numeric, arraycommand
(OBDCommand) - the command the response is fortime
(number) - Unix timestamp (seconds) of the responseunit
(string) - the unit of the response value. Non-numeric responses (dtcs, o2 sensors, etc) units will be represented as their type such as"<class 'tuple'>"
while numeric ones will be a literal representing their unit from the obd unit registry (pint value).
Example:
{
"value": 1500,
"command": {
"name": "RPM",
"desc": "Engine RPM"
},
"time": 1639266637000, // epoch time in ms
"unit": "revolutions_per_minute"
}
OBDResponse - Status Example:
{
"value": {
"MIL": false,
"DTC_COUNT": 0,
"ignition_type": "spark"
},
"command": {
"name": "STATUS",
"desc": "Status since DTCs cleared"
},
"time": 1639341509038,
"unit": "<class 'obd.OBDResponse.Status'>"
}
Note the value is another object and unit describes the type of the response since it is not measurable.