This tutorial assumes you have a working knowledge of how to create an Android application. Setting up the development environment and understanding Android fundamentals is outside the scope of OpenXC, and already Google provides great documentation and tutorials - we won’t repeat them here. The best place to start is Android Studio’s User Guide.
Once you’re comfortable with creating an Android app, continue on with this tutorial to enrich it with data from your vehicle.
The Starter project is based off of the same “Hello World” application that you should have already created in Google’s tutorial. The first difference is that we’ve specified that the Starter app will use the OpenXC library as a dependency.
This is already done in the Starter project and no changes have to be made. But to make your own app from scratch, go
to the app/build.gradle
file and add the openxc
library to the build
dependencies. This is mentioned in the Android Library Setup
page:
dependencies {
compile 'com.openxcplatform:library:7.0.6'
}
You can now proceed to the next steps to start using the library in your project.
Note: If for some reason you are unable to use the remote openxc-android
library, you can use a local copy. This requires replacing three files in the starter app, and one file in the imported library. All of the required files are included in each of the respective repositories with a .local extension. Below is a list of the files and locations:
openxc\openxc-starter --> settings.gradle.local
openxc\openxc-starter --> build.gradle.local
openxc\openxc-starter --> app\build.gradle.local
openxc\openxc-android --> library\build.gradle.local
Replace the standard versions of the files with the .local files. For example, in the OpenXC Starter app, replace the app\build.gralde file with the app\build.gradle.local file. Restart your IDE, run the Gradle build process, and your starter app should now be using a local copy of the openxc-android
library.
The AndroidManifest.xml
is the core of every Android application - it tells
the Android OS what views are available, which services are used and what sensors
your app needs.
Every OpenXC application, the Starter app included, needs to use the
VehicleManager
service. The next difference between the “Hello World” app and
the Starter app is the addition of this line to the manifest:
<service android:name="com.openxc.VehicleManager"/>
This should go between the <application>
tags, like this:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.openxc.openxcstarter.StarterActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.openxc.VehicleManager"/>
</application>
The next changes are all in Java code - for the Starter app, it’s in
StarterActivity.java
in the src
folder. In order to use the VehicleManager
in Java code, we have to initiate the our mVehicleManager
variable and then
bind with it when the application starts.
Initiate our Starter App variables as shown here:
public class StarterActivity extends Activity {
private static final String TAG = "StarterActivity";
private VehicleManager mVehicleManager;
private TextView mEngineSpeedView;
@Override
protected void onCreate(Bundle savedInstanceState) {
Then add the ServiceConnection in the StarterActivity
to bind with the
VehicleManager:
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the VehicleManager service is established
public void onServiceConnected(ComponentName className, IBinder service) {
Log.i(TAG, "Bound to VehicleManager");
// When the VehicleManager starts up, we store a reference to it
// here in "mVehicleManager" so we can call functions on it
// elsewhere in our code.
mVehicleManager = ((VehicleManager.VehicleBinder) service)
.getService();
// We want to receive updates whenever the EngineSpeed changes. We
// have an EngineSpeed.Listener (see above, mSpeedListener) and here
// we request that the VehicleManager call its receive() method
// whenever the EngineSpeed changes
mVehicleManager.addListener(EngineSpeed.class, mSpeedListener);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.w(TAG, "VehicleManager Service disconnected unexpectedly");
mVehicleManager = null;
}
};
In the onResume()
method of the activity, we request to bind with the service
using the new ServiceConnection instance:
@Override
public void onResume() {
super.onResume();
// When the activity starts up or returns from the background,
// re-connect to the VehicleManager so we can receive updates.
if(mVehicleManager == null) {
Intent intent = new Intent(this, VehicleManager.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
}
Now, when your app starts it will also start the OpenXC VehicleManager and if the Enabler is running, it will be ready to receive data from the vehicle.
The activity now has a connection to the vehicle service, and we want it to be
notified whenever the speed of the vehicle changes. Look for the
EnginerSpeed.Listener
object in the StarterActivity
:
EngineSpeed.Listener mSpeedListener = new EngineSpeed.Listener() {
public void receive(Measurement measurement) {
// When we receive a new EngineSpeed value from the car, we want to
// update the UI to display the new value. First we cast the generic
// Measurement back to the type we know it to be, an EngineSpeed.
final EngineSpeed speed = (EngineSpeed) measurement;
// In order to modify the UI, we have to make sure the code is
// running on the "UI thread" - Google around for this, it's an
// important concept in Android.
StarterActivity.this.runOnUiThread(new Runnable() {
public void run() {
// Finally, we've got a new value and we're running on the
// UI thread - we set the text of the EngineSpeed view to
// the latest value
mEngineSpeedView.setText("Engine speed (RPM): "
+ speed.getValue().doubleValue());
}
});
}
};
This mSpeedListener
is referred to from
ServiceConnection.onServiceConnected()
method we previously, where it’s handed
to the VehicleManager
for future updates. Every time a new value for
EngineSpeed
is received by the VehicleManager
, the receive(Measurement)
method of the new Listener
will be called with the data.
In order to listen to a custom message from the vehicle, follow these steps:
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the VehicleManager service is
// established, i.e. bound.
public void onServiceConnected(ComponentName className,
IBinder service) {
Log.i(TAG, "Bound to VehicleManager");
// When the VehicleManager starts up, we store a reference to it
// here in "mVehicleManager" so we can call functions on it
// elsewhere in our code.
mVehicleManager = ((VehicleManager.VehicleBinder) service)
.getService();
// We want to receive updates whenever our Message changes.
// We have customVehicleMessageListener and here we request that the VehicleManager
// call its receive() method whenever the custom simpleVehicleMsg changes
mVehicleManager.addListener(SimpleVehicleMessage.class, customVehicleMessageListener);
}
private VehicleMessage.Listener customVehicleMessageListener = new VehicleMessage.Listener() {
// When we receive a new SimpleVehicleMessage value from the car, we want to update the
// UI to display the new value. First we create a new SimpleVehicleMessage from the
// received VehicleMessage and if the name of message is as specified (custom), we set
// the text of the customMessageView view to the latest value
@Override
public void receive(final VehicleMessage message) {
StarterActivity.this.runOnUiThread(new Runnable() {
public void run() {
SimpleVehicleMessage simpleVehicleMsg = new SimpleVehicleMessage(message.getTimestamp(),
((SimpleVehicleMessage) message).getName(),
((SimpleVehicleMessage) message).getValue());
if (simpleVehicleMsg.getName().equalsIgnoreCase("custom_message")) {
customMessageView.setText(simpleVehicleMsg.getName() +": "+ simpleVehicleMsg.getValue());
}
}
});
}
};
Lastly, the Starter app adds one more element to the user interface so there’s a
place to display the current speed. In the main layout file for the activity,
res/layout/activity_starter.xml
, the existing “hello world” TextView
is
replaced with these two:
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:id="@+id/engine_speed" />
These widgets have IDs and are using the RelativeLayout
to make sure they
don’t print on top of each other.
In the app’s onCreate
method, we grab a reference to that text object in Java
as mEngineSpeedView
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_starter);
// grab a reference to the engine speed text object in the UI, so we can
// manipulate its value later from Java code
mEngineSpeedView = (TextView) findViewById(R.id.engine_speed);
}
Finally, look back at the EngineSpeed.Listener we created before - it’s updated the UI every time a new measurement arrives:
EngineSpeed.Listener mSpeedListener = new EngineSpeed.Listener() {
public void receive(Measurement measurement) {
// When we receive a new EngineSpeed value from the car, we want to
// update the UI to display the new value. First we cast the generic
// Measurement back to the type we know it to be, an EngineSpeed.
final EngineSpeed speed = (EngineSpeed) measurement;
// In order to modify the UI, we have to make sure the code is
// running on the "UI thread" - Google around for this, it's an
// important concept in Android.
StarterActivity.this.runOnUiThread(new Runnable() {
public void run() {
// Finally, we've got a new value and we're running on the
// UI thread - we set the text of the EngineSpeed view to
// the latest value
mEngineSpeedView.setText("Engine speed (RPM): "
+ speed.getValue().doubleValue());
}
});
}
};
That’s all you need to do to get measurements from OpenXC. You can see the full
list of Measurement
Java classes that you can use in the
library documentation.
Your Android device likely doesn’t have any vehicle data flowing through it yet. The next step is to use a pre-recorded vehicle trace file to simulate a real vehicle interface on your desk.
Install the Enabler app if you haven’t already. That application helps control the source of vehicle data, e.g. a vehicle interface or a trace file.
Download the driving trace and copy it to the SD card of your Android device. In the case of using an emulator, follow these steps:
With an Android device, you can do this in a few ways:
abd
on the command line:
$ adb push driving.json /sdcard/openxc-driving.json
Finally, the last steps:
OpenXC Enabler
app on the deviceSettings -> Data Sources
and change the vehicle
interface to a Pre-recorded Trace File.Trace File Playback
, select a trace file
for playback. You need a file manager app on your device to browse for a
file. Later Android devices come with a manager pre-installed. Older devices
may need to download another app, such as the OI File
Manager.
In the file manager, browse to the trace file you downloaded.Run the Starter app app and you should see the engine speed changing in the UI!
You’ve now completed the OpenXC Android tutorial, but there’s more to learn about supported Android devices and vehicle interfaces. You can also check out the Android API Guide for more information on how to use the API. If you are having trouble, check out the troubleshooting steps.