What if car manufacturers would provide us applications to view information of our vehicles on our smartwatches?
Wouldn’t it be nice?
Since there are a lot of apps for smartphones, it should be possible to adapt those functionalities to wearable platforms (e.g. Android Wearable).
So I decided to develop such an app.
What I needed:
- A smartwatch (Moto360)
- An ELM 327 WiFi adapter (to read data directly from a car) – or another OBD-2 adapter
- A smartphone (OnePlus One – Android 4.4)
- Information about the OBD-2 protocol – Click here to see all standard PIDs
Ahh and don’t forget:
- A car
But first let me show you the result:

So the scenario is very simple. The smartwatch is connected via BLUETOOTH to the smartphone.
The smartphone is via WiFi (TCP/IP) connected to the ELM 327 WiFi Adapter, to do data requests.
Once data is requested by the phone, the adapter will forward the request to the car and delivers the answer back to the phone, which will transmit the data to the watch.

The only thing you really have to know, is the OBD-2 Protokoll, to send the right requests to your car.
Everything else is just a little bit of coding and designing.
In my case I’ve used the following protocol:
To initialize (Mercedes 2013 and newer):
- AT Z
- AT L0
- AT E1
- AT H1
- AT SP 0
- 01 00
- AT DP
To read specific data e.g. fuel level:
- 01 2F
Cheers
Lars
Btw: the sourcecode is added below.
- Smartwatch application
- 1.1 Design / Markup
- 1.2 Manifest
- 1.3 WearableListenerService
- 1.4 Communication
- 1.5 Data storage
- Smartphone application
- 2.1 Manifest
- 2.2 Communication ELM 327
- 2.3 Communication Smartwatch
+ 1. Smartwatch (Sourcecode)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" tools:context=".MainActivity" tools:deviceIds="wear_round"> <ProgressBar android:id="@+id/pbarEmpty" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="273dp" android:layout_alignParentStart="true" android:indeterminate="false" android:max="100" android:progress="99" android:progressDrawable="@drawable/circular_progress_bar" android:rotation="90"/> <ProgressBar android:id="@+id/pbarState" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="273dp" android:layout_alignParentStart="true" android:indeterminate="false" android:max="100" android:progress="1" android:progressDrawable="@drawable/circular_progress_bar" android:rotation="90" /> <TextView android:id="@+id/txtFuelText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:alpha="1" android:layout_marginTop="48dp" android:text="@string/fuel_text" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#ffffff" android:textSize="23dp" android:fontFamily="sans-serif-light" /> <ImageView android:id="@+id/imgFuelIcon" android:layout_width="65dp" android:layout_height="75dp" android:layout_below="@+id/txtFuelText" android:background="@drawable/fuel_icon" android:layout_marginTop="10dp" android:layout_centerHorizontal="true" android:alpha="0.9" /> <TextView android:id="@+id/txtFuelState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/imgFuelIcon" android:layout_centerHorizontal="true" android:text="@string/fuel_initial_value" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#09aae0" android:textSize="25dp" android:layout_marginTop="7dp" android:fontFamily="sans-serif-light" /> </RelativeLayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.larsfeicho.car_info" > <uses-feature android:name="android.hardware.type.watch" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.DeviceDefault" > <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <activity android:name=".MainActivity" 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=".WearMessageListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service> </application> </manifest> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.larsfeicho.car_info; import android.content.Intent; import com.google.android.gms.wearable.MessageEvent; import com.google.android.gms.wearable.WearableListenerService; /** * Created by LFE on 28.03.2015. */ public class WearMessageListenerService extends WearableListenerService { private static final String START_ACTIVITY = "/start_activity"; @Override public void onMessageReceived(MessageEvent messageEvent) { if( messageEvent.getPath().equalsIgnoreCase( START_ACTIVITY ) ) { Intent intent = new Intent( this, MainActivity.class ); intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); startActivity( intent ); } else { super.onMessageReceived(messageEvent); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@Override public void onMessageReceived( final MessageEvent messageEvent ) { runOnUiThread( new Runnable() { @Override public void run() { if( messageEvent.getPath().equalsIgnoreCase( WEAR_MESSAGE_PATH ) ) { try{ String data = new String(messageEvent.getData()); Log.wtf("Received", data); int fuelLevel = Integer.parseInt(data); ObjectAnimator animation = ObjectAnimator.ofInt(pBarFuel, "progress", fuelLevel); animation.setDuration(1000); // 1 second animation.setInterpolator(new DecelerateInterpolator()); animation.start(); saveFuelData(data + "%"); }catch(Exception e){ Log.wtf("Error", e.toString()); } } } }); } |
1 2 3 4 5 6 7 8 9 10 |
private void saveFuelData(String fuelState){ try{ SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); SharedPreferences.Editor editor = sharedPref.edit(); editor.putString(LEVEL_FUEL_DATA, fuelState); editor.commit(); }catch (Exception e){ Log.wtf("Error", e.toString()); } } |
+ 2. Smartphone (Sourcecode)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.larsfeicho.car_info" > <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
package com.larsfeicho.car_info.TCP; import android.util.Log; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * Created by LFE on 28.03.2015. */ public class SocketImp { public static final String SRV_IP = "192.168.0.10"; public static final int SRV_PORT = 35000; private static final String PROMPT_CONST = ">"; private static final String CR_CONST = "\n"; private static final String LF_CONST = "\r"; private String serverMessage; private OnMessageReceived mMsgListener = null; private boolean mIsRunning = false; private OutputStream mOutStream; private InputStream mInStream; public SocketImp(OnMessageReceived listener) { mMsgListener = listener; } public void sendMessage(String message){ if (mOutStream != null) { try { mOutStream.write(message.getBytes()); } catch (IOException e) { e.printStackTrace(); Log.wtf("Error", e.toString()); } } } public void stopSocketClient(){ mIsRunning = false; } public void run() { mIsRunning = true; try { InetAddress srvAddr = InetAddress.getByName(SRV_IP); Socket sock = new Socket(srvAddr, SRV_PORT); try { mOutStream = sock.getOutputStream(); mInStream = sock.getInputStream(); byte[] bytes = null; boolean toSend = false; while (mIsRunning) { while(mInStream.available() > 0){ toSend = true; int ready = mInStream.available(); bytes = new byte[ready]; sock.getInputStream().read(bytes); } if(toSend){ String respString =new String(bytes, "UTF-8"); respString = respString.replace(CR_CONST, ""); respString = respString.replace(LF_CONST, ""); respString = respString.replace(PROMPT_CONST, ""); mMsgListener.messageReceived(respString); toSend = false; } } } catch (Exception e) { Log.wtf("TCP", "Failed", e); } finally { sock.close(); } } catch (Exception e) { Log.wtf("TCP", "Failed", e); } } public interface OnMessageReceived { public void messageReceived(String message); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private void initGoogleApiClient() { mApiClient = new GoogleApiClient.Builder( this ) .addApi( Wearable.API ) .build(); mApiClient.connect(); } protected void onDestroy() { super.onDestroy(); mApiClient.disconnect(); } private void sendMessage( final String path, final String text ) { new Thread( new Runnable() { @Override public void run() { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes( mApiClient ).await(); for(Node node : nodes.getNodes()) { MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage( mApiClient, node.getId(), path, text.getBytes() ).await(); } } }).start(); } |