├── .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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | ![Screenshot](https://cloud.githubusercontent.com/assets/210793/11647621/4e214c52-9d27-11e5-8965-d76ae00cba89.png) 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 |