Connecting a BMW to the Internet — Part Three
This blog series details how I connected my BMW E46 to the internet. In part one, we walked through the project motivation, architecture, and component installation process. In part two, we examined the Python code powering the Raspberry Pi which interfaces directly with the vehicle and wireless devices. In part three, we will review the code running on the Android tablet and smart watch, providing an end-to-end experience.
Introducing the Android App
The Android application is responsible for connecting to the Raspberry Pi via Bluetooth, and providing a user interface to issue commands such as rolling down the windows or locking the vehicle.
The project has three primary Java modules:
app
– The Android application for mobile/tablet devices
ibuswear
– The Android application for wearable devices
common
– Common logic shared between the Android apps
For the main application, I designed a simple user interface that allows you to interact with the vehicle. There is also quick-access to applications such as Pandora, Google Play Music and Google Maps by tapping “Radio”, “Media”, and “Maps” respectively.
To interact with the vehicle’s devices, you may select the “Devices” option from the main activity. You will be presented with a horizontal list view of the various integrated devices, along with some quick actions such as locking and unlocking the vehicle, rolling up or down the windows, and more. This was accomplished by creating a RecyclerView
and attaching our AdapterDevices
class (which implements RecyclerView.Adapter
). There is a single layout XML that represents a device in the list, which is inflated when the adapter’s onCreateViewHolder
method is called.
I introduced a simple Device
class to manage properties for each item in the list, including their callback functions and images.
I proceeded to create a method for each device, which instantiates a Device
object and sets the necessary attributes for the view. For example, in getInteriorLights()
we assign a name, icon, action buttons, as well as click listeners.
When instantiating the RecyclerView adapter (AdapterDevices
), an array list of these Device
objects is provided. The adapter and recycler view implementations handle the remaining responsibilities of binding events to the view and displaying resources.
I’ve introduced an IBUSWrapper
class which contains methods for interacting with the vehicle, as can be observed in the example device’s onClickListenerAction1
and onClickListenerAction2
. For the methods that actuate the windows, you may provide a boolean to indicate if you want a window to roll up or down. The same approach is used for moving the driver seat forward or backward, as well as opening and closing the sunroof.
Each of the IBUSWrapper methods is responsible for writing the appropriate IBUS packet to the Bluetooth output stream. For example:
The Raspberry Pi on the receiving end will parse the data, and write to the IBUS network via a serial connection.
Connecting to the Controller via Bluetooth
The following snippet demonstrates the necessary code to establish a connection with the Raspberry Pi controller. It is important to note that the Bluetooth MAC Address must be specified, and the devices should already be paired via Bluetooth.
The ConnectedThread
will continuously read from the Bluetooth input stream, and process information in the background. When data is received, it will relay the information to the current Activity. For example, if we see an IBUS packet that indicates the “next track” button on the steering wheel was pressed, we can instruct the Android device to advance to the next track.
Customizing the Application
Within Android Studio, you may customize the user interface and layouts to your liking. If you would like different default apps for Radio, Music, or Maps, you may easily make those changes.
Introducing Android Wear Support
Now that we have an Android application that communicates with the Raspberry Pi, it’s fairly simple to extend the project to support Android Wear. We can reuse all of our implementation for connectivity (the common
module), and will just need to implement the views.
For the Android Wear app interface, I went with a bi-directional navigation pattern. You may scroll vertically on the watch to select a device (such as “Driver Window” or “Locks”) and then can scroll horizontally to select an action (such as “Up” or “Down”). The very first device in the list is called “Voice”, which enables you to invoke commands such as “roll down the driver window” or “pop the trunk”.
This was accomplished using a FragmentGridPageAdapter
(now deprecated as of Android Wear 2.0), which contains a getFragment
method that returns a Fragment
for the appropriate view. The fragment arrangement is represented as a two-dimensional array.
In the adapter, the getFragment
method performs a lookup in PAGES
using the row and column int indexes.
Adding Voice Recognition
Introducing support for voice commands was easy thanks to Android Speech Recognition. The first step in obtaining a string representation of a voice command is to launch an Intent
that starts the Speech Recognizer activity.
We’ll also need to implement an onActivityResult
method in our Activity, which will be invoked when the Speech Recognizer activity finishes with a result.
We extract the spoken text string from the intent, and call VoiceCommand.processSpokenText()
which exists in the common
module. This method is responsible for taking the string and invoking an appropriate method in IBUSWrapper
to interact with the vehicle.
This may not be the most elegant solution, but it works well for the targeted use cases. You can vary the command and it will still resolve to a method: “turn off the interior lights” vs “interior lights off”.
Wrapping it Up
I learned a lot throughout the project and hope this blog series serves as a helpful resource for those interested in connecting an E46 BMW to the internet. I plan on continuing to extend the project with new functionality, and add support for additional vehicle types. The controller was recently updated to Python 3.7, and now supports running as a Docker container. Contributions are welcome to the GitHub repository!