├── .gitignore
├── .gitmodules
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── LICENSE.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── brianledbetter
│ │ └── kwplogger
│ │ ├── ApplicationTest.java
│ │ ├── KWP2000TestIO.java
│ │ ├── LoginIntegrationTest.java
│ │ ├── LoginTest.java
│ │ ├── MeasurementValueTest.java
│ │ ├── RetryBusyIntegrationTest.java
│ │ └── StartLocalRoutineIntegrationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── com
│ │ │ └── brianledbetter
│ │ │ └── kwplogger
│ │ │ ├── BluetoothPickerDialogFragment.java
│ │ │ ├── DetailedMeasurementActivity.java
│ │ │ ├── DiagnosticCodesActivity.java
│ │ │ ├── DiagnosticsService.java
│ │ │ ├── MainActivity.java
│ │ │ ├── ParcelableDTC.java
│ │ │ ├── ParcelableMeasurementValues.java
│ │ │ ├── PermanentService.java
│ │ │ ├── PollingService.java
│ │ │ ├── ResetClusterActivity.java
│ │ │ └── StateSingleton.java
│ └── res
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_measurement.xml
│ │ ├── diagnostic_codes.xml
│ │ └── reset_cluster.xml
│ │ ├── menu
│ │ ├── menu_codes.xml
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── brianledbetter
│ └── kwplogger
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "kwp2000"]
2 | path = kwp2000
3 | url = https://github.com/bri3d/kwp2000.git
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | kwp-android-logger
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Brian Ledbetter
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KWP Logger for Android
2 |
3 | 
4 |
5 | This project is intended to provide a KWP2000 data logger for Android.
6 |
7 | You can access a very primitive dialog similar to the Measurements dialog in VCDS by visiting the ... menu.
8 |
9 | The application connects to an ELM327 device over Bluetooth, but the hardware interface is completely abstracted and so a class conforming to the KWP2000IO protocol could allow it to work with a USB cable or other type of device.
10 |
11 | Right now, it's hardcoded at start to log into the transmission controller (0x02 init, 0x1A comm) and fetch the first part of Measurement Block 6, which is assumed to be the ATF temp.
12 |
13 | This is because this project is currently intended to provide a quick shop tool for flushing the transmission fluid in a Touraeg or 955 Cayenne.
14 |
15 | However, the basic library (once all measurement types are added) should be easily usable to log any KWP measurement blocks. I've tested logging various blocks from the ME7 in my Cayenne Turbo and it works well. Combined with the ability to alter measurement blocks in the flash ( http://nefariousmotorsports.com/forum/index.php?topic=2349.0 ) , this could provide a good mobile datalogging solution.
16 |
17 | I have also experimented with the various "fast" mechanisms to log ME7 ECUs (DynamicDefineLocalIdentifier, using WriteMem to patch the identifier table), but my Cayenne's ME7 doesn't let me write memory with a Diagnostic session and returns a negative response when I try to open a Development session, so I need to do more reversing work before I can proceed further.
18 |
19 | If you find this useful or have improvements, please send me a pull request!
20 |
21 | This code is released under the BSD License.
22 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 19
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.brianledbetter.kwplogger"
9 | minSdkVersion 19
10 | targetSdkVersion 19
11 | versionCode 4
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:19.+'
26 | compile project(':kwp2000')
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/b3d/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/KWP2000TestIO.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.HexUtil;
4 | import com.brianledbetter.kwplogger.KWP2000.KWP2000IO;
5 | import com.brianledbetter.kwplogger.KWP2000.KWPException;
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | /**
11 | * Created by b3d on 12/12/15.
12 | */
13 | public class KWP2000TestIO implements KWP2000IO {
14 | private List m_readBytes;
15 | private List m_expectedWriteBytes;
16 |
17 | public KWP2000TestIO(List readBytes, List expectedWriteBytes) {
18 | m_readBytes = readBytes;
19 | m_expectedWriteBytes = expectedWriteBytes;
20 | }
21 |
22 | @Override
23 | public byte[] readBytes() throws KWPException {
24 | if (m_readBytes.size() == 0) {
25 | throw new KWPException("Trying to read bytes but none available!");
26 | }
27 | return m_readBytes.remove(0);
28 | }
29 |
30 | @Override
31 | public void writeBytes(byte[] bytesToWrite) throws KWPException {
32 | if(m_expectedWriteBytes.size() == 0) {
33 | throw new KWPException("Wrote more bytes than expected!");
34 | }
35 | byte[] expectedBytes = m_expectedWriteBytes.remove(0);
36 | if (!Arrays.equals(expectedBytes, bytesToWrite)) {
37 | throw new KWPException("Expected we were going to write " + HexUtil.bytesToHexString(expectedBytes) + " but wrote " + HexUtil.bytesToHexString(bytesToWrite));
38 | }
39 | }
40 |
41 | @Override
42 | public void startKWPIO(byte initAddress, byte controllerAddress) throws KWPException {
43 |
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/LoginIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticSession;
4 | import com.brianledbetter.kwplogger.KWP2000.KWP2000IO;
5 | import com.brianledbetter.kwplogger.KWP2000.KWPException;
6 |
7 | import junit.framework.TestCase;
8 |
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * Created by b3d on 12/12/15.
13 | */
14 | public class LoginIntegrationTest extends TestCase {
15 | private KWP2000IO m_testIO;
16 |
17 | @Override
18 | protected void setUp() throws Exception {
19 | super.setUp();
20 | ArrayList readBytes = new ArrayList();
21 | ArrayList writeBytes = new ArrayList();
22 | writeBytes.add(new byte[]{(byte)0x1A,(byte)0x92});
23 | writeBytes.add(new byte[]{(byte)0x27,(byte)0x01});
24 | writeBytes.add(new byte[] {(byte)0x27,(byte)0x02,(byte)0x35,(byte)0x44,(byte)0x18,(byte)0x80});
25 | readBytes.add(new byte[] {(byte)0x5A,(byte)0x92,(byte)0x30,(byte)0x32,(byte)0x36,(byte)0x31,(byte)0x53});
26 | readBytes.add(new byte[] {(byte)0x67,(byte)0x01,(byte)0x01,(byte)0xAA,(byte)0x20,(byte)0xC4});
27 | readBytes.add(new byte[] {(byte)0x67,(byte)0x02});
28 | m_testIO = new KWP2000TestIO(readBytes, writeBytes);
29 | }
30 |
31 | public void testLogin() {
32 | DiagnosticSession testSession = new DiagnosticSession(m_testIO);
33 | try {
34 | assertEquals(testSession.securityLogin(), true);
35 | } catch (KWPException e) {
36 | fail(e.toString());
37 | }
38 |
39 |
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/LoginTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.XorSeedKeyLogin;
4 |
5 | import junit.framework.TestCase;
6 |
7 | /**
8 | * Created by b3d on 12/10/15.
9 | */
10 | public class LoginTest extends TestCase {
11 | @Override
12 | protected void setUp() throws Exception {
13 | super.setUp();
14 | }
15 |
16 | public void testLogin() {
17 | int seed = 0x0CF4233D;
18 | int ecuID = 63;
19 | int key = XorSeedKeyLogin.calculateKey(ecuID,seed);
20 | assertEquals(key, 0xC1393A1C);
21 | seed = 0x0332952C;
22 | key = XorSeedKeyLogin.calculateKey(ecuID,seed);
23 | assertEquals(key, 0x6652A580);
24 | seed = 0x10667401;
25 | key = XorSeedKeyLogin.calculateKey(ecuID,seed);
26 | assertEquals(key, 0xB3B43B58);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/MeasurementValueTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.MeasurementValue;
4 |
5 | import junit.framework.TestCase;
6 |
7 | /**
8 | * Created by b3d on 12/7/15.
9 | */
10 | public class MeasurementValueTest extends TestCase {
11 |
12 | @Override
13 | protected void setUp() throws Exception {
14 | super.setUp();
15 | }
16 |
17 | public void test1ATemperature() {
18 | MeasurementValue testResult = MeasurementValue.parseValue(new byte[] { 0x1A, 0x32, 0x54 });
19 | assertEquals(testResult.stringLabel, "deg C");
20 | assertEquals(testResult.stringValue, "34");
21 | }
22 |
23 | public void test02Percent() {
24 | MeasurementValue testResult = MeasurementValue.parseValue(new byte[] { 0x02, -22, 0x01 });
25 | assertEquals(testResult.stringLabel, "%");
26 | assertEquals(testResult.stringValue, "0.468");
27 | }
28 |
29 | public void test1BDegrees() {
30 | MeasurementValue testResult = MeasurementValue.parseValue(new byte[] { 0x1B, 0x01, -12 });
31 | assertEquals(testResult.stringLabel, "deg BTDC");
32 | assertEquals(testResult.stringValue, "1");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/RetryBusyIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticSession;
4 | import com.brianledbetter.kwplogger.KWP2000.KWP2000IO;
5 | import com.brianledbetter.kwplogger.KWP2000.KWPException;
6 |
7 | import junit.framework.TestCase;
8 |
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * Created by b3d on 2/14/16.
13 | */
14 | public class RetryBusyIntegrationTest extends TestCase {
15 | private KWP2000IO m_testIO;
16 |
17 | @Override
18 | protected void setUp() throws Exception {
19 | super.setUp();
20 | ArrayList readBytes = new ArrayList();
21 | ArrayList writeBytes = new ArrayList();
22 | writeBytes.add(new byte[]{(byte)0x10,(byte)0x89});
23 | writeBytes.add(new byte[]{(byte)0x1A,(byte)0x9B});
24 | readBytes.add(new byte[] {(byte)0x50,(byte)0x89});
25 | readBytes.add(new byte[] {(byte)0x7F,(byte)0x1A,(byte)0x78});
26 | readBytes.add(new byte[] {(byte)0x5A,(byte)0x9B,(byte)0x37,(byte)0x4C,(byte)0x35,(byte)0x39,(byte)0x32,(byte)0x30,(byte)0x39,(byte)0x38,(byte)0x30,(byte)0x43,(byte)0x20,(byte)0x20});
27 | m_testIO = new KWP2000TestIO(readBytes, writeBytes);
28 | }
29 |
30 | public void testRetry() {
31 | DiagnosticSession testSession = new DiagnosticSession(m_testIO);
32 | try {
33 | testSession.startVWDiagnosticSession();
34 | assertEquals(testSession.readECUIdentification().hardwareNumber, "7L5920980C");
35 | } catch (KWPException e) {
36 | fail(e.toString());
37 | }
38 | // 5 A 9 B 37 4 C 35 39 32 30 39 38 30 43 20 20 33 30 31 30 03 00 3 B 64 00 00 00 01 F5 C4 4 B
39 | // 4F 4D 42 49 49 4E 53 54 52 55 4D 45 4E 54 20 52 42 34 20
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/brianledbetter/kwplogger/StartLocalRoutineIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticSession;
4 | import com.brianledbetter.kwplogger.KWP2000.KWP2000IO;
5 | import com.brianledbetter.kwplogger.KWP2000.KWPException;
6 |
7 | import junit.framework.TestCase;
8 |
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * Created by b3d on 12/23/15.
13 | */
14 | public class StartLocalRoutineIntegrationTest extends TestCase {
15 | private KWP2000IO m_testIO;
16 |
17 | @Override
18 | protected void setUp() throws Exception {
19 | super.setUp();
20 | ArrayList readBytes = new ArrayList();
21 | ArrayList writeBytes = new ArrayList();
22 | writeBytes.add(new byte[]{(byte) 0x31, (byte) 0x1A, (byte) 0x01, (byte) 0x02});
23 | readBytes.add(new byte[]{(byte) 0x61, (byte) 0x01, (byte) 0x01, (byte) 0xAA, (byte) 0x20, (byte) 0xC4});
24 | m_testIO = new KWP2000TestIO(readBytes, writeBytes);
25 | }
26 |
27 | public void testLocalRoutine() {
28 | DiagnosticSession testSession = new DiagnosticSession(m_testIO);
29 | try {
30 | assertEquals(testSession.startLocalRoutine((byte)0x1A, new byte[] {0x1, 0x2}), true);
31 | } catch (KWPException e) {
32 | fail(e.toString());
33 | }
34 |
35 |
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/BluetoothPickerDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.app.DialogFragment;
7 | import android.bluetooth.BluetoothDevice;
8 | import android.content.DialogInterface;
9 | import android.os.Bundle;
10 | import android.os.Parcelable;
11 | import android.widget.Button;
12 |
13 | /**
14 | * Created by b3d on 12/19/15.
15 | */
16 | public class BluetoothPickerDialogFragment extends DialogFragment {
17 | public String mSelectedDevice;
18 | public Parcelable[] mPossibleDevices;
19 | BluetoothDialogListener mListener;
20 | Button mOKButton;
21 |
22 | public interface BluetoothDialogListener {
23 | public void onDialogPositiveClick(DialogFragment dialog, String selectedDevice);
24 | public void onDialogNegativeClick(DialogFragment dialog);
25 | }
26 |
27 | @Override
28 | public void onStart() {
29 | super.onStart();
30 | AlertDialog d = (AlertDialog) getDialog();
31 | if (d != null) {
32 | mOKButton = d.getButton(Dialog.BUTTON_POSITIVE);
33 | if (mPossibleDevices.length > 0)
34 | mOKButton.setEnabled(true);
35 | else
36 | mOKButton.setEnabled(false);
37 | }
38 |
39 | }
40 |
41 | @Override
42 | public void onAttach(Activity activity) {
43 | super.onAttach(activity);
44 | try {
45 | mListener = (BluetoothDialogListener) activity;
46 | } catch (ClassCastException e) {
47 | throw new ClassCastException(activity.toString()
48 | + " must implement BluetoothDialogListener");
49 | }
50 | }
51 |
52 | @Override
53 | public Dialog onCreateDialog(Bundle savedInstanceState) {
54 | if (savedInstanceState != null) {
55 | mPossibleDevices = savedInstanceState.getParcelableArray("bluetoothDevices");
56 | }
57 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
58 | CharSequence[] bluetoothDevices = new CharSequence[mPossibleDevices.length];
59 | for (int i = 0; i < mPossibleDevices.length; i++) {
60 | bluetoothDevices[i] = ((BluetoothDevice)mPossibleDevices[i]).getName();
61 | }
62 | mSelectedDevice = ((BluetoothDevice)mPossibleDevices[0]).getAddress();
63 | builder.setTitle(R.string.pick_bluetooth)
64 | .setSingleChoiceItems(bluetoothDevices, 0,
65 | new DialogInterface.OnClickListener() {
66 | @Override
67 | public void onClick(DialogInterface dialog, int which) {
68 | mSelectedDevice = ((BluetoothDevice)mPossibleDevices[which]).getAddress();
69 | }
70 | })
71 | // Set the action buttons
72 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
73 | @Override
74 | public void onClick(DialogInterface dialog, int id) {
75 | mListener.onDialogPositiveClick(BluetoothPickerDialogFragment.this, mSelectedDevice);
76 | }
77 | })
78 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
79 | @Override
80 | public void onClick(DialogInterface dialog, int id) {
81 | mListener.onDialogNegativeClick(BluetoothPickerDialogFragment.this);
82 | }
83 | });
84 | return builder.create();
85 | }
86 |
87 | @Override
88 | public void onSaveInstanceState(Bundle outState) {
89 | super.onSaveInstanceState(outState);
90 | outState.putParcelableArray("bluetoothDevices", mPossibleDevices);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/DetailedMeasurementActivity.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.Activity;
4 | import android.app.DialogFragment;
5 | import android.bluetooth.BluetoothAdapter;
6 | import android.bluetooth.BluetoothDevice;
7 | import android.content.BroadcastReceiver;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.IntentFilter;
11 | import android.os.Bundle;
12 | import android.os.Parcelable;
13 | import android.support.v7.app.ActionBarActivity;
14 | import android.view.Menu;
15 | import android.widget.CompoundButton;
16 | import android.widget.EditText;
17 | import android.widget.NumberPicker;
18 | import android.widget.TextView;
19 | import android.widget.Toast;
20 | import android.widget.ToggleButton;
21 |
22 | import com.brianledbetter.kwplogger.KWP2000.MeasurementValue;
23 |
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.ScheduledExecutorService;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | /**
29 | * Created by b3d on 12/7/15.
30 | */
31 | public class DetailedMeasurementActivity extends ActionBarActivity implements BluetoothPickerDialogFragment.BluetoothDialogListener {
32 | private DiagnosticReceiver m_receiver = null;
33 | private int m_selectedMeasurementGroup = 1;
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_measurement);
39 | ToggleButton toggle = (ToggleButton) findViewById(R.id.connectionToggle);
40 | toggle.setChecked(StateSingleton.getInstance().getIsConnecting());
41 | toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
42 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
43 | if (isChecked) {
44 | if (!StateSingleton.getInstance().getIsConnecting()) {
45 | StateSingleton.getInstance().setIsConnecting(true);
46 | startConnection();
47 | }
48 | } else {
49 | stopConnection();
50 | }
51 | }
52 | });
53 | NumberPicker numberPicker = (NumberPicker) findViewById(R.id.numberPicker);
54 | numberPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
55 | @Override
56 | public void onValueChange(NumberPicker numberPicker, int i, int i1) {
57 | m_selectedMeasurementGroup = numberPicker.getValue();
58 | schedulePolling();
59 | }
60 | });
61 | numberPicker.setMinValue(1);
62 | numberPicker.setMaxValue(255);
63 | numberPicker.setValue(1);
64 | }
65 |
66 | @Override
67 | protected void onStop() {
68 | super.onStop();
69 | unregisterReceiver(m_receiver);
70 | }
71 |
72 | @Override
73 | protected void onStart() {
74 | super.onStart();
75 | registerReceivers();
76 | }
77 |
78 | @Override
79 | public boolean onCreateOptionsMenu(Menu menu) {
80 | return false;
81 | }
82 |
83 | @Override
84 | public void onDialogPositiveClick(DialogFragment dialog, String selectedDevice) {
85 | Intent startIntent = new Intent(this, DiagnosticsService.class);
86 | startIntent.setAction(DiagnosticsService.START_DIAGNOSTICS_SERVICE);
87 | String initAddress = ((EditText)findViewById(R.id.initAddress)).getText().toString();
88 | String remoteAddress = ((EditText)findViewById(R.id.controllerAddress)).getText().toString();
89 | startIntent.putExtra(DiagnosticsService.INIT_ADDRESS, Integer.parseInt(initAddress));
90 | startIntent.putExtra(DiagnosticsService.REMOTE_ADDRESS, Integer.parseInt(remoteAddress));
91 | startIntent.putExtra(DiagnosticsService.BLUETOOTH_DEVICE, selectedDevice);
92 | startService(startIntent);
93 | }
94 |
95 | @Override
96 | public void onDialogNegativeClick(DialogFragment dialog) {
97 | ToggleButton toggle = (ToggleButton) findViewById(R.id.connectionToggle);
98 | toggle.setChecked(false);
99 | }
100 |
101 | public void startConnection() {
102 | BluetoothAdapter b = BluetoothAdapter.getDefaultAdapter();
103 |
104 | if (!b.isEnabled()) {
105 | Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
106 | startActivityForResult(turnOn, 0);
107 | }
108 |
109 | BluetoothDevice[] devices = b.getBondedDevices().toArray(new BluetoothDevice[0]);
110 |
111 | BluetoothPickerDialogFragment bpdf = new BluetoothPickerDialogFragment();
112 | bpdf.mPossibleDevices = devices;
113 | bpdf.show(getFragmentManager(), "BluetoothPickerDialogFragment");
114 | }
115 |
116 | public void stopConnection() {
117 | StateSingleton.getInstance().setIsConnected(false);
118 | StateSingleton.getInstance().setIsConnecting(false);
119 | Intent stopIntent = new Intent(this, PollingService.class);
120 | stopIntent.setAction(PollingService.STOP_POLL_DIAGNOSTICS_SERVICE);
121 | startService(stopIntent);
122 | Intent stopBluetoothIntent = new Intent(this, DiagnosticsService.class);
123 | stopBluetoothIntent.setAction(DiagnosticsService.END_DIAGNOSTICS_SERVICE);
124 | startService(stopBluetoothIntent);
125 | }
126 |
127 | private void registerReceivers() {
128 | IntentFilter ecuFilter = new IntentFilter(DiagnosticReceiver.ECU_RESP);
129 | ecuFilter.addCategory(Intent.CATEGORY_DEFAULT);
130 | IntentFilter dataFilter = new IntentFilter(DiagnosticReceiver.MEASUREMENT_RESP);
131 | dataFilter.addCategory(Intent.CATEGORY_DEFAULT);
132 | IntentFilter failureFilter = new IntentFilter(DiagnosticReceiver.FAILURE_RESP);
133 | failureFilter.addCategory(Intent.CATEGORY_DEFAULT);
134 | m_receiver = new DiagnosticReceiver();
135 | registerReceiver(m_receiver, ecuFilter);
136 | registerReceiver(m_receiver, dataFilter);
137 | registerReceiver(m_receiver, failureFilter);
138 | }
139 |
140 | private void schedulePolling() {
141 | if (!StateSingleton.getInstance().getIsConnected()) return;
142 | Intent startIntent = new Intent(getApplicationContext(), PollingService.class);
143 | startIntent.setAction(PollingService.START_POLL_DIAGNOSTICS_SERVICE);
144 | startIntent.putExtra(PollingService.MEASUREMENT_GROUP, m_selectedMeasurementGroup);
145 | startService(startIntent);
146 | }
147 |
148 | public class DiagnosticReceiver extends BroadcastReceiver {
149 | public static final String ECU_RESP = "com.brianledbetter.kwplogger.ECU_ID";
150 | public static final String MEASUREMENT_RESP = "com.brianledbetter.kwplogger.MEASUREMENT";
151 | public static final String FAILURE_RESP = "com.brianledbetter.kwplogger.FAILURE";
152 |
153 | @Override
154 | public void onReceive(Context context, Intent intent) {
155 | if (intent.getAction().equals(ECU_RESP)) {
156 | TextView ecuIDView = (TextView)findViewById(R.id.partNumber);
157 | ecuIDView.setText(intent.getStringExtra(DiagnosticsService.ECU_ID_STRING));
158 | StateSingleton.getInstance().setIsConnected(true);
159 | schedulePolling();
160 | } else if(intent.getAction().equals(MEASUREMENT_RESP)) {
161 | ParcelableMeasurementValues values = intent.getParcelableExtra(DiagnosticsService.VALUE_STRING);
162 | for (int i = 0; i < values.measurementValues.size(); i++) {
163 | TextView valueLabel = (TextView)findViewById(getResources().getIdentifier("value" + (i + 1), "id", getPackageName()));
164 | TextView labelLabel = (TextView)findViewById(getResources().getIdentifier("label" + (i + 1), "id", getPackageName()));
165 | MeasurementValue value = values.measurementValues.get(i);
166 | if (valueLabel != null) // Guards against too many measured values coming back.
167 | valueLabel.setText(value.stringValue);
168 | if (labelLabel != null)
169 | labelLabel.setText(value.stringLabel);
170 | }
171 | } else if(intent.getAction().equals(FAILURE_RESP)) {
172 | Toast.makeText(getApplicationContext(), "ERROR! " + intent.getStringExtra(DiagnosticsService.ERROR_STRING), Toast.LENGTH_LONG).show();
173 | stopConnection();
174 | }
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/DiagnosticCodesActivity.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.DialogFragment;
4 | import android.app.ListActivity;
5 | import android.bluetooth.BluetoothAdapter;
6 | import android.bluetooth.BluetoothDevice;
7 | import android.content.BroadcastReceiver;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.IntentFilter;
11 | import android.os.Bundle;
12 | import android.os.Parcelable;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.view.View;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.CompoundButton;
18 | import android.widget.ListView;
19 | import android.widget.Toast;
20 | import android.widget.ToggleButton;
21 |
22 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticTroubleCode;
23 |
24 | /**
25 | * Created by b3d on 12/22/15.
26 | */
27 | public class DiagnosticCodesActivity extends ListActivity implements BluetoothPickerDialogFragment.BluetoothDialogListener {
28 | DiagnosticReceiver m_receiver = null;
29 |
30 | public void onCreate(Bundle bundle) {
31 | super.onCreate(bundle);
32 | setContentView(R.layout.diagnostic_codes);
33 | ToggleButton toggle = (ToggleButton) findViewById(R.id.toggleButton);
34 | toggle.setChecked(StateSingleton.getInstance().getIsConnecting());
35 | toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
36 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
37 | if (isChecked) {
38 | if (!StateSingleton.getInstance().getIsConnecting()) {
39 | StateSingleton.getInstance().setIsConnecting(true);
40 | startConnection();
41 | }
42 | } else {
43 | stopConnection();
44 | }
45 | }
46 | });
47 | String[] values = new String[] { "Not Connected" };
48 | ArrayAdapter adapter = new ArrayAdapter(this,
49 | android.R.layout.simple_list_item_1, values);
50 | setListAdapter(adapter);
51 | }
52 |
53 | @Override
54 | protected void onListItemClick(ListView l, View v, int position, long id) {
55 | super.onListItemClick(l, v, position, id);
56 | }
57 |
58 | @Override
59 | protected void onStop() {
60 | super.onStop();
61 | unregisterReceiver(m_receiver);
62 | }
63 |
64 | @Override
65 | protected void onStart() {
66 | super.onStart();
67 | registerReceivers();
68 | }
69 |
70 | private void registerReceivers() {
71 | IntentFilter dataFilter = new IntentFilter(DiagnosticReceiver.CODES_RESP);
72 | dataFilter.addCategory(Intent.CATEGORY_DEFAULT);
73 | IntentFilter ecuFilter = new IntentFilter(DiagnosticReceiver.ECU_RESP);
74 | ecuFilter.addCategory(Intent.CATEGORY_DEFAULT);
75 | IntentFilter failureFilter = new IntentFilter(DiagnosticReceiver.FAILURE_RESP);
76 | failureFilter.addCategory(Intent.CATEGORY_DEFAULT);
77 | m_receiver = new DiagnosticReceiver();
78 | registerReceiver(m_receiver, dataFilter);
79 | registerReceiver(m_receiver, failureFilter);
80 | registerReceiver(m_receiver, ecuFilter);
81 | }
82 |
83 | public void startConnection() {
84 | BluetoothAdapter b = BluetoothAdapter.getDefaultAdapter();
85 |
86 | if (!b.isEnabled()) {
87 | Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
88 | startActivityForResult(turnOn, 0);
89 | }
90 |
91 | Parcelable[] devices = b.getBondedDevices().toArray(new BluetoothDevice[0]);
92 | if (devices.length > 0) {
93 | BluetoothPickerDialogFragment bpdf = new BluetoothPickerDialogFragment();
94 | bpdf.mPossibleDevices = devices;
95 | bpdf.show(getFragmentManager(), "BluetoothPickerDialogFragment");
96 | } else {
97 | Toast.makeText(getApplicationContext(), "ERROR! " + "No Bluetooth Device available!", Toast.LENGTH_LONG).show();
98 | }
99 | }
100 |
101 | public void stopConnection() {
102 | ToggleButton toggle = (ToggleButton) findViewById(R.id.toggleButton);
103 | toggle.setChecked(false);
104 | StateSingleton.getInstance().setIsConnected(false);
105 | StateSingleton.getInstance().setIsConnecting(false);
106 | Intent stopBluetoothIntent = new Intent(this, DiagnosticsService.class);
107 | stopBluetoothIntent.setAction(DiagnosticsService.END_DIAGNOSTICS_SERVICE);
108 | startService(stopBluetoothIntent);
109 | }
110 |
111 | @Override
112 | public void onDialogPositiveClick(DialogFragment dialog, String selectedDevice) {
113 | Intent startIntent = new Intent(this, DiagnosticsService.class);
114 | startIntent.setAction(DiagnosticsService.START_DIAGNOSTICS_SERVICE);
115 | startIntent.putExtra(DiagnosticsService.INIT_ADDRESS, 0x1);
116 | startIntent.putExtra(DiagnosticsService.REMOTE_ADDRESS, 0x10);
117 | startIntent.putExtra(DiagnosticsService.BLUETOOTH_DEVICE, selectedDevice);
118 | startService(startIntent);
119 | }
120 |
121 | @Override
122 | public void onDialogNegativeClick(DialogFragment dialog) {
123 | ToggleButton toggle = (ToggleButton) findViewById(R.id.toggleButton);
124 | toggle.setChecked(false);
125 | }
126 |
127 | @Override
128 | public boolean onCreateOptionsMenu(Menu menu) {
129 | getMenuInflater().inflate(R.menu.menu_codes, menu);
130 | return true;
131 | }
132 |
133 | @Override
134 | public boolean onOptionsItemSelected(MenuItem item) {
135 | // Handle action bar item clicks here. The action bar will
136 | // automatically handle clicks on the Home/Up button, so long
137 | // as you specify a parent activity in AndroidManifest.xml.
138 | int id = item.getItemId();
139 |
140 | if (id == R.id.action_codes) {
141 | Intent codesIntent = new Intent(DiagnosticCodesActivity.this, DiagnosticsService.class);
142 | codesIntent.setAction(DiagnosticsService.CLEAR_CODES_SERVICE);
143 | startService(codesIntent);
144 | return true;
145 | }
146 |
147 | return super.onOptionsItemSelected(item);
148 | }
149 |
150 | public class DiagnosticReceiver extends BroadcastReceiver {
151 | public static final String CODES_RESP = "com.brianledbetter.kwplogger.CODES";
152 | public static final String FAILURE_RESP = "com.brianledbetter.kwplogger.FAILURE";
153 | public static final String ECU_RESP = "com.brianledbetter.kwplogger.ECU_ID";
154 |
155 | @Override
156 | public void onReceive(Context context, Intent intent) {
157 |
158 | if (intent.getAction().equals(ECU_RESP)) {
159 | StateSingleton.getInstance().setIsConnected(true);
160 | Intent codesIntent = new Intent(DiagnosticCodesActivity.this, DiagnosticsService.class);
161 | codesIntent.setAction(DiagnosticsService.READ_CODES_SERVICE);
162 | startService(codesIntent);
163 | }
164 | else if (intent.getAction().equals(CODES_RESP)) {
165 | StateSingleton.getInstance().setIsConnected(true);
166 | ParcelableDTC dtcs = intent.getParcelableExtra(DiagnosticsService.CODES_STRING);
167 | String[] codesStrings = new String[dtcs.dtcs.size()];
168 | int i = 0;
169 | for (DiagnosticTroubleCode dtc : dtcs.dtcs) {
170 | codesStrings[i] = Integer.toString(dtc.statusCode);
171 | i++;
172 | }
173 | ArrayAdapter adapter = new ArrayAdapter(DiagnosticCodesActivity.this,
174 | android.R.layout.simple_list_item_1, codesStrings);
175 | setListAdapter(adapter);
176 | } else if(intent.getAction().equals(FAILURE_RESP)) {
177 | Toast.makeText(getApplicationContext(), "ERROR! " + intent.getStringExtra(DiagnosticsService.ERROR_STRING), Toast.LENGTH_LONG).show();
178 | stopConnection();
179 | }
180 | }
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/DiagnosticsService.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.bluetooth.BluetoothDevice;
5 | import android.bluetooth.BluetoothSocket;
6 | import android.content.Intent;
7 | import android.util.Log;
8 |
9 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticSession;
10 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticTroubleCode;
11 | import com.brianledbetter.kwplogger.KWP2000.ECUIdentification;
12 | import com.brianledbetter.kwplogger.KWP2000.ELMIO;
13 | import com.brianledbetter.kwplogger.KWP2000.HexUtil;
14 | import com.brianledbetter.kwplogger.KWP2000.KWP2000IO;
15 | import com.brianledbetter.kwplogger.KWP2000.KWPException;
16 | import com.brianledbetter.kwplogger.KWP2000.MeasurementValue;
17 |
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.OutputStream;
21 | import java.util.List;
22 | import java.util.UUID;
23 |
24 | /**
25 | * Created by b3d on 12/6/15.
26 | */
27 | public class DiagnosticsService extends PermanentService {
28 | public static final String START_DIAGNOSTICS_SERVICE = "com.brianledbetter.kwplogger.StartService";
29 | public static final String READ_MEMORY_SERVICE = "com.brianledbetter.kwplogger.ReadMemoryService";
30 | public static final String POLL_DIAGNOSTICS_SERVICE = "com.brianledbetter.kwplogger.PollService";
31 | public static final String END_DIAGNOSTICS_SERVICE = "com.brianledbetter.kwplogger.EndService";
32 | public static final String READ_CODES_SERVICE = "com.brianledbetter.kwplogger.ReadCodesService";
33 | public static final String CLEAR_CODES_SERVICE = "com.brianledbetter.kwplogger.ClearCodesService";
34 | public static final String RESET_CLUSTER_SERVICE = "com.brianledbetter.kwplogger.ResetClusterService";
35 |
36 | public static final String ECU_ID_STRING = "ecuID";
37 | public static final String VALUE_STRING = "value";
38 | public static final String CODES_STRING = "codes";
39 |
40 | public static final String MEASUREMENT_GROUP = "measurementGroup";
41 | public static final String MEMORY_ADDRESS = "memoryAddress";
42 | public static final String MEMORY_SIZE = "memorySize";
43 | public static final String INIT_ADDRESS = "initAddress";
44 | public static final String REMOTE_ADDRESS = "remoteAddress";
45 | public static final String BLUETOOTH_DEVICE = "bluetoothDevice";
46 | public static final String ERROR_STRING = "error";
47 |
48 | // this is the "well known" UUID for Bluetooth SPP (Serial Profile).
49 | private static final UUID SPP_UUID =
50 | UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
51 |
52 | private BluetoothSocket m_btSocket = null;
53 | private DiagnosticSession m_kwp = null;
54 | private KWP2000IO m_ELMKWP = null;
55 | private boolean m_isConnected = false;
56 |
57 | public DiagnosticsService() {
58 | super("com.brianledbetter.kwplogger.KWP2000Service");
59 | }
60 | @Override
61 | protected void onHandleIntent(Intent intent) {
62 | if (intent == null) return;
63 | if (intent.getAction().equals(START_DIAGNOSTICS_SERVICE)) {
64 | int initAddress = intent.getIntExtra(INIT_ADDRESS, 0x1);
65 | int remoteAddress = intent.getIntExtra(REMOTE_ADDRESS, 0x10);
66 | String bluetoothDevice = intent.getStringExtra(BLUETOOTH_DEVICE);
67 | Log.d("KWP", "Starting connection!");
68 | startConnection(initAddress, remoteAddress, bluetoothDevice);
69 | }
70 | if (intent.getAction().equals(POLL_DIAGNOSTICS_SERVICE)) {
71 | int measurementGroup = intent.getIntExtra(MEASUREMENT_GROUP, 0x1);
72 | Log.d("KWP", "Polling... " + measurementGroup);
73 | pollData(measurementGroup);
74 | }
75 | if (intent.getAction().equals(END_DIAGNOSTICS_SERVICE)) {
76 | Log.d("KWP", "Ending connection...");
77 | endConnection();
78 | }
79 | if (intent.getAction().equals(READ_MEMORY_SERVICE)) {
80 | Log.d("KWP", "Reading Memory");
81 | int address = intent.getIntExtra(MEMORY_ADDRESS, 0x1);
82 | int size = intent.getIntExtra(MEMORY_SIZE, 0x1);
83 | readMemory(address, size);
84 | }
85 | if (intent.getAction().equals(READ_CODES_SERVICE)) {
86 | Log.d("KWP", "Reading Codes");
87 | readCodes();
88 | }
89 | if (intent.getAction().equals(CLEAR_CODES_SERVICE)) {
90 | Log.d("KWP", "Clearing Codes");
91 | clearCodes();
92 | }
93 | if (intent.getAction().equals(RESET_CLUSTER_SERVICE)) {
94 | Log.d("KWP", "Resetting cluster...");
95 | resetCluster(intent.getStringExtra(BLUETOOTH_DEVICE));
96 | }
97 | }
98 |
99 | @Override
100 | public void onDestroy() {
101 | super.onDestroy();
102 | endConnection();
103 | }
104 |
105 | private void endConnection() {
106 | try {
107 | m_kwp.stopSession();
108 | m_btSocket.close();
109 | m_isConnected = false;
110 | stopSelf();
111 | } catch (KWPException e) {
112 | Log.d("KWP", "Failed to close connection!");
113 | } catch (IOException e) { // Got to love early 2000s Java, thanks Android
114 | Log.d("KWP", "I/O error closing connection!");
115 | } catch (NullPointerException e) {
116 | Log.d("KWP", "Connection was not fully established before being ended!");
117 | }
118 | }
119 |
120 | private ECUIdentification startConnection(int initAddress, int remoteAddress, String bluetoothDevice) {
121 | try {
122 | if(!connectBluetooth(initAddress, remoteAddress, bluetoothDevice)) return null;
123 | connectKWP2000();
124 | ECUIdentification ecuID = m_kwp.readECUIdentification();
125 | Log.d("KWP", "Got string " + ecuID.hardwareNumber + " for hardware number");
126 | m_isConnected = true;
127 | Intent broadcastIntent = new Intent();
128 | broadcastIntent.setAction(MainActivity.DiagnosticReceiver.ECU_RESP);
129 | broadcastIntent.putExtra(ECU_ID_STRING, ecuID.hardwareNumber);
130 | broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
131 | sendBroadcast(broadcastIntent);
132 | return ecuID;
133 | } catch (KWPException e) {
134 | endConnection();
135 | broadcastError("Issue opening ECU. Is the key on? " + e.toString());
136 | return null;
137 | }
138 | }
139 |
140 | private boolean connectBluetooth(int initAddress, int remoteAddress, String bluetoothDeviceAddress) {
141 | BluetoothAdapter b = BluetoothAdapter.getDefaultAdapter();
142 | BluetoothDevice bluetoothDevice = b.getRemoteDevice(bluetoothDeviceAddress);
143 | try {
144 | m_btSocket = bluetoothDevice.createRfcommSocketToServiceRecord(SPP_UUID);
145 | Log.d("KWP", "Created RFComm Service");
146 | } catch (IOException e) {
147 | Log.d("KWP", "RFComm Creation Failed");
148 | broadcastError("Issue opening connection. Is the ELM327 device on? " + e.toString());
149 | return false;
150 | }
151 | b.cancelDiscovery();
152 | try {
153 | m_btSocket.connect();
154 | Log.d("KWP", "Serial port opened!");
155 | OutputStream outStream = m_btSocket.getOutputStream();
156 | InputStream inStream = m_btSocket.getInputStream();
157 | m_ELMKWP = new ELMIO(inStream, outStream);
158 | } catch (IOException e) {
159 | Log.d("KWP", "RFComm Connection Failed");
160 | broadcastError("Issue opening connection. Is the ELM327 device on? " + e.toString());
161 | try {
162 | m_btSocket.close();
163 | return false;
164 | } catch (IOException e2) { // J A V A
165 | broadcastError("Issue closing connection. Is the ELM327 device on? " + e.toString());
166 | }
167 | }
168 | try {
169 | m_ELMKWP.startKWPIO((byte)initAddress, (byte)remoteAddress);
170 | } catch (KWPException e) {
171 | Log.d("KWP", "Connection failed!");
172 | return false;
173 | }
174 | Log.d("KWP", "KWP Connection Succeeded");
175 | return true;
176 | }
177 |
178 | private void connectKWP2000() throws KWPException {
179 | if(!m_btSocket.isConnected()) {
180 | Log.d("KWP", "Trying to connect to a closed socket!");
181 | throw new KWPException("Trying to connect to a closed Bluetooth device!");
182 | }
183 | m_kwp = new DiagnosticSession(m_ELMKWP);
184 | m_kwp.startVWDiagnosticSession();
185 | }
186 |
187 | private void pollData(int measurementIdentifier) {
188 | if(!m_isConnected) {
189 | return;
190 | }
191 | try {
192 | List measurementValues = m_kwp.readIdentifier(measurementIdentifier);
193 | if (measurementValues.size() > 0) {
194 | Log.d("KWP", "Got values : ");
195 | for (int i = 0; i < measurementValues.size(); i++)
196 | {
197 | Log.d("KWP", "Value " + i + " : " + measurementValues.get(i).stringValue + " " + measurementValues.get(i).stringLabel + " for identifier " + measurementIdentifier);
198 | }
199 | Intent broadcastIntent = new Intent();
200 | broadcastIntent.setAction(MainActivity.DiagnosticReceiver.MEASUREMENT_RESP);
201 | broadcastIntent.putExtra(VALUE_STRING, new ParcelableMeasurementValues(measurementValues));
202 | broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
203 | sendBroadcast(broadcastIntent);
204 | }
205 | }
206 | catch (KWPException e)
207 | {
208 | Log.d("KWP", "Failed to poll due to " + e.toString());
209 | }
210 | }
211 |
212 | private void securityLogin() {
213 | if(!m_isConnected) {
214 | return;
215 | }
216 | try {
217 | m_kwp.securityLogin();
218 | } catch (KWPException e)
219 | {
220 | Log.d("KWP", "Failed to login due to " + e.toString());
221 | }
222 | }
223 |
224 | private void readMemory(int address, int length) {
225 | if (!m_isConnected) {
226 | return;
227 | }
228 | try {
229 | Log.d("KWP", "Read memory : " + HexUtil.bytesToHexString(m_kwp.readMemoryByAddress(address, length)));
230 | } catch (KWPException e)
231 | {
232 | Log.d("KWP", "Failed to read memory due to " + e.toString());
233 | }
234 | }
235 |
236 | private void readCodes() {
237 | if(!m_isConnected) {
238 | return;
239 | }
240 | try {
241 | List dtcs = m_kwp.readDTCs();
242 | Intent broadcastIntent = new Intent();
243 | broadcastIntent.setAction(DiagnosticCodesActivity.DiagnosticReceiver.CODES_RESP);
244 | broadcastIntent.putExtra(CODES_STRING, new ParcelableDTC(dtcs));
245 | broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
246 | sendBroadcast(broadcastIntent);
247 |
248 | } catch (KWPException e)
249 | {
250 | broadcastError("Issue reading DTCs. Is the key on? " + e.toString());
251 | }
252 | }
253 |
254 | private void clearCodes() {
255 | if(!m_isConnected) {
256 | return;
257 | }
258 | try {
259 | m_kwp.clearDTCs();
260 | readCodes();
261 |
262 | } catch (KWPException e)
263 | {
264 | broadcastError("Issue clearing DTCs. Is the key on? " + e.toString());
265 | }
266 | }
267 |
268 | private void resetCluster(String bluetoothDevice) {
269 | int initAddress = 0x97;
270 | int remoteAddress = 0x61;
271 | ECUIdentification identification = startConnection(initAddress, remoteAddress, bluetoothDevice);
272 | if(identification == null) return;
273 | try {
274 | Log.d("KWP", "Preparing to reset service indicator for cluster " + identification.hardwareNumber);
275 | m_kwp.clearCayenneClusterServiceIndicator();
276 | } catch (KWPException e)
277 | {
278 | broadcastError("Issue clearing cluster. Is the key on? " + e.toString());
279 | }
280 | }
281 |
282 | private void broadcastError(String errorMessage) {
283 | Intent broadcastIntent = new Intent();
284 | broadcastIntent.setAction(MainActivity.DiagnosticReceiver.FAILURE_RESP);
285 | broadcastIntent.putExtra(ERROR_STRING, errorMessage);
286 | broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
287 | sendBroadcast(broadcastIntent);
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.DialogFragment;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.content.BroadcastReceiver;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.IntentFilter;
10 | import android.os.Bundle;
11 | import android.os.Parcelable;
12 | import android.support.v7.app.ActionBarActivity;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.widget.CompoundButton;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 | import android.widget.ToggleButton;
19 |
20 | public class MainActivity extends ActionBarActivity implements BluetoothPickerDialogFragment.BluetoothDialogListener {
21 | DiagnosticReceiver m_receiver = null;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.activity_main);
27 | ToggleButton toggle = (ToggleButton) findViewById(R.id.connectionToggle);
28 | toggle.setChecked(StateSingleton.getInstance().getIsConnecting());
29 | toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
30 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
31 | if (isChecked) {
32 | if (!StateSingleton.getInstance().getIsConnecting()) {
33 | StateSingleton.getInstance().setIsConnecting(true);
34 | startConnection();
35 | }
36 | } else {
37 | stopConnection();
38 | }
39 | }
40 | });
41 | }
42 |
43 | @Override
44 | protected void onStop() {
45 | super.onStop();
46 | unregisterReceiver(m_receiver);
47 | }
48 |
49 | @Override
50 | protected void onStart() {
51 | super.onStart();
52 | registerReceivers();
53 | }
54 |
55 | @Override
56 | public boolean onCreateOptionsMenu(Menu menu) {
57 | getMenuInflater().inflate(R.menu.menu_main, menu);
58 | return true;
59 | }
60 |
61 | @Override
62 | public boolean onOptionsItemSelected(MenuItem item) {
63 | // Handle action bar item clicks here. The action bar will
64 | // automatically handle clicks on the Home/Up button, so long
65 | // as you specify a parent activity in AndroidManifest.xml.
66 | int id = item.getItemId();
67 |
68 | if (id == R.id.action_settings) {
69 | Intent intent = new Intent(this, DetailedMeasurementActivity.class);
70 | startActivity(intent);
71 | return true;
72 | }
73 |
74 | if (id == R.id.action_dtcs) {
75 | Intent intent = new Intent(this, DiagnosticCodesActivity.class);
76 | startActivity(intent);
77 | return true;
78 | }
79 |
80 | if (id == R.id.action_reset) {
81 | Intent intent = new Intent(this, ResetClusterActivity.class);
82 | startActivity(intent);
83 | return true;
84 | }
85 |
86 | return super.onOptionsItemSelected(item);
87 | }
88 |
89 | @Override
90 | public void onDialogPositiveClick(DialogFragment dialog, String selectedDevice) {
91 | Intent startIntent = new Intent(this, DiagnosticsService.class);
92 | startIntent.setAction(DiagnosticsService.START_DIAGNOSTICS_SERVICE);
93 | startIntent.putExtra(DiagnosticsService.INIT_ADDRESS, 0x2);
94 | startIntent.putExtra(DiagnosticsService.REMOTE_ADDRESS, 0x1A);
95 | startIntent.putExtra(DiagnosticsService.BLUETOOTH_DEVICE, selectedDevice);
96 | startService(startIntent);
97 | }
98 |
99 | @Override
100 | public void onDialogNegativeClick(DialogFragment dialog) {
101 | ToggleButton toggle = (ToggleButton) findViewById(R.id.connectionToggle);
102 | toggle.setChecked(false);
103 | }
104 |
105 | public void startConnection() {
106 | BluetoothAdapter b = BluetoothAdapter.getDefaultAdapter();
107 |
108 | if (!b.isEnabled()) {
109 | Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
110 | startActivityForResult(turnOn, 0);
111 | }
112 |
113 | Parcelable[] devices = b.getBondedDevices().toArray(new BluetoothDevice[0]);
114 | if (devices.length > 0) {
115 | BluetoothPickerDialogFragment bpdf = new BluetoothPickerDialogFragment();
116 | bpdf.mPossibleDevices = devices;
117 | bpdf.show(getFragmentManager(), "BluetoothPickerDialogFragment");
118 | } else {
119 | Toast.makeText(getApplicationContext(), "ERROR! " + "No Bluetooth Device available!", Toast.LENGTH_LONG).show();
120 | }
121 | }
122 |
123 | public void stopConnection() {
124 | ToggleButton toggle = (ToggleButton) findViewById(R.id.connectionToggle);
125 | toggle.setChecked(false);
126 | StateSingleton.getInstance().setIsConnected(false);
127 | StateSingleton.getInstance().setIsConnecting(false);
128 | Intent stopIntent = new Intent(this, PollingService.class);
129 | stopIntent.setAction(PollingService.STOP_POLL_DIAGNOSTICS_SERVICE);
130 | startService(stopIntent);
131 | Intent stopBluetoothIntent = new Intent(this, DiagnosticsService.class);
132 | stopBluetoothIntent.setAction(DiagnosticsService.END_DIAGNOSTICS_SERVICE);
133 | startService(stopBluetoothIntent);
134 | }
135 |
136 | private void registerReceivers() {
137 | IntentFilter ecuFilter = new IntentFilter(DiagnosticReceiver.ECU_RESP);
138 | ecuFilter.addCategory(Intent.CATEGORY_DEFAULT);
139 | IntentFilter dataFilter = new IntentFilter(DiagnosticReceiver.MEASUREMENT_RESP);
140 | dataFilter.addCategory(Intent.CATEGORY_DEFAULT);
141 | IntentFilter failureFilter = new IntentFilter(DiagnosticReceiver.FAILURE_RESP);
142 | failureFilter.addCategory(Intent.CATEGORY_DEFAULT);
143 | m_receiver = new DiagnosticReceiver();
144 | registerReceiver(m_receiver, ecuFilter);
145 | registerReceiver(m_receiver, dataFilter);
146 | registerReceiver(m_receiver, failureFilter);
147 | }
148 |
149 | private void schedulePolling() {
150 | if (!StateSingleton.getInstance().getIsConnected()) return;
151 | Intent startIntent = new Intent(getApplicationContext(), PollingService.class);
152 | startIntent.setAction(PollingService.START_POLL_DIAGNOSTICS_SERVICE);
153 | startIntent.putExtra(PollingService.MEASUREMENT_GROUP, 0x6);
154 | startService(startIntent);
155 | }
156 |
157 | public class DiagnosticReceiver extends BroadcastReceiver {
158 | public static final String ECU_RESP = "com.brianledbetter.kwplogger.ECU_ID";
159 | public static final String MEASUREMENT_RESP = "com.brianledbetter.kwplogger.MEASUREMENT";
160 | public static final String FAILURE_RESP = "com.brianledbetter.kwplogger.FAILURE";
161 |
162 | @Override
163 | public void onReceive(Context context, Intent intent) {
164 | if (intent.getAction().equals(ECU_RESP)) {
165 | TextView ecuIDView = (TextView)findViewById(R.id.partNumber);
166 | ecuIDView.setText(intent.getStringExtra(DiagnosticsService.ECU_ID_STRING));
167 | StateSingleton.getInstance().setIsConnected(true);
168 | schedulePolling();
169 | } else if(intent.getAction().equals(MEASUREMENT_RESP)) {
170 | TextView valueView = (TextView)findViewById(R.id.valueValue);
171 | ParcelableMeasurementValues values = intent.getParcelableExtra(DiagnosticsService.VALUE_STRING);
172 | valueView.setText(values.measurementValues.get(0).stringValue);
173 | valueView = (TextView)findViewById(R.id.valueLabel);
174 | valueView.setText(values.measurementValues.get(0).stringLabel);
175 | } else if(intent.getAction().equals(FAILURE_RESP)) {
176 | Toast.makeText(getApplicationContext(), "ERROR! " + intent.getStringExtra(DiagnosticsService.ERROR_STRING), Toast.LENGTH_LONG).show();
177 | stopConnection();
178 | }
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/ParcelableDTC.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import com.brianledbetter.kwplogger.KWP2000.DiagnosticTroubleCode;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * Created by b3d on 12/22/15.
13 | */
14 | public class ParcelableDTC implements Parcelable {
15 | public List dtcs;
16 | public int describeContents() {
17 | return 0;
18 | }
19 |
20 | public void writeToParcel(Parcel out, int flags) {
21 | out.writeInt(dtcs.size());
22 | for (DiagnosticTroubleCode dtc : dtcs)
23 | {
24 | out.writeInt(dtc.codeNumber);
25 | out.writeInt(dtc.statusCode);
26 | }
27 | }
28 |
29 | public static final Parcelable.Creator CREATOR
30 | = new Parcelable.Creator() {
31 | public ParcelableDTC createFromParcel(Parcel in) {
32 | return new ParcelableDTC(in);
33 | }
34 |
35 | public ParcelableDTC[] newArray(int size) {
36 | return new ParcelableDTC[size];
37 | }
38 | };
39 |
40 | private ParcelableDTC(Parcel in) {
41 | int numberOfValues = in.readInt();
42 | dtcs = new ArrayList(numberOfValues);
43 | for (int i = 0; i < numberOfValues; i++) {
44 | DiagnosticTroubleCode dtc = new DiagnosticTroubleCode(in.readInt(), in.readInt());
45 | dtcs.add(dtc);
46 | }
47 | }
48 |
49 | public ParcelableDTC(List mv) {
50 | dtcs = mv;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/ParcelableMeasurementValues.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import com.brianledbetter.kwplogger.KWP2000.MeasurementValue;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * Created by b3d on 12/7/15.
13 | */
14 |
15 | public class ParcelableMeasurementValues implements Parcelable {
16 | public List measurementValues;
17 | public int describeContents() {
18 | return 0;
19 | }
20 |
21 | public void writeToParcel(Parcel out, int flags) {
22 | out.writeInt(measurementValues.size());
23 | for (MeasurementValue mv : measurementValues)
24 | {
25 | out.writeString(mv.stringValue);
26 | out.writeString(mv.stringLabel);
27 | }
28 | }
29 |
30 | public static final Parcelable.Creator CREATOR
31 | = new Parcelable.Creator() {
32 | public ParcelableMeasurementValues createFromParcel(Parcel in) {
33 | return new ParcelableMeasurementValues(in);
34 | }
35 |
36 | public ParcelableMeasurementValues[] newArray(int size) {
37 | return new ParcelableMeasurementValues[size];
38 | }
39 | };
40 |
41 | private ParcelableMeasurementValues(Parcel in) {
42 | int numberOfValues = in.readInt();
43 | measurementValues = new ArrayList(numberOfValues);
44 | for (int i = 0; i < numberOfValues; i++) {
45 | MeasurementValue mv = new MeasurementValue(in.readString(), in.readString());
46 | measurementValues.add(mv);
47 | }
48 | }
49 |
50 | public ParcelableMeasurementValues(List mv) {
51 | measurementValues = mv;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/PermanentService.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.Handler;
6 | import android.os.HandlerThread;
7 | import android.os.IBinder;
8 | import android.os.Looper;
9 | import android.os.Message;
10 |
11 | /**
12 | * Created by b3d on 12/6/15.
13 | */
14 | public abstract class PermanentService extends Service {
15 | // This is a simple re-implementation of the standard issue Android IntentService, modified to never stop.
16 |
17 | private String mName;
18 | private volatile Looper mServiceLooper;
19 | private volatile ServiceHandler mServiceHandler;
20 |
21 | public PermanentService(String name) {
22 | super();
23 | mName = name;
24 | }
25 |
26 | private final class ServiceHandler extends Handler {
27 | public ServiceHandler(Looper looper) {
28 | super(looper);
29 | }
30 |
31 | @Override
32 | public void handleMessage(Message msg) {
33 | onHandleIntent((Intent)msg.obj);
34 | }
35 | }
36 |
37 | @Override
38 | public void onCreate() {
39 | super.onCreate();
40 | HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
41 | thread.start();
42 |
43 | mServiceLooper = thread.getLooper();
44 | mServiceHandler = new ServiceHandler(mServiceLooper);
45 | }
46 |
47 | @Override
48 | public void onStart(Intent intent, int startId) {
49 | Message msg = mServiceHandler.obtainMessage();
50 | msg.arg1 = startId;
51 | msg.obj = intent;
52 | mServiceHandler.sendMessage(msg);
53 | }
54 |
55 | @Override
56 | public int onStartCommand(Intent intent, int flags, int startId) {
57 | onStart(intent, startId);
58 | return START_STICKY;
59 | }
60 |
61 | @Override
62 | public void onDestroy() {
63 | mServiceLooper.quit();
64 | }
65 |
66 | @Override
67 | public IBinder onBind(Intent intent) {
68 | return null;
69 | }
70 |
71 | protected abstract void onHandleIntent(Intent intent);
72 |
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/PollingService.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.content.Intent;
4 |
5 | import java.util.concurrent.Executors;
6 | import java.util.concurrent.ScheduledExecutorService;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | /**
10 | * Created by b3d on 12/19/15.
11 | */
12 | public class PollingService extends PermanentService {
13 | // Ensures only one ScheduledExecutor is running at once.
14 |
15 | public static final String START_POLL_DIAGNOSTICS_SERVICE = "com.brianledbetter.kwplogger.START_POLL";
16 | public static final String STOP_POLL_DIAGNOSTICS_SERVICE = "com.brianledbetter.kwplogger.STOP_POLL";
17 | public static final String MEASUREMENT_GROUP = "measurementGroup";
18 |
19 | private int m_measurementGroup = 1;
20 | private ScheduledExecutorService m_pollTemperature = Executors.newSingleThreadScheduledExecutor();
21 |
22 | public PollingService() {
23 | super("com.brianledbetter.kwplogger.PollingService");
24 | }
25 |
26 | @Override
27 | protected void onHandleIntent(Intent intent) {
28 | if (intent == null) return;
29 | if (intent.getAction().equals(START_POLL_DIAGNOSTICS_SERVICE)) {
30 | m_pollTemperature.shutdownNow();
31 | m_pollTemperature = Executors.newSingleThreadScheduledExecutor();
32 | m_measurementGroup = intent.getIntExtra(MEASUREMENT_GROUP, 1);
33 | m_pollTemperature.scheduleAtFixedRate
34 | (new Runnable() {
35 | public void run() {
36 | Intent startIntent = new Intent(getApplicationContext(), DiagnosticsService.class);
37 | startIntent.setAction(DiagnosticsService.POLL_DIAGNOSTICS_SERVICE);
38 | startIntent.putExtra(DiagnosticsService.MEASUREMENT_GROUP, m_measurementGroup);
39 | startService(startIntent);
40 | }
41 | }, 0, 250, TimeUnit.MILLISECONDS);
42 | }
43 | if (intent.getAction().equals(STOP_POLL_DIAGNOSTICS_SERVICE)) {
44 | m_pollTemperature.shutdownNow();
45 | stopSelf();
46 |
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/ResetClusterActivity.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import android.app.DialogFragment;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.os.Parcelable;
9 | import android.support.v7.app.ActionBarActivity;
10 | import android.view.View;
11 | import android.widget.Toast;
12 |
13 | /**
14 | * Created by b3d on 12/23/15.
15 | */
16 | public class ResetClusterActivity extends ActionBarActivity implements BluetoothPickerDialogFragment.BluetoothDialogListener {
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.reset_cluster);
21 | }
22 |
23 | public void startConnection(View v) {
24 | BluetoothAdapter b = BluetoothAdapter.getDefaultAdapter();
25 |
26 | if (!b.isEnabled()) {
27 | Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
28 | startActivityForResult(turnOn, 0);
29 | }
30 |
31 | Parcelable[] devices = b.getBondedDevices().toArray(new BluetoothDevice[0]);
32 | if (devices.length > 0) {
33 | BluetoothPickerDialogFragment bpdf = new BluetoothPickerDialogFragment();
34 | bpdf.mPossibleDevices = devices;
35 | bpdf.show(getFragmentManager(), "BluetoothPickerDialogFragment");
36 | } else {
37 | Toast.makeText(getApplicationContext(), "ERROR! " + "No Bluetooth Device available!", Toast.LENGTH_LONG).show();
38 | }
39 | }
40 |
41 | @Override
42 | public void onDialogPositiveClick(DialogFragment dialog, String selectedDevice) {
43 | Intent startIntent = new Intent(this, DiagnosticsService.class);
44 | startIntent.setAction(DiagnosticsService.RESET_CLUSTER_SERVICE);
45 | startIntent.putExtra(DiagnosticsService.BLUETOOTH_DEVICE, selectedDevice);
46 | startService(startIntent);
47 | }
48 |
49 | @Override
50 | public void onDialogNegativeClick(DialogFragment dialog) {
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/brianledbetter/kwplogger/StateSingleton.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | /**
4 | * Created by b3d on 12/19/15.
5 | */
6 | // Android = facepalm
7 | public class StateSingleton {
8 | private static StateSingleton mInstance = null;
9 | private boolean mIsConnected = false;
10 | private boolean mIsConnecting = false;
11 |
12 | public static synchronized StateSingleton getInstance(){
13 | if(mInstance == null)
14 | {
15 | mInstance = new StateSingleton();
16 | }
17 | return mInstance;
18 | }
19 |
20 | public boolean getIsConnected() {
21 | return this.mIsConnected;
22 | }
23 |
24 | public void setIsConnected(boolean value){
25 | mIsConnected = value;
26 | }
27 |
28 | public boolean getIsConnecting() {
29 | return this.mIsConnecting;
30 | }
31 |
32 | public void setIsConnecting(boolean value){
33 | mIsConnecting = value;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
21 |
22 |
31 |
32 |
39 |
40 |
47 |
48 |
56 |
57 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_measurement.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
22 |
23 |
33 |
34 |
44 |
45 |
55 |
56 |
65 |
66 |
74 |
75 |
83 |
84 |
92 |
93 |
101 |
102 |
110 |
111 |
119 |
120 |
128 |
129 |
137 |
138 |
147 |
148 |
156 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/diagnostic_codes.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/reset_cluster.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_codes.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 120dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Pepper Diagnostics
3 | Transmission Temperature
4 | Advanced Measurements
5 | Trouble Codes
6 | Not Connected
7 | Transmission Temperature
8 | Part Number
9 | Settings
10 | Connect
11 | Disconnect
12 | Group
13 | KWP Init Address
14 | KWP Controller Address
15 | Measurements
16 | Select ELM327
17 | OK
18 | Cancel
19 | Fetch Codes
20 | Fetching Codes...
21 | Trouble Codes
22 | Reset Cluster
23 | Clear DTCs
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/test/java/com/brianledbetter/kwplogger/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.brianledbetter.kwplogger;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.0.0-beta5'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bri3d/kwp-android-logger/163d3859a0f671b25804a078f29f0f8977b5e9e7/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Feb 09 19:00:04 MST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':kwp2000'
2 |
--------------------------------------------------------------------------------