Skip to content

Commit

Permalink
Adds an AndrOBD plugin to provide CDS data
Browse files Browse the repository at this point in the history
  • Loading branch information
hufman committed Sep 7, 2021
1 parent 5edd5e5 commit 5655a4c
Show file tree
Hide file tree
Showing 34 changed files with 714 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "androbd_gestalt/AndrOBD-libplugin"]
path = androbd_gestalt/AndrOBD-libplugin
url = https://github.com/fr3ts0n/AndrOBD-libplugin.git
1 change: 1 addition & 0 deletions androbd_gestalt/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
1 change: 1 addition & 0 deletions androbd_gestalt/AndrOBD-libplugin
Submodule AndrOBD-libplugin added at 82746a
52 changes: 52 additions & 0 deletions androbd_gestalt/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}

android {
compileSdkVersion 30
buildToolsVersion "30.0.0"

defaultConfig {
applicationId "me.hufman.idriveconnectaddons.androbd_gestalt"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
dataBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation "com.google.code.gson:gson:2.8.6"
implementation project(path: ':lib')
implementation project(path: ':androbd_gestalt:AndrOBD-libplugin')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
21 changes: 21 additions & 0 deletions androbd_gestalt/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("me.hufman.idriveconnectaddons.androbd_gestalt", appContext.packageName)
}
}
50 changes: 50 additions & 0 deletions androbd_gestalt/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.hufman.idriveconnectaddons.androbd_gestalt">

<uses-permission android:name="bimmergestalt.permission.CDS_normal" />
<uses-permission android:name="bimmergestalt.permission.CDS_personal" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.IDriveConnectAddons">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>

<!-- AndrOBD plugin receiver -->
<!-- For the previous BroadcastReceiver connection -->
<receiver android:name=".AndrobdPluginReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.fr3ts0n.androbd.plugin.IDENTIFY" />
<category android:name="com.fr3ts0n.androbd.plugin.REQUEST" />
</intent-filter>
</receiver>

<!-- AndrOBD plugin service -->
<service android:name=".AndrobdPlugin"
android:exported="true"
android:enabled="true">
<!-- AndrOBD plugin service binding -->
<intent-filter>
<action android:name="com.fr3ts0n.androbd.plugin.IDENTIFY" />
<category android:name="com.fr3ts0n.androbd.plugin.REQUEST" />
</intent-filter>
</service>

<!-- Become discoverable in the Addons tab -->
<service android:name=".GestaltPlugin">
<intent-filter>
<action android:name="bimmergestalt.cardata.service" />
</intent-filter>
</service>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import android.content.*
import android.os.IBinder
import android.util.Log
import androidx.lifecycle.Observer
import com.fr3ts0n.androbd.plugin.Plugin
import com.fr3ts0n.androbd.plugin.PluginInfo
import me.hufman.idriveconnectaddons.lib.CDSLiveData
import me.hufman.idriveconnectaddons.lib.CDSProperty
import com.google.gson.JsonObject
import me.hufman.idriveconnectaddons.lib.GsonNullable.tryAsDouble
import me.hufman.idriveconnectaddons.lib.GsonNullable.tryAsInt
import me.hufman.idriveconnectaddons.lib.GsonNullable.tryAsJsonObject
import me.hufman.idriveconnectaddons.lib.GsonNullable.tryAsJsonPrimitive

class AndrobdPlugin: Plugin(), Plugin.DataProvider, Plugin.ConfigurationHandler {
companion object {
const val TAG = "AndrobdBimmerGestalt"
}

private val pluginInfo = PluginInfo("AndrOBD Bimmer Gestalt",
AndrobdPlugin::class.java, "AndrOBD Bimmer Gestalt bridge",
"Copyright (C) 2021 Bimmer Gestalt", "MIT", "https://github.com/BimmerGestalt/IDriveConnectAddons")

val receiver = AndrobdPluginReceiver()

val bindListener = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i(TAG, "Connected to AndrOBD")
}

override fun onServiceDisconnected(name: ComponentName?) {
Log.i(TAG, "Disconnected from AndrOBD")
}

}

val cdsListeners = mapOf(
CDSProperty.DRIVING_ACCELERATORPEDAL to Observer<JsonObject> {
val value = it?.tryAsJsonObject("acceleratorPedal")?.tryAsJsonPrimitive("position")?.tryAsInt
sendDataUpdate("driving.acceleratorPedal", value?.toString())
},
CDSProperty.DRIVING_BRAKECONTACT to Observer<JsonObject> {
val value = it?.tryAsJsonPrimitive("brakeContact")?.tryAsInt
sendDataUpdate("driving.brakeContact", value?.toString())
},
CDSProperty.DRIVING_CLUTCHPEDAL to Observer<JsonObject> {
val value = it?.tryAsJsonObject("clutchPedal")?.tryAsJsonPrimitive("position")?.tryAsInt
sendDataUpdate("driving.clutchPedal", value?.toString())
},
CDSProperty.DRIVING_STEERINGWHEEL to Observer<JsonObject> {
val value = it?.tryAsJsonObject("steeringWheel")?.tryAsJsonPrimitive("angle")?.tryAsDouble
sendDataUpdate("driving.steeringWheel", value?.toString())
},
CDSProperty.DRIVING_ACCELERATION to Observer<JsonObject> {
val lat = it?.tryAsJsonObject("acceleration")?.tryAsJsonPrimitive("lateral")?.tryAsDouble?.div(9.80665)
sendDataUpdate("driving.acceleration.lateral", lat?.toString())
val long = it?.tryAsJsonObject("acceleration")?.tryAsJsonPrimitive("longitudinal")?.tryAsDouble?.div(9.80665)
sendDataUpdate("driving.acceleration.longitudinal", long?.toString())
},
CDSProperty.DRIVING_GEAR to Observer<JsonObject> {
sendDataUpdate("driving.gear", it?.tryAsJsonPrimitive("gear")?.tryAsInt?.toString())
},
CDSProperty.DRIVING_SPEEDACTUAL to Observer<JsonObject> {
sendDataUpdate("driving.speed", it?.tryAsJsonPrimitive("speedActual")?.tryAsDouble?.toString())
},
CDSProperty.ENGINE_CONSUMPTION to Observer<JsonObject> {
sendDataUpdate("engine.consumption", it?.tryAsJsonPrimitive("consumption")?.tryAsDouble?.toString())
},
CDSProperty.ENGINE_RPMSPEED to Observer<JsonObject> {
sendDataUpdate("engine.rpmSpeed", it?.tryAsJsonPrimitive("RPMSpeed")?.tryAsDouble?.toString())
},
CDSProperty.ENGINE_TORQUE to Observer<JsonObject> {
sendDataUpdate("engine.torque", it?.tryAsJsonPrimitive("torque")?.tryAsDouble?.toString())
},
CDSProperty.NAVIGATION_GPSPOSITION to Observer<JsonObject> {
val lat = it?.tryAsJsonObject("GPSPosition")?.tryAsJsonPrimitive("latitude")?.tryAsDouble
sendDataUpdate("navigation.gpsPosition.latitude", lat?.toString())
val long = it?.tryAsJsonObject("GPSPosition")?.tryAsJsonPrimitive("longitude")?.tryAsDouble
sendDataUpdate("navigation.gpsPosition.longitude", long?.toString())
},
CDSProperty.NAVIGATION_GPSEXTENDEDINFO to Observer<JsonObject> {
val altitude = it?.tryAsJsonObject("GPSExtendedInfo")?.tryAsJsonPrimitive("altitude")?.tryAsInt
if ((altitude ?: 65530) < 50000) {
sendDataUpdate("navigation.gpsPosition.altitude", altitude?.toString())
}
val heading = it?.tryAsJsonObject("GPSExtendedInfo")?.tryAsJsonPrimitive("heading")?.tryAsInt
sendDataUpdate("navigation.gpsPosition.heading", heading?.toString())
},
)
val csvDataList = listOf(
PvDefinition("driving.acceleratorPedal", 0.0, 100.0, "%"),
PvDefinition("driving.brakeContact", 0.0, 100.0, "%"),
PvDefinition("driving.clutchPedal", 0.0, 100.0, "%"),
PvDefinition("driving.steeringWheel", -560.0, 560.0, "°"),
PvDefinition("driving.acceleration.lateral", -10.0, 10.0, "g"),
PvDefinition("driving.acceleration.longitudinal", -10.0, 10.0, "g"),
PvDefinition("driving.gear", 0.0, 8.0, "."),
PvDefinition("driving.speed", 0.0, 250.0, "kmph"),
PvDefinition("engine.consumption", 0.0, 100.0, "."),
PvDefinition("engine.rpmSpeed", 0.0, 9000.0, "RPM"),
PvDefinition("engine.torque", -150.0, 500.0, "nm"),
PvDefinition("navigation.gpsPosition.latitude", -360.0, 360.0, "°"),
PvDefinition("navigation.gpsPosition.longitude", -360.0, 360.0, "°"),
PvDefinition("navigation.gpsPosition.altitude", -360.0, 360.0, "°"),
PvDefinition("navigation.gpsPosition.heading", -360.0, 360.0, "°"),
).joinToString("\n") {
it.toCSV()
}
val cdsData = HashMap<CDSProperty, CDSLiveData>()

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onStart(intent: Intent?, startId: Int) {
MainModel.isAndrobdConnected.value = true
val filter = IntentFilter(IDENTIFY)
filter.addCategory(REQUEST)
registerReceiver(receiver, filter)
sendDataList()

// start listening to the car data
cdsListeners.entries.forEach {
if (!cdsData.containsKey(it.key)) {
val liveData = CDSLiveData(applicationContext, it.key)
liveData.observeForever(it.value)
}
}
}

override fun onDestroy() {
super.onDestroy()
MainModel.isAndrobdConnected.value = false
try {
unregisterReceiver(receiver)
} catch (e: Exception) {}
try {
unbindService(bindListener)
} catch (e: Exception) {}
}

override fun handleIdentify(context: Context?, intent: Intent?) {
super.handleIdentify(context, intent)

// headerSent = false
sendDataList()
}

override fun getPluginInfo(): PluginInfo {
return pluginInfo
}

fun sendDataList() {
Log.i(TAG, "Sending Data List")
super.sendDataList(csvDataList)
}

override fun performConfigure() {
val intent = Intent(applicationContext, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import android.util.Log
import com.fr3ts0n.androbd.plugin.PluginReceiver
import me.hufman.idriveconnectaddons.androbd_gestalt.AndrobdPlugin.Companion.TAG

class AndrobdPluginReceiver: PluginReceiver() {
override fun getPluginClass(): Class<*> {
Log.i(TAG, "Received AndrobdPlugin query")
return AndrobdPlugin::class.java
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import android.app.Service
import android.content.Intent
import android.os.IBinder

class GestaltPlugin: Service() {
override fun onBind(intent: Intent?): IBinder? {
MainModel.isGestaltConnected.value = true
return null
}

override fun onUnbind(intent: Intent?): Boolean {
MainModel.isGestaltConnected.value = false
return super.onUnbind(intent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import me.hufman.idriveconnectaddons.androbd_gestalt.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding = ActivityMainBinding.inflate(layoutInflater)
binding.lifecycleOwner = this
binding.viewModel = MainModel
setContentView(binding.root)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package me.hufman.idriveconnectaddons.androbd_gestalt

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

object MainModel: ViewModel() {
val isGestaltConnected = MutableLiveData(false)
val isAndrobdConnected = MutableLiveData(false)
}
Loading

0 comments on commit 5655a4c

Please sign in to comment.