├── .github
└── workflows
│ ├── android.yml
│ └── mavenCentral.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── obd
├── .gitignore
├── build.gradle.kts
├── proguard-rules.txt
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── dtc-codes.json
│ ├── pids-mode1.json
│ ├── pids-mode4.json
│ └── pids-mode9.json
│ ├── java
│ └── com
│ │ └── pnuema
│ │ └── android
│ │ └── obd
│ │ ├── ObdInitializer.kt
│ │ ├── commands
│ │ ├── BaseObdCommand.kt
│ │ └── OBDCommand.kt
│ │ ├── enums
│ │ ├── ObdModes.kt
│ │ └── ObdProtocols.kt
│ │ ├── models
│ │ ├── DTC.kt
│ │ ├── DTCS.kt
│ │ ├── PID.kt
│ │ └── PIDS.kt
│ │ └── statics
│ │ ├── DTCUtils.kt
│ │ ├── FileUtils.kt
│ │ ├── ObdLibrary.kt
│ │ ├── PIDUtils.kt
│ │ ├── PersistentStorage.kt
│ │ └── Translations.kt
│ └── res
│ └── values
│ └── strings.xml
└── settings.gradle.kts
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ main ]
7 | pull_request:
8 | branches: [ main ]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | java: [ '17' ]
16 |
17 | environment: build
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: set up JDK ${{ matrix.java }}
21 | uses: actions/setup-java@v4
22 | with:
23 | distribution: 'zulu'
24 | java-version: ${{ matrix.java }}
25 | check-latest: true
26 | - name: Build with Gradle
27 | run: ./gradlew clean build
28 | env:
29 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVENCENTRALUSERNAME }}
30 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVENCENTRALPASSWORD }}
31 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
32 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
33 | REMOTE_CACHE_URL: ${{ secrets.REMOTE_CACHE_URL }}
34 | REMOTE_CACHE_USER: ${{ secrets.REMOTE_CACHE_USER }}
35 | REMOTE_CACHE_PASS: ${{ secrets.REMOTE_CACHE_PASS }}
36 | - uses: actions/upload-artifact@v4
37 | with:
38 | name: Package
39 | path: obd/build/outputs/aar
40 |
--------------------------------------------------------------------------------
/.github/workflows/mavenCentral.yml:
--------------------------------------------------------------------------------
1 | name: MavenCentral Release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | mavencentral:
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | java: [ '17' ]
12 |
13 | environment: build
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up JDK ${{ matrix.java }}
17 | uses: actions/setup-java@v4
18 | with:
19 | distribution: 'zulu'
20 | java-version: ${{ matrix.java }}
21 | check-latest: true
22 | - name: Build and Publish to the Maven Central Repository
23 | run: ./gradlew publishAndReleaseToMavenCentral --no-daemon --no-parallel
24 | env:
25 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVENCENTRALUSERNAME }}
26 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVENCENTRALPASSWORD }}
27 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
28 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
29 | REMOTE_CACHE_URL: ${{ secrets.REMOTE_CACHE_URL }}
30 | REMOTE_CACHE_USER: ${{ secrets.REMOTE_CACHE_USER }}
31 | REMOTE_CACHE_PASS: ${{ secrets.REMOTE_CACHE_PASS }}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | /local.properties
3 | build/
4 | javadoc/
5 | .DS_Store/
6 | .idea/
7 | *.iml
8 | *.properties
9 | *.jar
10 | *.war
11 | *.aar
12 | *.apk
13 | *.class
14 | *.xml
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Brad Barnhill
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  [](https://android-arsenal.com/api?level=24)
2 |
3 | ## Android OBD Library
4 |
5 |
6 | ### What is this repository for? ###
7 |
8 | This project offers a developer friendly interface to communicate with ELM 327 OBD devices via BLUETOOTH.
9 |
10 | ### Usage ###
11 |
12 | Add Dependency:
13 | ```Gradle
14 | implementation 'com.pnuema.android:obd:1.7.1'
15 | ```
16 |
17 | To get started you will need to first send a few commands over bluetooth or usb whatever the input stream is that you negotiate with the ELM-327 device.
18 |
19 | Connection and init:
20 | ```
21 | val MODE_AT = "AT"
22 |
23 | //set defaults
24 | initPid.mode = MODE_AT
25 | initPid.PID = "D"
26 | var cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
27 | Log.d(TAG, "Set defaults sent (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
28 |
29 | //resets the ELM327
30 | initPid.mode = MODE_AT
31 | initPid.PID = "Z"
32 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
33 | Log.d(TAG, "Reset command sent (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
34 |
35 | //extended responses off
36 | initPid.mode = MODE_AT
37 | initPid.PID = "E0"
38 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
39 | Log.d(TAG, "Extended Responses Off (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
40 |
41 | //line feeds off
42 | initPid.mode = MODE_AT
43 | initPid.PID = "L0"
44 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
45 | Log.d(TAG, "Turn Off Line Feeds (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
46 |
47 | //printing of spaces off
48 | initPid.mode = MODE_AT
49 | initPid.PID = "S0"
50 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
51 | Log.d(TAG, "Printing Spaces Off (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
52 |
53 | //headers off
54 | initPid.mode = MODE_AT
55 | initPid.PID = "H0"
56 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
57 | Log.d(TAG, "Headers Off (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
58 |
59 | //set protocol
60 | initPid.mode = "$MODE_AT SP"
61 | initPid.PID = ObdProtocols.AUTO.value.toString()
62 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
63 | Log.d(TAG, "Select Protocol (" + initPid.mode + " " + initPid.PID + ") Received: " + cmd.rawResult)
64 |
65 | //set timeout for response from the ECU
66 | initPid.mode = "$MODE_AT ST"
67 | initPid.PID = Integer.toHexString(0xFF and ECU_RESPONSE_TIMEOUT)
68 | cmd = OBDCommand(initPid).setIgnoreResult(true).run(inputStream, outputStream)
69 | ```
70 |
71 | Once a connection has been established and inited you can send commands and get responses as follows:
72 |
73 | Code:
74 | ```
75 | //Request MODE 1, PID 0C - RPM
76 | val pid = PIDUtils.getPid(ObdModes.MODE_01, "OC")
77 | val command = OBDCommand(pid)
78 | command.run(bluetoothSocket.inputStream, bluetoothSocket.outputStream)
79 |
80 | Log.d("PID", "${pid.description} : ${pid.calculatedResult}")
81 | Log.d("PID Formatted Result", command.formattedResult)
82 | ```
83 |
84 | ```
85 | //Clear DTCs - NonPermanent
86 | val pid = PID(ObdModes.MODE_04) //Clear DTCs
87 | val command = OBDCommand(pid)
88 | command.run(bluetoothSocket.inputStream, bluetoothSocket.outputStream)
89 | ```
90 |
91 | Note that you do not have to take the raw values and calculate it yourself. The library will run the value through the formula that are specified in the specifications for CAN to get the resulting value. This is available in the `calculatedResult` and `formattedResult` fields on the pid after the `pid.run(...)` command finishes.
92 |
93 | ### Who do I talk to? ###
94 |
95 | * Brad Barnhill (https://github.com/barnhill)
96 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | mavenLocal()
6 | }
7 | }
8 |
9 | plugins {
10 | alias(libs.plugins.androidLibrary).apply(false)
11 | alias(libs.plugins.kotlin.android).apply(false)
12 | alias(libs.plugins.maven.publish).apply(false)
13 | alias(libs.plugins.gradle.cachefix).apply(false)
14 | }
15 |
16 | tasks {
17 | wrapper {
18 | gradleVersion = "8.13"
19 | distributionType = Wrapper.DistributionType.BIN
20 | }
21 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
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
19 |
20 | org.gradle.jvmargs=-Xmx1548M -Dkotlin.daemon.jvm.options\="-Xmx2548M"
21 | org.gradle.configureondemand=false
22 | org.gradle.caching=true
23 | android.useAndroidX=true
24 | android.enableJetifier=false
25 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
26 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
27 |
28 | GROUP=com.pnuema.android
29 | VERSION_NAME=1.7.1
30 |
31 | POM_NAME=Android OBD Library
32 | POM_ARTIFACT_ID=obd
33 | POM_PACKAGING=aar
34 |
35 | POM_DESCRIPTION=Android library to communicate with ELM327 based OBD devices
36 | POM_INCEPTION_YEAR=2016
37 | POM_URL=https://github.com/barnhill/AndroidOBD
38 | POM_SCM_URL=https://github.com/barnhill/AndroidOBD
39 | POM_SCM_CONNECTION=scm:git@github.com/barnhill/AndroidOBD.git
40 | POM_SCM_DEV_CONNECTION=scm:git@github.com/barnhill/AndroidOBD.git
41 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
42 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
43 | POM_LICENCE_DIST=repo
44 | POM_DEVELOPER_ID=barnhill
45 | POM_DEVELOPER_NAME=Brad Barnhill
46 | POM_DEVELOPER_URL=https://github.com/barnhill/
47 |
48 | SONATYPE_HOST=CENTRAL_PORTAL
49 | RELEASE_SIGNING_ENABLED=true
50 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | #libs
3 | kotlinx-serialization = "1.7.1"
4 | vanniktech-maven-publish = "0.29.0"
5 | evalex = "3.4.0"
6 | androidx-startup = "1.2.0"
7 |
8 | #plugins
9 | kotlin= "2.1.20"
10 | gradlePlugins-agp = "8.7.3"
11 | tomlChecker = "0.51.0"
12 | gradleCacheFix = "3.0.1"
13 | dokka = "2.0.0"
14 |
15 | [libraries]
16 | dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
17 | kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
18 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
19 | evalex = { module = "com.ezylang:EvalEx", version.ref = "evalex"}
20 | androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
21 |
22 | [plugins]
23 | androidLibrary = { id = "com.android.library", version.ref = "gradlePlugins-agp" }
24 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
25 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
26 | maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech-maven-publish" }
27 | gradle-cachefix = { id = "org.gradle.android.cache-fix", version.ref = "gradleCacheFix" }
28 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
29 | toml-version-checker = { id = "com.github.ben-manes.versions", version.ref = "tomlChecker" }
30 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/barnhill/AndroidOBD/4827f560d10d576a9732a312d065938815683dc0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | if ! command -v java >/dev/null 2>&1
137 | then
138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
139 |
140 | Please set the JAVA_HOME variable in your environment to match the
141 | location of your Java installation."
142 | fi
143 | fi
144 |
145 | # Increase the maximum file descriptors if we can.
146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
147 | case $MAX_FD in #(
148 | max*)
149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
150 | # shellcheck disable=SC2039,SC3045
151 | MAX_FD=$( ulimit -H -n ) ||
152 | warn "Could not query maximum file descriptor limit"
153 | esac
154 | case $MAX_FD in #(
155 | '' | soft) :;; #(
156 | *)
157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
158 | # shellcheck disable=SC2039,SC3045
159 | ulimit -n "$MAX_FD" ||
160 | warn "Could not set maximum file descriptor limit to $MAX_FD"
161 | esac
162 | fi
163 |
164 | # Collect all arguments for the java command, stacking in reverse order:
165 | # * args from the command line
166 | # * the main class name
167 | # * -classpath
168 | # * -D...appname settings
169 | # * --module-path (only if needed)
170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
171 |
172 | # For Cygwin or MSYS, switch paths to Windows format before running java
173 | if "$cygwin" || "$msys" ; then
174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
176 |
177 | JAVACMD=$( cygpath --unix "$JAVACMD" )
178 |
179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
180 | for arg do
181 | if
182 | case $arg in #(
183 | -*) false ;; # don't mess with options #(
184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
185 | [ -e "$t" ] ;; #(
186 | *) false ;;
187 | esac
188 | then
189 | arg=$( cygpath --path --ignore --mixed "$arg" )
190 | fi
191 | # Roll the args list around exactly as many times as the number of
192 | # args, so each arg winds up back in the position where it started, but
193 | # possibly modified.
194 | #
195 | # NB: a `for` loop captures its iteration list before it begins, so
196 | # changing the positional parameters here affects neither the number of
197 | # iterations, nor the values presented in `arg`.
198 | shift # remove old arg
199 | set -- "$@" "$arg" # push replacement arg
200 | done
201 | fi
202 |
203 |
204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
206 |
207 | # Collect all arguments for the java command:
208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
209 | # and any embedded shellness will be escaped.
210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
211 | # treated as '${Hostname}' itself on the command line.
212 |
213 | set -- \
214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
215 | -classpath "$CLASSPATH" \
216 | org.gradle.wrapper.GradleWrapperMain \
217 | "$@"
218 |
219 | # Stop when "xargs" is not available.
220 | if ! command -v xargs >/dev/null 2>&1
221 | then
222 | die "xargs is not available"
223 | fi
224 |
225 | # Use "xargs" to parse quoted args.
226 | #
227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
228 | #
229 | # In Bash we could simply go:
230 | #
231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
232 | # set -- "${ARGS[@]}" "$@"
233 | #
234 | # but POSIX shell has neither arrays nor command substitution, so instead we
235 | # post-process each arg (as a line of input to sed) to backslash-escape any
236 | # character that might be a shell metacharacter, then use eval to reverse
237 | # that process (while maintaining the separation between arguments), and wrap
238 | # the whole thing up as a single "set" statement.
239 | #
240 | # This will of course break if any of these variables contains a newline or
241 | # an unmatched quote.
242 | #
243 |
244 | eval "set -- $(
245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
246 | xargs -n1 |
247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
248 | tr '\n' ' '
249 | )" '"$@"'
250 |
251 | exec "$JAVACMD" "$@"
252 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/obd/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/obd/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.maven.publish)
5 | alias(libs.plugins.kotlin.serialization)
6 | alias(libs.plugins.dokka)
7 | alias(libs.plugins.toml.version.checker)
8 | }
9 |
10 | version = project.properties["VERSION_NAME"] as String
11 | group = project.properties["GROUP"] as String
12 |
13 | android {
14 | base.archivesName.set("obd")
15 | namespace = "com.pnuema.android.obd"
16 | compileSdk = 35
17 | defaultConfig {
18 | minSdk = 24
19 | }
20 |
21 | buildTypes {
22 | named("release") {
23 | isMinifyEnabled = false
24 | isShrinkResources = false
25 | setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_17
30 | targetCompatibility = JavaVersion.VERSION_17
31 | }
32 |
33 | kotlinOptions {
34 | jvmTarget = JavaVersion.VERSION_17.toString()
35 | }
36 |
37 | kotlin {
38 | jvmToolchain(JavaVersion.VERSION_17.toString().toInt())
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation(libs.evalex)
44 | implementation(libs.kotlinx.serialization.json)
45 | implementation(libs.androidx.startup)
46 | }
47 |
48 | val dokkaOutputDir = layout.buildDirectory.dir("dokka")
49 | tasks {
50 | val sourcesJar by registering(Jar::class, fun Jar.() {
51 | archiveClassifier.set("sources")
52 | from(android.sourceSets.getByName("main").java.srcDirs)
53 | })
54 |
55 | val javadocJar by registering(Jar::class, fun Jar.() {
56 | dependsOn.add(dokkaGenerate)
57 | archiveClassifier.set("javadoc")
58 | from(android.sourceSets.getByName("main").java.srcDirs)
59 | from(dokkaOutputDir)
60 | })
61 |
62 | artifacts {
63 | archives(sourcesJar)
64 | archives(javadocJar)
65 | }
66 |
67 | dokka {
68 | moduleName.set(project.properties["POM_NAME"] as String)
69 | dokkaPublications.html {
70 | suppressInheritedMembers.set(true)
71 | failOnWarning.set(true)
72 | outputDirectory.set(dokkaOutputDir)
73 | }
74 | dokkaSourceSets.main {
75 | sourceLink {
76 | localDirectory.set(file("src/main/java"))
77 | remoteUrl(project.properties["POM_URL"] as String)
78 | }
79 | }
80 | pluginsConfiguration.html {
81 | footerMessage.set("(c) " + project.properties["POM_DEVELOPER_NAME"])
82 | }
83 | }
84 |
85 | build {
86 | dependsOn(dokkaGenerate)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/obd/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:/Users/bbarnhill/AppData/Local/Android/android-studio/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
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 |
19 | # ----- KotlinX Serialization -----
20 | -keepattributes *Annotation*, InnerClasses
21 | -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
22 |
23 | # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
24 | -keepclassmembers class kotlinx.serialization.json.** {
25 | *** Companion;
26 | }
27 | -keepclasseswithmembers class kotlinx.serialization.json.** {
28 | kotlinx.serialization.KSerializer serializer(...);
29 | }
30 |
31 | -keep,includedescriptorclasses class com.pnuema.android.obd.**$$serializer { *; }
32 | -keepclassmembers class com.pnuema.android.obd.** {
33 | *** Companion;
34 | }
35 | -keepclasseswithmembers class com.pnuema.android.obd.** {
36 | kotlinx.serialization.KSerializer serializer(...);
37 | }
38 | # ----- KotlinX Serialization -----
--------------------------------------------------------------------------------
/obd/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/obd/src/main/assets/pids-mode1.json:
--------------------------------------------------------------------------------
1 | {
2 | "pids": [
3 | {
4 | "Mode": "01",
5 | "PID": "00",
6 | "Bytes": "4",
7 | "Description": "PIDs supported [01 - 20]",
8 | "Min": "0",
9 | "Max": "9999999999999",
10 | "Units": "",
11 | "Formula": "A",
12 | "isPersistent": true
13 | },
14 | {
15 | "Mode": "01",
16 | "PID": "20",
17 | "Bytes": "4",
18 | "Description": "PIDs supported [21 - 40]",
19 | "Min": "0",
20 | "Max": "9999999999999",
21 | "Units": "",
22 | "Formula": "A",
23 | "isPersistent": true
24 | },
25 | {
26 | "Mode": "01",
27 | "PID": "40",
28 | "Bytes": "4",
29 | "Description": "PIDs supported [41 - 60]",
30 | "Min": "0",
31 | "Max": "9999999999999",
32 | "Units": "",
33 | "Formula": "A",
34 | "isPersistent": true
35 | },
36 | {
37 | "Mode": "01",
38 | "PID": "60",
39 | "Bytes": "4",
40 | "Description": "PIDs supported [61 - 80]",
41 | "Min": "0",
42 | "Max": "9999999999999",
43 | "Units": "",
44 | "Formula": "A",
45 | "isPersistent": true
46 | },
47 | {
48 | "Mode": "01",
49 | "PID": "80",
50 | "Bytes": "4",
51 | "Description": "PIDs supported [81 - A0]",
52 | "Min": "0",
53 | "Max": "9999999999999",
54 | "Units": "",
55 | "Formula": "A",
56 | "isPersistent": true
57 | },
58 | {
59 | "Mode": "01",
60 | "PID": "01",
61 | "Bytes": "4",
62 | "Description": "MIL On",
63 | "Min": "0",
64 | "Max": "1",
65 | "Units": "",
66 | "Formula": "(A/128)>=1"
67 | },
68 | {
69 | "Mode": "01",
70 | "PID": "04",
71 | "Bytes": "1",
72 | "Description": "Calculated engine load value",
73 | "Min": "0",
74 | "Max": "100",
75 | "Units": "%",
76 | "Formula": "A*100/255"
77 | },
78 | {
79 | "Mode": "01",
80 | "PID": "05",
81 | "Bytes": "1",
82 | "Description": "Engine coolant temperature",
83 | "Min": "-40",
84 | "Max": "215",
85 | "Units": "°C",
86 | "Formula": "A-40",
87 | "ImperialFormula": "((A-40) * 1.8) + 32",
88 | "ImperialUnits": "°F"
89 | },
90 | {
91 | "Mode": "01",
92 | "PID": "0A",
93 | "Bytes": "1",
94 | "Description": "Fuel pressure",
95 | "Min": "0",
96 | "Max": "765",
97 | "Units": "kPa (gauge)",
98 | "Formula": "A*3",
99 | "ImperialFormula": "(A*3)*0.145037738",
100 | "ImperialUnits": "psi (gauge)"
101 | },
102 | {
103 | "Mode": "01",
104 | "PID": "0B",
105 | "Bytes": "1",
106 | "Description": "Intake manifold absolute pressure",
107 | "Min": "0",
108 | "Max": "255",
109 | "Units": "kPa (absolute)",
110 | "Formula": "A",
111 | "ImperialFormula": "A*0.145037738",
112 | "ImperialUnits": "psi (absolute)"
113 | },
114 | {
115 | "Mode": "01",
116 | "PID": "0C",
117 | "Bytes": "2",
118 | "Description": "Engine RPM",
119 | "Min": "0",
120 | "Max": "16383.75",
121 | "Units": "rpm",
122 | "Formula": "((A*256)+B)/4"
123 | },
124 | {
125 | "Mode": "01",
126 | "PID": "0D",
127 | "Bytes": "1",
128 | "Description": "Vehicle speed",
129 | "Min": "0",
130 | "Max": "255",
131 | "Units": "km/h",
132 | "Formula": "A",
133 | "ImperialFormula": "A*0.62137",
134 | "ImperialUnits": "mph"
135 | },
136 | {
137 | "Mode": "01",
138 | "PID": "0E",
139 | "Bytes": "1",
140 | "Description": "Timing advance",
141 | "Min": "-64",
142 | "Max": "63.5",
143 | "Units": "° relative to #1 cylinder",
144 | "Formula": "A/2 - 64"
145 | },
146 | {
147 | "Mode": "01",
148 | "PID": "0F",
149 | "Bytes": "1",
150 | "Description": "Intake air temperature",
151 | "Min": "-40",
152 | "Max": "215",
153 | "Units": "°C",
154 | "Formula": "A-40",
155 | "ImperialFormula": "((A-40) * 1.8) + 32",
156 | "ImperialUnits": "°F"
157 | },
158 | {
159 | "Mode": "01",
160 | "PID": "10",
161 | "Bytes": "2",
162 | "Description": "MAF air flow rate",
163 | "Min": "0",
164 | "Max": "655.35",
165 | "Units": "grams/sec",
166 | "Formula": "((A*256)+B) / 100"
167 | },
168 | {
169 | "Mode": "01",
170 | "PID": "11",
171 | "Bytes": "1",
172 | "Description": "Throttle position",
173 | "Min": "0",
174 | "Max": "100",
175 | "Units": "%",
176 | "Formula": "A*100/255"
177 | },
178 | {
179 | "Mode": "01",
180 | "PID": "1F",
181 | "Bytes": "2",
182 | "Description": "Run time since engine start",
183 | "Min": "0",
184 | "Max": "65.535",
185 | "Units": "seconds",
186 | "Formula": "(A*256)+B"
187 | },
188 | {
189 | "Mode": "01",
190 | "PID": "21",
191 | "Bytes": "2",
192 | "Description": "Distance traveled with malfunction indicator lamp (MIL) on",
193 | "Min": "0",
194 | "Max": "65.535",
195 | "Units": "km",
196 | "Formula": "(A*256)+B",
197 | "ImperialFormula": "((A*256)+B)*0.62137",
198 | "ImperialUnits": "miles"
199 | },
200 | {
201 | "Mode": "01",
202 | "PID": "22",
203 | "Bytes": "2",
204 | "Description": "Fuel Rail Pressure (relative to manifold vacuum)",
205 | "Min": "0",
206 | "Max": "5177.265",
207 | "Units": "kPa",
208 | "Formula": "((A*256)+B) * 0.079",
209 | "ImperialFormula": "(((A*256)+B) * 0.079)*0.145037738",
210 | "ImperialUnits": "psi"
211 | },
212 | {
213 | "Mode": "01",
214 | "PID": "23",
215 | "Bytes": "2",
216 | "Description": "Fuel Rail Pressure (diesel. or gasoline direct inject)",
217 | "Min": "0",
218 | "Max": "655.350",
219 | "Units": "kPa (gauge)",
220 | "Formula": "((A*256)+B) * 10",
221 | "ImperialFormula": "(((A*256)+B) * 10)*0.145037738",
222 | "ImperialUnits": "psi"
223 | },
224 | {
225 | "Mode": "01",
226 | "PID": "2C",
227 | "Bytes": "1",
228 | "Description": "Commanded EGR",
229 | "Min": "0",
230 | "Max": "100",
231 | "Units": "%",
232 | "Formula": "100*A/255"
233 | },
234 | {
235 | "Mode": "01",
236 | "PID": "2D",
237 | "Bytes": "1",
238 | "Description": "EGR Error",
239 | "Min": "-100",
240 | "Max": "99.22",
241 | "Units": "%",
242 | "Formula": "(A-128) * 100/128"
243 | },
244 | {
245 | "Mode": "01",
246 | "PID": "2E",
247 | "Bytes": "1",
248 | "Description": "Commanded evaporative purge",
249 | "Min": "0",
250 | "Max": "100",
251 | "Units": "%",
252 | "Formula": "100*A/255"
253 | },
254 | {
255 | "Mode": "01",
256 | "PID": "2F",
257 | "Bytes": "1",
258 | "Description": "Fuel Level Input",
259 | "Min": "0",
260 | "Max": "100",
261 | "Units": "%",
262 | "Formula": "100*A/255"
263 | },
264 | {
265 | "Mode": "01",
266 | "PID": "30",
267 | "Bytes": "1",
268 | "Description": "# of warm-ups since codes cleared",
269 | "Min": "0",
270 | "Max": "255",
271 | "Formula": "A"
272 | },
273 | {
274 | "Mode": "01",
275 | "PID": "31",
276 | "Bytes": "2",
277 | "Description": "Distance traveled since codes cleared",
278 | "Min": "0",
279 | "Max": "65.535",
280 | "Units": "km",
281 | "Formula": "(A*256)+B",
282 | "ImperialFormula": "((A*256)+B)*0.62137",
283 | "ImperialUnits": "miles"
284 | },
285 | {
286 | "Mode": "01",
287 | "PID": "32",
288 | "Bytes": "2",
289 | "Description": "Evap. System Vapor Pressure",
290 | "Min": "-8.192",
291 | "Max": "8.192",
292 | "Units": "Pa",
293 | "Formula": "((A*256)+B)/4"
294 | },
295 | {
296 | "Mode": "01",
297 | "PID": "33",
298 | "Bytes": "1",
299 | "Description": "Barometric pressure",
300 | "Min": "0",
301 | "Max": "255",
302 | "Units": "kPa (Absolute)",
303 | "Formula": "A",
304 | "ImperialFormula": "A*0.145037738",
305 | "ImperialUnits": "psi (Absolute)"
306 | },
307 | {
308 | "Mode": "01",
309 | "PID": "34",
310 | "Bytes": "4",
311 | "Description": "O2S1_WR_lambda(1):",
312 | "Min": "0",
313 | "Max": "1.999",
314 | "Formula": "((A*256)+B)/32.768"
315 | },
316 | {
317 | "Mode": "01",
318 | "PID": "35",
319 | "Bytes": "4",
320 | "Description": "O2S2_WR_lambda",
321 | "Min": "0",
322 | "Max": "2",
323 | "Formula": "((A*256)+B)/32.768"
324 | },
325 | {
326 | "Mode": "01",
327 | "PID": "36",
328 | "Bytes": "4",
329 | "Description": "O2S3_WR_lambda",
330 | "Min": "0",
331 | "Max": "2",
332 | "Formula": "((A*256)+B)/32768"
333 | },
334 | {
335 | "Mode": "01",
336 | "PID": "37",
337 | "Bytes": "4",
338 | "Description": "O2S4_WR_lambda",
339 | "Min": "0",
340 | "Max": "2",
341 | "Formula": "((A*256)+B)/32.768"
342 | },
343 | {
344 | "Mode": "01",
345 | "PID": "38",
346 | "Bytes": "4",
347 | "Description": "O2S5_WR_lambda",
348 | "Min": "0",
349 | "Max": "2",
350 | "Formula": "((A*256)+B)/32.768"
351 | },
352 | {
353 | "Mode": "01",
354 | "PID": "39",
355 | "Bytes": "4",
356 | "Description": "O2S6_WR_lambda",
357 | "Min": "0",
358 | "Max": "2",
359 | "Formula": "((A*256)+B)/32.768"
360 | },
361 | {
362 | "Mode": "01",
363 | "PID": "3A",
364 | "Bytes": "4",
365 | "Description": "O2S7_WR_lambda",
366 | "Min": "0",
367 | "Max": "2",
368 | "Formula": "((A*256)+B)/32.768"
369 | },
370 | {
371 | "Mode": "01",
372 | "PID": "3B",
373 | "Bytes": "4",
374 | "Description": "O2S8_WR_lambda",
375 | "Min": "0",
376 | "Max": "2",
377 | "Formula": "((A*256)+B)/32.768"
378 | },
379 | {
380 | "Mode": "01",
381 | "PID": "3C",
382 | "Bytes": "2",
383 | "Description": "Catalyst Temperature Bank 1 Sensor 1",
384 | "Min": "-40",
385 | "Max": "6.513.50",
386 | "Units": "°C",
387 | "Formula": "((A*256)+B)/10 - 40",
388 | "ImperialFormula": "((((A*256)+B)/10 - 40) * 1.8) + 32",
389 | "ImperialUnits": "°F"
390 | },
391 | {
392 | "Mode": "01",
393 | "PID": "3D",
394 | "Bytes": "2",
395 | "Description": "Catalyst Temperature Bank 2 Sensor 1",
396 | "Min": "-40",
397 | "Max": "6.513.50",
398 | "Units": "°C",
399 | "Formula": "((A*256)+B)/10 - 40",
400 | "ImperialFormula": "((((A*256)+B)/10 - 40) * 1.8) + 32",
401 | "ImperialUnits": "°F"
402 | },
403 | {
404 | "Mode": "01",
405 | "PID": "3E",
406 | "Bytes": "2",
407 | "Description": "Catalyst Temperature Bank 1 Sensor 2",
408 | "Min": "-40",
409 | "Max": "6.513.50",
410 | "Units": "°C",
411 | "Formula": "((A*256)+B)/10 - 40",
412 | "ImperialFormula": "((((A*256)+B)/10 - 40) * 1.8) + 32",
413 | "ImperialUnits": "°F"
414 | },
415 | {
416 | "Mode": "01",
417 | "PID": "3F",
418 | "Bytes": "2",
419 | "Description": "Catalyst Temperature Bank 2 Sensor 2",
420 | "Min": "-40",
421 | "Max": "6.513.50",
422 | "Units": "°C",
423 | "Formula": "((A*256)+B)/10 - 40",
424 | "ImperialFormula": "((((A*256)+B)/10 - 40) * 1.8) + 32",
425 | "ImperialUnits": "°F"
426 | },
427 | {
428 | "Mode": "01",
429 | "PID": "42",
430 | "Bytes": "2",
431 | "Description": "Control module voltage",
432 | "Min": "0",
433 | "Max": "65.535",
434 | "Units": "V",
435 | "Formula": "((A*256)+B)/1000"
436 | },
437 | {
438 | "Mode": "01",
439 | "PID": "43",
440 | "Bytes": "2",
441 | "Description": "Absolute load value",
442 | "Min": "0",
443 | "Max": "25.700",
444 | "Units": "°%",
445 | "Formula": "((A*256)+B)*100/255"
446 | },
447 | {
448 | "Mode": "01",
449 | "PID": "44",
450 | "Bytes": "2",
451 | "Description": "Command equivalence ratio",
452 | "Min": "0",
453 | "Max": "2",
454 | "Formula": "((A*256)+B)/32768"
455 | },
456 | {
457 | "Mode": "01",
458 | "PID": "45",
459 | "Bytes": "1",
460 | "Description": "Relative throttle position",
461 | "Min": "0",
462 | "Max": "100",
463 | "Units": "%",
464 | "Formula": "A*100/255"
465 | },
466 | {
467 | "Mode": "01",
468 | "PID": "46",
469 | "Bytes": "1",
470 | "Description": "Ambient air temperature",
471 | "Min": "-40",
472 | "Max": "215",
473 | "Units": "°C",
474 | "Formula": "A-40",
475 | "ImperialFormula": "((A-40) * 1.8) + 32",
476 | "ImperialUnits": "°F"
477 | },
478 | {
479 | "Mode": "01",
480 | "PID": "47",
481 | "Bytes": "1",
482 | "Description": "Absolute throttle position B",
483 | "Min": "0",
484 | "Max": "100",
485 | "Units": "%",
486 | "Formula": "A*100/255"
487 | },
488 | {
489 | "Mode": "01",
490 | "PID": "48",
491 | "Bytes": "1",
492 | "Description": "Absolute throttle position C",
493 | "Min": "0",
494 | "Max": "100",
495 | "Units": "%",
496 | "Formula": "A*100/255"
497 | },
498 | {
499 | "Mode": "01",
500 | "PID": "49",
501 | "Bytes": "1",
502 | "Description": "Accelerator pedal position D",
503 | "Min": "0",
504 | "Max": "100",
505 | "Units": "%",
506 | "Formula": "A*100/255"
507 | },
508 | {
509 | "Mode": "01",
510 | "PID": "4A",
511 | "Bytes": "1",
512 | "Description": "Accelerator pedal position E",
513 | "Min": "0",
514 | "Max": "100",
515 | "Units": "%",
516 | "Formula": "A*100/255"
517 | },
518 | {
519 | "Mode": "01",
520 | "PID": "4B",
521 | "Bytes": "1",
522 | "Description": "Accelerator pedal position F",
523 | "Min": "0",
524 | "Max": "100",
525 | "Units": "%",
526 | "Formula": "A*100/255"
527 | },
528 | {
529 | "Mode": "01",
530 | "PID": "4C",
531 | "Bytes": "1",
532 | "Description": "Commanded throttle actuator",
533 | "Min": "0",
534 | "Max": "100",
535 | "Units": "%",
536 | "Formula": "A*100/255"
537 | },
538 | {
539 | "Mode": "01",
540 | "PID": "4D",
541 | "Bytes": "2",
542 | "Description": "Time run with MIL on",
543 | "Min": "0",
544 | "Max": "65.535",
545 | "Units": "minutes",
546 | "Formula": "(A*256)+B"
547 | },
548 | {
549 | "Mode": "01",
550 | "PID": "4E",
551 | "Bytes": "2",
552 | "Description": "Time since trouble codes cleared",
553 | "Min": "0",
554 | "Max": "65.535",
555 | "Units": "minutes",
556 | "Formula": "(A*256)+B"
557 | },
558 | {
559 | "Mode": "01",
560 | "PID": "51",
561 | "Bytes": "1",
562 | "Description": "Fuel type",
563 | "Min": "",
564 | "Max": "",
565 | "Units": "",
566 | "Formula": ""
567 | },
568 | {
569 | "Mode": "01",
570 | "PID": "52",
571 | "Bytes": "1",
572 | "Description": "Ethanol fuel %",
573 | "Min": "0",
574 | "Max": "100",
575 | "Units": "%",
576 | "Formula": "A*100/255"
577 | },
578 | {
579 | "Mode": "01",
580 | "PID": "53",
581 | "Bytes": "2",
582 | "Description": "Absolute Evap system Vapour Pressure",
583 | "Min": "0",
584 | "Max": "327.675",
585 | "Units": "kPa",
586 | "Formula": "((A*256)+B)/200",
587 | "ImperialFormula": "(((A*256)+B)/200)*0.145037738",
588 | "ImperialUnits": "psi"
589 | },
590 | {
591 | "Mode": "01",
592 | "PID": "54",
593 | "Bytes": "2",
594 | "Description": "Evap system vapor pressure",
595 | "Min": "-32.767",
596 | "Max": "32.768",
597 | "Units": "Pa",
598 | "Formula": "A*256+B - 32768"
599 | },
600 | {
601 | "Mode": "01",
602 | "PID": "59",
603 | "Bytes": "2",
604 | "Description": "Fuel rail pressure (absolute)",
605 | "Min": "0",
606 | "Max": "655.350",
607 | "Units": "kPa",
608 | "Formula": "((A*256)+B) * 10",
609 | "ImperialFormula": "(((A*256)+B) * 10)*0.145037738",
610 | "ImperialUnits": "psi"
611 | },
612 | {
613 | "Mode": "01",
614 | "PID": "5A",
615 | "Bytes": "1",
616 | "Description": "Relative accelerator pedal position",
617 | "Min": "0",
618 | "Max": "100",
619 | "Units": "%",
620 | "Formula": "A*100/255"
621 | },
622 | {
623 | "Mode": "01",
624 | "PID": "5B",
625 | "Bytes": "1",
626 | "Description": "Hybrid battery pack remaining life",
627 | "Min": "0",
628 | "Max": "100",
629 | "Units": "%",
630 | "Formula": "A*100/255"
631 | },
632 | {
633 | "Mode": "01",
634 | "PID": "5C",
635 | "Bytes": "1",
636 | "Description": "Engine oil temperature",
637 | "Min": "-40",
638 | "Max": "210",
639 | "Units": "°C",
640 | "Formula": "A-40",
641 | "ImperialFormula": "((A-40) * 1.8) + 32",
642 | "ImperialUnits": "°F"
643 | },
644 | {
645 | "Mode": "01",
646 | "PID": "5D",
647 | "Bytes": "2",
648 | "Description": "Fuel injection timing",
649 | "Min": "-210",
650 | "Max": "301.992",
651 | "Formula": "(((A*256)+B)-26.880)/128"
652 | },
653 | {
654 | "Mode": "01",
655 | "PID": "5E",
656 | "Bytes": "2",
657 | "Description": "Engine fuel rate",
658 | "Min": "0",
659 | "Max": "3212.75",
660 | "Units": "L/h",
661 | "Formula": "((A*256)+B)*0.05"
662 | },
663 | {
664 | "Mode": "01",
665 | "PID": "61",
666 | "Bytes": "1",
667 | "Description": "Driver's demand engine - percent torque",
668 | "Min": "-125",
669 | "Max": "125",
670 | "Units": "%",
671 | "Formula": "A-125"
672 | },
673 | {
674 | "Mode": "01",
675 | "PID": "62",
676 | "Bytes": "1",
677 | "Description": "Actual engine - percent torque",
678 | "Min": "-125",
679 | "Max": "125",
680 | "Units": "%",
681 | "Formula": "A-125"
682 | },
683 | {
684 | "Mode": "01",
685 | "PID": "63",
686 | "Bytes": "2",
687 | "Description": "Engine reference torque",
688 | "Min": "0",
689 | "Max": "65.535",
690 | "Units": "Nm",
691 | "Formula": "A*256+B"
692 | },
693 | {
694 | "Mode": "01",
695 | "PID": "65",
696 | "Bytes": "2",
697 | "Description": "Auxiliary input-output supported",
698 | "Formula": "Bit Encoded"
699 | },
700 | {
701 | "Mode": "01",
702 | "PID": "66",
703 | "Bytes": "5",
704 | "Description": "Mass air flow sensor"
705 | },
706 | {
707 | "Mode": "01",
708 | "PID": "67",
709 | "Bytes": "3",
710 | "Description": "Engine coolant temperature"
711 | },
712 | {
713 | "Mode": "01",
714 | "PID": "68",
715 | "Bytes": "7",
716 | "Description": "Intake air temperature sensor"
717 | },
718 | {
719 | "Mode": "01",
720 | "PID": "69",
721 | "Bytes": "7",
722 | "Description": "Commanded EGR and EGR Error"
723 | },
724 | {
725 | "Mode": "01",
726 | "PID": "6B",
727 | "Bytes": "5",
728 | "Description": "Exhaust gas recirculation temperature"
729 | },
730 | {
731 | "Mode": "01",
732 | "PID": "6D",
733 | "Bytes": "6",
734 | "Description": "Fuel pressure control system"
735 | },
736 | {
737 | "Mode": "01",
738 | "PID": "6E",
739 | "Bytes": "5",
740 | "Description": "Injection pressure control system"
741 | },
742 | {
743 | "Mode": "01",
744 | "PID": "6F",
745 | "Bytes": "3",
746 | "Description": "Turbocharger compressor inlet pressure"
747 | },
748 | {
749 | "Mode": "01",
750 | "PID": "70",
751 | "Bytes": "9",
752 | "Description": "Boost pressure control"
753 | },
754 | {
755 | "Mode": "01",
756 | "PID": "71",
757 | "Bytes": "5",
758 | "Description": "Variable Geometry turbo (VGT) control"
759 | },
760 | {
761 | "Mode": "01",
762 | "PID": "72",
763 | "Bytes": "5",
764 | "Description": "Wastegate control"
765 | },
766 | {
767 | "Mode": "01",
768 | "PID": "73",
769 | "Bytes": "5",
770 | "Description": "Exhaust pressure"
771 | },
772 | {
773 | "Mode": "01",
774 | "PID": "74",
775 | "Bytes": "5",
776 | "Description": "Turbocharger RPM"
777 | },
778 | {
779 | "Mode": "01",
780 | "PID": "75",
781 | "Bytes": "7",
782 | "Description": "Turbocharger temperature"
783 | },
784 | {
785 | "Mode": "01",
786 | "PID": "76",
787 | "Bytes": "7",
788 | "Description": "Turbocharger temperature"
789 | },
790 | {
791 | "Mode": "01",
792 | "PID": "77",
793 | "Bytes": "5",
794 | "Description": "Charge air cooler temperature (CACT)"
795 | },
796 | {
797 | "Mode": "01",
798 | "PID": "7A",
799 | "Bytes": "7",
800 | "Description": "Diesel particulate filter (DPF)"
801 | },
802 | {
803 | "Mode": "01",
804 | "PID": "7B",
805 | "Bytes": "7",
806 | "Description": "Diesel particulate filter (DPF)"
807 | },
808 | {
809 | "Mode": "01",
810 | "PID": "7C",
811 | "Bytes": "9",
812 | "Description": "Diesel Particulate filter (DPF) temperature"
813 | },
814 | {
815 | "Mode": "01",
816 | "PID": "7D",
817 | "Bytes": "1",
818 | "Description": "NOx NTE control area status"
819 | },
820 | {
821 | "Mode": "01",
822 | "PID": "7E",
823 | "Bytes": "1",
824 | "Description": "PM NTE control area status"
825 | },
826 | {
827 | "Mode": "01",
828 | "PID": "7F",
829 | "Bytes": "13",
830 | "Description": "Engine run time"
831 | },
832 | {
833 | "Mode": "01",
834 | "PID": "81",
835 | "Bytes": "21",
836 | "Description": "Engine run time for AECD"
837 | },
838 | {
839 | "Mode": "01",
840 | "PID": "82",
841 | "Bytes": "21",
842 | "Description": "Engine run time for AECD"
843 | },
844 | {
845 | "Mode": "01",
846 | "PID": "83",
847 | "Bytes": "5",
848 | "Description": "NOx sensor"
849 | },
850 | {
851 | "Mode": "01",
852 | "PID": "84",
853 | "Description": "Manifold surface temperature"
854 | },
855 | {
856 | "Mode": "01",
857 | "PID": "85",
858 | "Description": "NOx reagent system"
859 | },
860 | {
861 | "Mode": "01",
862 | "PID": "86",
863 | "Description": "Particulate matter (PM) sensor"
864 | },
865 | {
866 | "Mode": "01",
867 | "PID": "87",
868 | "Description": "Intake manifold absolute pressure"
869 | }
870 | ]
871 | }
--------------------------------------------------------------------------------
/obd/src/main/assets/pids-mode4.json:
--------------------------------------------------------------------------------
1 | {
2 | "pids": [
3 | {
4 | "Mode": "04",
5 | "PID": "0",
6 | "Bytes": "0",
7 | "Description": "Clear DTCs"
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/obd/src/main/assets/pids-mode9.json:
--------------------------------------------------------------------------------
1 | {
2 | "pids": [
3 | {
4 | "Mode": "09",
5 | "PID": "00",
6 | "Bytes": "4",
7 | "Description": "Mode 9 supported PIDs"
8 | },
9 | {
10 | "Mode": "09",
11 | "PID": "02",
12 | "Bytes": "20",
13 | "Description": "Vehicle Identification Number (VIN)"
14 | },
15 | {
16 | "Mode": "09",
17 | "PID": "04",
18 | "Bytes": "16",
19 | "Description": "Calibration ID"
20 | },
21 | {
22 | "Mode": "09",
23 | "PID": "0A",
24 | "Bytes": "20",
25 | "Description": "ECU Name"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/ObdInitializer.kt:
--------------------------------------------------------------------------------
1 | package com.pnuema.android.obd
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.startup.Initializer
6 | import com.pnuema.android.obd.statics.ObdLibrary
7 |
8 | @Suppress("unused") //inited in the manifest
9 | class ObdInitializer: Initializer {
10 | override fun create(context: Context): Context {
11 | ObdLibrary.init(context as Application)
12 | return context
13 | }
14 |
15 | override fun dependencies(): MutableList>> = mutableListOf()
16 | }
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/commands/BaseObdCommand.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | @file:Suppress("unused")
14 |
15 | package com.pnuema.android.obd.commands
16 |
17 | import com.pnuema.android.obd.models.PID
18 | import com.pnuema.android.obd.statics.PersistentStorage
19 | import java.io.BufferedReader
20 | import java.io.IOException
21 | import java.io.InputStream
22 | import java.io.InputStreamReader
23 | import java.io.OutputStream
24 | import kotlin.system.measureTimeMillis
25 |
26 | /**
27 | * Base OBD command class for communicating with an ELM327 device.
28 | */
29 | abstract class BaseObdCommand {
30 | /**
31 | * @return a list of integers
32 | */
33 | protected var buffer: ArrayList = ArrayList()
34 | private var cmd: String? = null
35 | private var useImperialUnits = false
36 | private var rawData: String? = null
37 | internal var mIgnoreResult: Boolean = false
38 |
39 | companion object {
40 | const val NODATA = "NODATA"
41 | const val SEARCHING = "SEARCHING"
42 | const val DATA = "DATA"
43 | const val ELM327 = "ELM327"
44 |
45 | internal lateinit var mPid: PID
46 |
47 | private fun readPersistent() {
48 | if (mPid.isPersistent && PersistentStorage.containsPid(mPid)) {
49 | mPid.calculatedResult = PersistentStorage.getElement(mPid)?.calculatedResult ?: 0f
50 | mPid.calculatedResultString = PersistentStorage.getElement(mPid)?.calculatedResultString
51 | mPid.data = PersistentStorage.getElement(mPid)?.data ?: ArrayList()
52 | }
53 | }
54 |
55 | private fun storePersistent() {
56 | if (mPid.isPersistent && !PersistentStorage.containsPid(mPid)) {
57 | PersistentStorage.addElement(mPid)
58 | }
59 | }
60 | }
61 |
62 | /**
63 | * @return [String] representation of the formatted command response.
64 | */
65 | abstract val formattedResult: String
66 |
67 | /**
68 | * @return the raw command response in string representation.
69 | */
70 | val rawResult: String
71 | get() {
72 | rawData = if (rawData == null || rawData!!.contains(SEARCHING) || rawData!!.contains(DATA) || rawData!!.contains(ELM327))
73 | NODATA
74 | else
75 | rawData
76 |
77 | return rawData!!
78 | }
79 |
80 | /**
81 | * @return the OBD command name.
82 | */
83 | abstract val name: String
84 |
85 | @Suppress("RemoveEmptySecondaryConstructorBody")
86 | private constructor() {}
87 |
88 | /**
89 | * Default ctor to use
90 | *
91 | * @param command the command to send
92 | */
93 | private constructor(command: String?) {
94 | this.cmd = command
95 | }
96 |
97 | internal constructor(command: String, pid: PID) : this(command.trim { it <= ' ' }) {
98 | mPid = pid
99 | }
100 |
101 | /**
102 | * Copy constructor.
103 | *
104 | * @param other the ObdCommand to copy.
105 | */
106 | constructor(other: BaseObdCommand) : this(other.cmd)
107 |
108 | /**
109 | * This method exists so that for each command, there must be a method that is
110 | * called only once to perform calculations.
111 | */
112 | protected abstract fun performCalculations()
113 |
114 | /**
115 | * Sends the OBD-II request and deals with the response.
116 | *
117 | * This method CAN be overridden in fake commands.
118 | * @param inputStream [InputStream] to read the result of the requested PID.
119 | * @param out [OutputStream] on which to send the request.
120 | * @throws IOException thrown if IO is unable to be performed
121 | * @throws InterruptedException thrown if the process is interrupted
122 | * @return Current instance of [BaseObdCommand]
123 | */
124 | @Throws(IOException::class, InterruptedException::class)
125 | fun run(inputStream: InputStream, out: OutputStream): BaseObdCommand {
126 | synchronized(BaseObdCommand::class.java) {
127 | mPid.retrievalTime = measureTimeMillis {
128 | if (mPid.isPersistent && PersistentStorage.containsPid(mPid)) {
129 | readPersistent()
130 | } else {
131 | sendCommand(out)
132 | readResult(inputStream)
133 | }
134 | }
135 | }
136 | return this
137 | }
138 |
139 | /**
140 | * Sends the OBD-II request.
141 | *
142 | * This method may be overridden in subclasses
143 | *
144 | * @param out The output stream.
145 | */
146 | @Throws(IOException::class, InterruptedException::class)
147 | private fun sendCommand(out: OutputStream) {
148 | // write to OutputStream (i.e.: a BluetoothSocket) with an added carriage return
149 | out.write((cmd!! + "\r").toByteArray())
150 | out.flush()
151 | }
152 |
153 | /**
154 | * Reads the OBD-II response.
155 | *
156 | *
157 | * This method may be overridden in subclasses, such as ObdMultiCommand.
158 | */
159 | @Throws(IOException::class)
160 | private fun readResult(inputStream: InputStream) {
161 | readRawData(inputStream)
162 |
163 | if (!mIgnoreResult) {
164 | fillBuffer()
165 | performCalculations()
166 | storePersistent()
167 | }
168 | }
169 |
170 | /**
171 | * Fills the buffer from the raw data.
172 | */
173 | private fun fillBuffer() {
174 | // read string each two chars
175 | buffer.clear()
176 | rawData?.chunked(2)?.forEach {
177 | try {
178 | buffer.add(Integer.decode("0x$it"))
179 | } catch (e: NumberFormatException) { return }
180 | }
181 | }
182 |
183 | @Throws(IOException::class)
184 | private fun readRawData(inputStream: InputStream) {
185 | val reader = BufferedReader(InputStreamReader(inputStream))
186 |
187 | val res = StringBuilder()
188 |
189 | var c: Char
190 | var b = reader.read()
191 | while (b > -1) { // -1 if the end of the stream is reached
192 | c = b.toChar()
193 |
194 | if (c == '>') { // read until '>' arrives
195 | break
196 | }
197 | res.append(c)
198 | b = reader.read()
199 | }
200 |
201 | /*
202 | * Imagine the following response 41 0c 00 0d.
203 | *
204 | * ELM sends strings!! So, ELM puts spaces between each "byte". Pay
205 | * attention to the fact that I've put the word byte in quotes, because 41
206 | * is actually TWO bytes (two chars) in the socket. So, we must do some more
207 | * processing..
208 | */
209 | rawData = res.toString().trim { it <= ' ' }
210 |
211 | /*
212 | * data may have echo or informative text like "INIT BUS..." or similar.
213 | * The response ends with two carriage return characters. So we need to take
214 | * everything from the last carriage return before those two (trimmed above).
215 | */
216 | rawData = rawData?.let {
217 | it.substring(it.indexOf(13.toChar()) + 1)
218 | }
219 | }
220 |
221 | /**
222 | * Gets whether imperial or metric units should be shown
223 | * @return true if imperial units are used, or false otherwise
224 | */
225 | fun useImperialUnits(): Boolean = useImperialUnits
226 |
227 | /**
228 | * Sets whether to use imperial units or metric.
229 | *
230 | * @param isImperial Set to 'true' if you want to use imperial units, false otherwise. By
231 | * default this value is set to 'false'.
232 | */
233 | fun setImperialUnits(isImperial: Boolean) {
234 | this.useImperialUnits = isImperial
235 | }
236 | }
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/commands/OBDCommand.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | @file:Suppress("unused")
14 |
15 | package com.pnuema.android.obd.commands
16 |
17 | import android.util.Log
18 | import com.ezylang.evalex.Expression
19 | import com.ezylang.evalex.config.ExpressionConfiguration
20 | import com.pnuema.android.obd.models.PID
21 | import com.pnuema.android.obd.statics.Translations
22 |
23 |
24 | /**
25 | * Generic class to form an OBD command for communication, also includes the parsing of the result.
26 | *
27 | * @author Brad Barnhill
28 | */
29 | class OBDCommand(pid: PID) : BaseObdCommand(pid.mode.trim { it <= ' ' } + if (pid.PID.trim { it <= ' ' }.isEmpty()) "" else " " + pid.PID.trim { it <= ' ' }, pid) {
30 | private var mMetricUnits = true
31 |
32 | override val formattedResult: String
33 | get() = mPid.calculatedResult.toString() + " " + if (mMetricUnits || mPid.imperialFormula == null) mPid.units else mPid.imperialUnits
34 |
35 | override val name: String
36 | get() = mPid.description
37 |
38 | val callDuration: Long
39 | get() = mPid.retrievalTime
40 |
41 | companion object {
42 | const val A = "A"
43 | const val B = "B"
44 | const val C = "C"
45 | const val D = "D"
46 |
47 | val expressionConfig: ExpressionConfiguration by lazy {
48 | ExpressionConfiguration.builder()
49 | .decimalPlacesRounding(5)
50 | .stripTrailingZeros(true)
51 | .build()
52 | }
53 | }
54 |
55 | init {
56 | pid.retrievalTime = 0
57 | }
58 |
59 | /**
60 | * Set if the result of the request will be ignored
61 | * @param ignoreResult [Boolean] which is whether the result should be ignored when its returned.
62 | * @return Current [OBDCommand]
63 | */
64 | fun setIgnoreResult(ignoreResult: Boolean): OBDCommand {
65 | mIgnoreResult = ignoreResult
66 | return this
67 | }
68 |
69 | /**
70 | * Set to metric or imperial units
71 | * @param metric True if to return metric units, false for imperial units.
72 | * @return Current [OBDCommand]
73 | */
74 | fun setUnitType(metric: Boolean): OBDCommand {
75 | mMetricUnits = metric
76 | return this
77 | }
78 |
79 | override fun performCalculations() {
80 | if (NODATA != rawResult) {
81 | val exprText = if (mMetricUnits || mPid.imperialFormula == null) mPid.formula else mPid.imperialFormula
82 |
83 | val numBytes: Byte = try {
84 | java.lang.Byte.parseByte(mPid.bytes)
85 | } catch (nfex: NumberFormatException) {
86 | 0
87 | }
88 |
89 | mPid.data.clear()
90 | mPid.data.addAll(buffer)
91 |
92 | if (Translations.handleSpecialPidEnumerations(mPid, mPid.data)) {
93 | return
94 | }
95 |
96 | if (exprText == null || !exprText.contains(A) && !exprText.contains(B) && !exprText.contains(C) && !exprText.contains(D) || numBytes > 4 || numBytes <= 0 || mPid.data.size <= 2) {
97 | mPid.calculatedResultString = mPid.data.toString()
98 | return
99 | }
100 |
101 | //TODO: first two bytes show what command the data is for, verify this is the command returning that is expected
102 |
103 | val expression = Expression(exprText, expressionConfig)
104 |
105 | if (mPid.data.size > 2)
106 | expression.with(A, mPid.data[2])
107 |
108 | if (mPid.data.size > 3)
109 | expression.with(B, mPid.data[3])
110 |
111 | if (mPid.data.size > 4)
112 | expression.with(C, mPid.data[4])
113 |
114 | if (mPid.data.size > 5)
115 | expression.with(D, mPid.data[5])
116 |
117 | try {
118 | mPid.calculatedResult = expression.evaluate().numberValue.toFloat()
119 | mPid.calculatedResultString = mPid.calculatedResult.toString()
120 | } catch (nfex: NumberFormatException) {
121 | Log.e(OBDCommand::class.java.simpleName, "[Expression:" + expression + "] [mode:" + mPid.mode + "] [Pid:" + mPid.PID + "] [formula:" + mPid.formula
122 | + "] [bytes:" + mPid.bytes + "] [BytesReturned:" + mPid.data.size + "]", nfex)
123 | } catch (genEx: Exception) {
124 | Log.e(OBDCommand::class.java.simpleName, "[Expression:" + expression + "] [mode:" + mPid.mode + "] [Pid:" + mPid.PID + "] [formula:" + mPid.formula + "] [bytes:" + mPid.bytes + "] [BytesReturned:" + mPid.data.size + "]", genEx)
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/enums/ObdModes.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | package com.pnuema.android.obd.enums
14 |
15 | import java.lang.Long.parseLong
16 |
17 | /**
18 | * All OBD modes.
19 | *
20 | * @author Brad Barnhill
21 | */
22 | @Suppress("unused")
23 | enum class ObdModes constructor(val value: Char) {
24 | /**
25 | * This mode returns the common values for some sensors
26 | */
27 | MODE_01('1'),
28 |
29 | /**
30 | * This mode gives the freeze frame (or instantaneous) data of a fault
31 | */
32 | MODE_02('2'),
33 |
34 | /**
35 | * This mode shows the stored diagnostic trouble codes
36 | */
37 | MODE_03('3'),
38 |
39 | /**
40 | * This mode is used to clear recorded fault codes
41 | */
42 | MODE_04('4'),
43 |
44 | /**
45 | * This mode gives the results of self-diagnostics done on the oxygen/lamda sensors
46 | */
47 | MODE_05('5'),
48 |
49 | /**
50 | * This mode gives the results of self-diagnostics done on systems not subject to constant surveillance
51 | */
52 | MODE_06('6'),
53 |
54 | /**
55 | * This mode gives unconfirmed fault codes
56 | */
57 | MODE_07('7'),
58 |
59 | /**
60 | * This mode gives the results of self-diagnostics on other systems (Hardly used in Europe)
61 | */
62 | MODE_08('8'),
63 |
64 | /**
65 | * This mode gives the information concerning the vehicle
66 | */
67 | MODE_09('9'),
68 |
69 | /**
70 | * This mode shows the permanent diagnostic trouble codes
71 | */
72 | MODE_0A('A');
73 |
74 | val intValue: Int get() = parseLong(value.toString(), 16).toInt()
75 |
76 | override fun toString(): String = value.toString()
77 | }
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/enums/ObdProtocols.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | package com.pnuema.android.obd.enums
14 |
15 | /**
16 | * All OBD protocols.
17 | *
18 | * @author Brad Barnhill
19 | */
20 | @Suppress("unused")
21 | enum class ObdProtocols constructor(val value: Char) {
22 |
23 | /**
24 | * Auto select protocol and save.
25 | */
26 | AUTO('0'),
27 |
28 | /**
29 | * 41.6 kbaud
30 | */
31 | SAE_J1850_PWM('1'),
32 |
33 | /**
34 | * 10.4 kbaud
35 | */
36 | SAE_J1850_VPW('2'),
37 |
38 | /**
39 | * 5 baud init
40 | */
41 | ISO_9141_2('3'),
42 |
43 | /**
44 | * 5 baud init
45 | */
46 | ISO_14230_4_KWP('4'),
47 |
48 | /**
49 | * Fast init
50 | */
51 | ISO_14230_4_KWP_FAST('5'),
52 |
53 | /**
54 | * 11 bit ID, 500 kbaud
55 | */
56 | ISO_15765_4_CAN('6'),
57 |
58 | /**
59 | * 29 bit ID, 500 kbaud
60 | */
61 | ISO_15765_4_CAN_B('7'),
62 |
63 | /**
64 | * 11 bit ID, 250 kbaud
65 | */
66 | ISO_15765_4_CAN_C('8'),
67 |
68 | /**
69 | * 29 bit ID, 250 kbaud
70 | */
71 | ISO_15765_4_CAN_D('9'),
72 |
73 | /**
74 | * 29 bit ID, 250 kbaud (user adjustable)
75 | */
76 | SAE_J1939_CAN('A'),
77 |
78 | /**
79 | * 11 bit ID (user adjustable), 125 kbaud (user adjustable)
80 | */
81 | USER1_CAN('B'),
82 |
83 | /**
84 | * 11 bit ID (user adjustable), 50 kbaud (user adjustable)
85 | */
86 | USER2_CAN('C')
87 | }
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/models/DTC.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | @file:Suppress("unused")
14 |
15 | package com.pnuema.android.obd.models
16 |
17 | import com.pnuema.android.obd.enums.ObdModes
18 | import kotlinx.serialization.Serializable
19 |
20 | /**
21 | * Holder for a single DTC's data
22 | *
23 | * @author Brad Barnhill
24 | */
25 | @Serializable
26 | data class DTC (
27 | var mode: String = "01",
28 | var code: String? = null,
29 | var description: String? = null
30 | ) : java.io.Serializable {
31 | /**
32 | * Sets the mode.
33 | *
34 | * @param mode mode to set.
35 | * @return DTC object with the mode set. (returns object for method chaining support)
36 | */
37 | fun setMode(mode: ObdModes): DTC {
38 | this.mode = "0${mode.value}"
39 |
40 | return this
41 | }
42 |
43 | val modeString get() = mode.trimStart('0')
44 | }
45 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/models/DTCS.kt:
--------------------------------------------------------------------------------
1 | package com.pnuema.android.obd.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * Holder for all DTCs
8 | *
9 | * @author Brad Barnhill
10 | */
11 | @Serializable
12 | data class DTCS (
13 | @SerialName("dtcs")
14 | val dtcs: List = ArrayList()
15 | )
16 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/models/PID.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | package com.pnuema.android.obd.models
14 |
15 | import com.pnuema.android.obd.enums.ObdModes
16 | import kotlinx.serialization.SerialName
17 | import kotlinx.serialization.Serializable
18 |
19 | /**
20 | * Holder for a single pids data
21 | *
22 | * @author Brad Barnhill
23 | */
24 | @Suppress("MemberVisibilityCanBePrivate")
25 | @Serializable
26 | data class PID (
27 | @SerialName("Mode")
28 | var mode: String = "01",
29 | @SerialName("PID")
30 | var PID: String = "01",
31 | @SerialName("Bytes")
32 | var bytes: String = "",
33 | @SerialName("Description")
34 | var description: String = "",
35 | @SerialName("Min")
36 | var min: String? = null,
37 | @SerialName("Max")
38 | var max: String? = null,
39 | @SerialName("Units")
40 | var units: String? = null,
41 | @SerialName("Formula")
42 | var formula: String? = null,
43 | @SerialName("ImperialFormula")
44 | var imperialFormula: String? = null,
45 | @SerialName("ImperialUnits")
46 | var imperialUnits: String? = null
47 | ): java.io.Serializable {
48 | var data: ArrayList = ArrayList()
49 | var calculatedResultString: String? = null
50 | var calculatedResult: Float = 0.toFloat()
51 | var retrievalTime: Long = 0
52 | var isPersistent: Boolean = false
53 |
54 | constructor(mode: ObdModes, pid: String = ""): this() {
55 | setModeAndPID(mode, pid)
56 | }
57 |
58 | /**
59 | * Sets the mode of the PID
60 | *
61 | * @param mode mode to set.
62 | * @return DTC object with the mode set. (returns object for method chaining support)
63 | */
64 | fun setMode(mode: ObdModes): PID {
65 | this.mode = "0" + mode.value
66 |
67 | return this
68 | }
69 |
70 | /**
71 | * Sets the PID.
72 | *
73 | * @param pid Pid to set (ex. 0C).
74 | * @param mode Mode to set (ex. 01).
75 | * @return PID object with the Mode and Pid set. (returns object for method chaining support)
76 | */
77 | fun setModeAndPID(mode: ObdModes, pid: String): PID {
78 | setMode(mode)
79 | this.PID = pid
80 | return this
81 | }
82 |
83 | /**
84 | * Get the string description of the PID.
85 | *
86 | * @return [String] description of the PID
87 | */
88 | override fun toString(): String {
89 | return description
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/models/PIDS.kt:
--------------------------------------------------------------------------------
1 | package com.pnuema.android.obd.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * Holder for all PIDS
8 | *
9 | * @author Brad Barnhill
10 | */
11 | @Serializable
12 | data class PIDS (
13 | @SerialName("pids")
14 | val pids: List = ArrayList()
15 | )
16 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/DTCUtils.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | package com.pnuema.android.obd.statics
14 |
15 | import com.pnuema.android.obd.models.DTC
16 | import com.pnuema.android.obd.models.DTCS
17 | import kotlinx.serialization.decodeFromString
18 | import kotlinx.serialization.json.Json
19 | import java.io.IOException
20 |
21 | /**
22 | * Class to hold all the static methods necessary for the OBD library
23 | * that pertain to DTCs
24 | *
25 | * @author Brad Barnhill
26 | */
27 | @Suppress("unused")
28 | object DTCUtils {
29 | val dtcList: List
30 | @Throws(IOException::class)
31 | get() = Json.decodeFromString(FileUtils.readFromFile("dtc-codes.json")).dtcs
32 | }
33 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pnuema.android.obd.statics
2 |
3 | import java.io.BufferedReader
4 | import java.io.IOException
5 | import java.io.InputStream
6 | import java.io.InputStreamReader
7 |
8 | /**
9 | * Static definitions for all file related utilities.
10 | *
11 | * @author Brad Barnhill
12 | */
13 | object FileUtils {
14 | /**
15 | * Read entire file into a string. (used to read json files from assets)
16 | *
17 | * @param fileName name of file to read
18 | * @return Entire file contents in a string
19 | * @throws IOException thrown if IO can not be performed
20 | */
21 | @Throws(IOException::class)
22 | fun readFromFile(fileName: String): String {
23 | val returnString = StringBuilder()
24 |
25 | val fIn = ObdLibrary.getResourceFileInputStream(fileName)
26 | ?: error("Could not read resource files. ObdLibrary not initialized.")
27 |
28 | InputStreamReader(fIn).use { isr ->
29 | BufferedReader(isr).use { input ->
30 | var line = input.readLine()
31 | while (line != null) {
32 | returnString.append(line)
33 | line = input.readLine()
34 | }
35 | }
36 | }
37 |
38 | return returnString.toString()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/ObdLibrary.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Pnuema Productions LLC ("COMPANY") CONFIDENTIAL
3 | * Unpublished Copyright (c) 2009-2015 Pnuema Productions, All Rights Reserved.
4 | *
5 | * NOTICE: All information contained herein is, and remains the property of COMPANY. The intellectual and technical concepts contained herein are proprietary to COMPANY and may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from COMPANY. Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, managers or contractors who have executed Confidentiality and Non-disclosure agreements explicitly covering such access.
6 | *
7 | * The copyright notice above does not evidence any actual or intended publication or disclosure of this source code, which includes information that is confidential and/or proprietary, and is a trade secret, of COMPANY. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
8 | * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE
9 | * LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS
10 | * TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
11 | */
12 |
13 | package com.pnuema.android.obd.statics
14 |
15 | import android.app.Application
16 | import java.io.InputStream
17 |
18 | /**
19 | * This class handles getting the applications context.
20 | *
21 | * @author Brad Barnhill
22 | */
23 | internal object ObdLibrary {
24 | fun init(applicationContext: Application) {
25 | this.applicationContext = applicationContext
26 | }
27 |
28 | private lateinit var applicationContext: Application
29 |
30 | fun getResourceString(stringRes: Int): String = applicationContext.getString(stringRes)
31 |
32 | fun getResourceStringArray(stringArrayRes: Int): Array = applicationContext.resources.getStringArray(stringArrayRes)
33 |
34 | fun getResourceFileInputStream(fileName: String): InputStream? = runCatching { applicationContext.assets.open(fileName) }.getOrNull()
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/PIDUtils.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
3 | * use this file except in compliance with the License. You may obtain a copy of
4 | * the License at
5 | * http://www.apache.org/licenses/LICENSE-2.0
6 | *
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 | * License for the specific language governing permissions and limitations under
11 | * the License.
12 | */
13 | package com.pnuema.android.obd.statics
14 |
15 | import android.util.Log
16 | import android.util.SparseArray
17 | import com.pnuema.android.obd.enums.ObdModes
18 | import com.pnuema.android.obd.models.PID
19 | import com.pnuema.android.obd.models.PIDS
20 | import kotlinx.serialization.json.Json
21 | import java.io.IOException
22 | import java.util.*
23 |
24 | /**
25 | * Class to hold all the static methods necessary for the OBD library.
26 | * that pertain to DTCs
27 | *
28 | * @author Brad Barnhill
29 | */
30 | @Suppress("unused")
31 | object PIDUtils {
32 | private val TAG = PIDUtils::class.java.simpleName
33 | private val pidsSparseArray = SparseArray>()
34 |
35 | /**
36 | * Gets list of pids for the mode specified
37 | *
38 | * @param mode mode to look up the list of pids
39 | * @return List of [PIDS] contained in the specified mode
40 | * @throws IOException thrown if IO can not be performed
41 | */
42 | @Throws(IOException::class)
43 | fun getPidList(mode: ObdModes): List = ArrayList(getPidMap(mode)!!.values)
44 |
45 | /**
46 | * Gets PID object by mode and pid.
47 | *
48 | * @param mode mode to look the pid up in
49 | * @param pid Pid number to retrieve
50 | * @return [PID] object
51 | * @throws IOException thrown if IO can not be performed
52 | */
53 | @Throws(IOException::class,java.lang.IllegalArgumentException::class)
54 | fun getPid(mode: ObdModes, pid: String): PID? {
55 | getPidMap(mode)?.let { pids ->
56 | return pids[Integer.parseInt(pid, 16)]
57 | } ?: run {
58 | Log.d(TAG, "Pids for this mode do not exist.")
59 | return null
60 | }
61 | }
62 |
63 | @Throws(IOException::class,java.lang.IllegalArgumentException::class)
64 | private fun getPidMap(mode: ObdModes): SortedMap? {
65 | if (pidsSparseArray.size() > 0 && pidsSparseArray.indexOfKey(mode.intValue) >= 0) {
66 | //get value from pid cache
67 | return pidsSparseArray.get(mode.intValue)
68 | } else {
69 | //not found in cache so read it from json files and store it in cache
70 | val pidList = Json.decodeFromString(FileUtils.readFromFile("pids-mode" + mode.intValue + ".json")).pids
71 | val pidMap = TreeMap()
72 |
73 | pidList.forEach { pid ->
74 | try {
75 | pidMap[Integer.parseInt(pid.PID, 16)] = pid
76 | } catch (nfex: NumberFormatException) {
77 | Log.d(TAG, "Parsing PID number to integer failed: " + nfex.message)
78 | }
79 | }
80 |
81 | require(!pidMap.isEmpty()) { "Unsupported mode requested: $mode" }
82 |
83 | pidsSparseArray.put(mode.intValue, pidMap)
84 | return pidsSparseArray.get(mode.intValue)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/PersistentStorage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Pnuema Productions LLC ("COMPANY") CONFIDENTIAL
3 | * Unpublished Copyright (c) 2009-2015 Pnuema Productions, All Rights Reserved.
4 | *
5 | * NOTICE: All information contained herein is, and remains the property of COMPANY. The intellectual and technical concepts contained herein are proprietary to COMPANY and may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from COMPANY. Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, managers or contractors who have executed Confidentiality and Non-disclosure agreements explicitly covering such access.
6 | *
7 | * The copyright notice above does not evidence any actual or intended publication or disclosure of this source code, which includes information that is confidential and/or proprietary, and is a trade secret, of COMPANY. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE,
8 | * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE
9 | * LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS
10 | * TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
11 | */
12 |
13 | package com.pnuema.android.obd.statics
14 |
15 | import com.pnuema.android.obd.models.PID
16 |
17 | /**
18 | * Storage for the persistent pids so they dont have to be retrieved more than once.
19 | *
20 | * @author Brad Barnhill
21 | */
22 | @Suppress("unused")
23 | object PersistentStorage {
24 | private val persistentPidStorage by lazy { HashMap() }
25 |
26 | fun addElement(element: PID?) {
27 | element?.let { pid ->
28 | persistentPidStorage[formKey(pid)] = pid
29 | }
30 | }
31 |
32 | fun removeElement(element: PID?) {
33 | element?.let { pid ->
34 | persistentPidStorage.remove(formKey(pid))
35 | }
36 | }
37 |
38 | fun getElement(element: PID?): PID? {
39 | return element?.let { pid ->
40 | persistentPidStorage[formKey(pid)]
41 | }
42 | }
43 |
44 | fun containsPid(element: PID): Boolean {
45 | return element.isPersistent && persistentPidStorage.containsKey(formKey(element))
46 | }
47 |
48 | fun clearAll() {
49 | persistentPidStorage.clear()
50 | }
51 |
52 | private fun formKey(pid: PID): String {
53 | return pid.mode + pid.PID
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/obd/src/main/java/com/pnuema/android/obd/statics/Translations.kt:
--------------------------------------------------------------------------------
1 | package com.pnuema.android.obd.statics
2 |
3 | import com.pnuema.android.obd.R
4 | import com.pnuema.android.obd.models.PID
5 | import java.util.*
6 |
7 | /**
8 | * This class provides translations for pids that are based on enumeration or other translation
9 | * methods other than just conversion using a simple formula.
10 | *
11 | * @author Brad Barnhill
12 | */
13 | @Suppress("unused")
14 | object Translations {
15 | private const val bitsInOneCharValue = 4
16 | private const val HEX_RADIX = 16
17 |
18 | fun handleSpecialPidEnumerations(pid: PID, buffer: ArrayList): Boolean {
19 | if (pid.mode == "01" && pid.PID == "00") {
20 | mode1Pid00Translation(pid, buffer)
21 | return true
22 | } else if (pid.mode == "01" && pid.PID == "01") {
23 | //dtc count
24 | mode1Pid01Translation(pid, buffer)
25 | return true
26 | } else if (pid.mode == "01" && pid.PID == "51") {
27 | //fuel type
28 | mode1Pid51Translation(pid, buffer)
29 | return true
30 | }
31 |
32 | return false
33 | }
34 |
35 | /**
36 | * Not finished yet
37 | */
38 | private fun mode1Pid00Translation(pid: PID, buffer: ArrayList) {
39 | if (buffer.size != 6) {
40 | return
41 | }
42 |
43 | val hexString = StringBuilder()
44 | val subList = buffer.subList(2, buffer.size)
45 | var temp: String
46 | subList.forEach { item ->
47 | temp = Integer.toHexString(item)
48 | hexString.append(if (temp.length == 1) "0" else "").append(temp)
49 | }
50 |
51 | pid.calculatedResult = java.lang.Long.parseLong(hexString.toString(), HEX_RADIX).toFloat()
52 | pid.calculatedResultString = hexString.toString()
53 | }
54 |
55 | private fun mode1Pid01Translation(pid: PID, buffer: ArrayList) {
56 | if (buffer.isNotEmpty() && buffer.size > 2) {
57 | // ignore first two bytes [hh hh] of the response
58 | val mil = buffer[2]
59 | val on = ObdLibrary.getResourceString(R.string.mode1_pid01_translation_on)
60 | val off = ObdLibrary.getResourceString(R.string.mode1_pid01_translation_off)
61 | val onOff = if (mil and 0x80 == 128) on else off
62 | pid.calculatedResultString = String.format(ObdLibrary.getResourceString(R.string.mode1_pid01_translation), onOff, mil and 0x7F)
63 | }
64 | }
65 |
66 | private fun mode1Pid51Translation(pid: PID, buffer: ArrayList) {
67 | val pidTranslation = ObdLibrary.getResourceStringArray(R.array.mode1_pid51_translation)
68 | if (buffer.isNotEmpty() && buffer.size > 2 && buffer[2] < pidTranslation.size) {
69 | pid.calculatedResultString = pidTranslation[buffer[2]]
70 | } else {
71 | pid.calculatedResultString = ObdLibrary.getResourceString(R.string.pid_value_unavailable)
72 | }
73 | }
74 |
75 | /**
76 | * Convert the given hexadecimal number to bits
77 | *
78 | * @param hex The [String] representation of the hexadecimal
79 | * @return [BitSet] containing the bits of the hexadecimal
80 | */
81 | private fun hexToBitSet(hex: String): BitSet {
82 | val hexBitSize = hex.length * bitsInOneCharValue
83 | val hexBitSet = BitSet(hexBitSize)
84 |
85 | hex.forEachIndexed { index, hexChar ->
86 | val charBitSet = hexCharToBitSet(hexChar)
87 |
88 | for (j in 0 until bitsInOneCharValue) {
89 | if (charBitSet.get(j)) {
90 | hexBitSet.set(j + (hex.length - index - 1) * bitsInOneCharValue)
91 | }
92 | }
93 | }
94 |
95 | return hexBitSet
96 | }
97 |
98 | /**
99 | * Convert the given hexadecimal character to its bits. Note: valid inputs
100 | * are 0-F
101 | *
102 | * @param hexChar the hexadecimal character to convert
103 | * @return BitSet containing the bits of the hexadecimal character
104 | */
105 | private fun hexCharToBitSet(hexChar: Char): BitSet {
106 | val charBitSet = BitSet(bitsInOneCharValue)
107 | val hex = Integer.parseInt(hexChar.toString(), HEX_RADIX)
108 |
109 | for (i in 0 until bitsInOneCharValue) {
110 | val bit = Integer.lowestOneBit(hex shr i)
111 | if (bit == 1)
112 | charBitSet.set(i)
113 | }
114 |
115 | return charBitSet
116 | }
117 |
118 | /**
119 | * Gets if a pid is available or not based on the value of a bitset
120 | *
121 | * @param pid PID to check if available to query
122 | * @param pidValue parent pid value (hex) which denotes which pids are available
123 | * @return True if PID is available, false otherwise.
124 | */
125 | fun isPidAvailable(pid: PID, pidValue: String): Boolean {
126 | //each parent pid governs 32 pids
127 | val pidIndex = Integer.parseInt(pid.PID, HEX_RADIX) % 32 - 1
128 |
129 | return hexToBitSet(pidValue).get(pidIndex)
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/obd/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | On
3 | Off
4 | %1$s %2$s
5 |
6 | Error
7 |
8 |
9 | - Not available
10 | - Gasoline
11 | - Methanol
12 | - Ethanol
13 | - Diesel
14 | - LPG
15 | - CNG
16 | - Propane
17 | - Electric
18 | - Bifuel running Gasoline
19 | - Bifuel running Methanol
20 | - Bifuel running Ethanol
21 | - Bifuel running LPG
22 | - Bifuel running CNG
23 | - Bifuel running Propane
24 | - Bifuel running Electricity
25 | - Bifuel running electric and combustion engine
26 | - Hybrid gasoline
27 | - Hybrid Ethanol
28 | - Hybrid Diesel
29 | - Hybrid Electric
30 | - Hybrid running electric and combustion engine
31 | - Hybrid Regenerative
32 | - Bifuel running diesel
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | pluginManagement {
4 | repositories {
5 | gradlePluginPortal()
6 | google()
7 | mavenCentral()
8 | }
9 | }
10 |
11 | plugins {
12 | id("com.gradle.develocity") version "3.19.2"
13 | }
14 |
15 | dependencyResolutionManagement {
16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
17 | repositories {
18 | gradlePluginPortal()
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | develocity {
25 | buildScan {
26 | termsOfUseUrl = "https://gradle.com/terms-of-service"
27 | termsOfUseAgree = "yes"
28 | }
29 | }
30 |
31 | val remoteCacheUrl: String? by extra
32 | val cacheUrl: String? = if (System.getenv("REMOTE_CACHE_URL") == null) remoteCacheUrl as String else System.getenv("REMOTE_CACHE_URL")
33 |
34 | if (cacheUrl != null) {
35 | buildCache {
36 | remote {
37 | url = uri(cacheUrl)
38 | isEnabled = true
39 | isPush = true
40 | isAllowUntrustedServer = true
41 | isAllowInsecureProtocol = false
42 | if (isEnabled) {
43 | println("Using remote build cache: $cacheUrl")
44 | }
45 |
46 | val remoteCacheUser: String? by extra
47 | val remoteCachePass: String? by extra
48 | credentials {
49 | username = if (System.getenv("REMOTE_CACHE_USER") == null) remoteCacheUser as String else System.getenv("REMOTE_CACHE_USER")
50 | password = if (System.getenv("REMOTE_CACHE_PASS") == null) remoteCachePass as String else System.getenv("REMOTE_CACHE_PASS")
51 | }
52 | }
53 | }
54 | } else {
55 | println("Not using remote build cache!")
56 | }
57 |
58 | include("obd")
--------------------------------------------------------------------------------