Closed Katana1234 closed 1 year ago
nice! I also recall some similar concepts requests which i collect here below for any possible futher improvements!
https://github.com/BimmerGestalt/IDriveConnectAddons/issues/5 https://github.com/BimmerGestalt/AAIdrive/issues/175
That looks so good! Congrats on figuring it out without any help! Your approach of using a List is the correct one. There aren't very strong graphical capabilities in the system, the closest being manually sending over PNGs to take the place of some of the table cells. It would also be possible to add an AM App Icon to the Car Information menu in the car, as another entry point besides the unlabeled entryButton in the OnlineServices menu.
Do you imagine it should stay in the Readout app? I was planning for such a detailed view to be a separate addon app, but that would add an extra unlabeled icon to the car. I also have been unsure of how to build the UI to configure the displayed data. A first version could simply show a static list, easily enough.
Would you like to contribute a Pull Request, or may I copy code from your branch and add some polish?
Very nice. I also tried to implement such feature but I wasn't able to. I would very appreciate it if it could get integrated into AAIDrive (or within a extra application).
Thank you
Thanks, i also love these advanced infos :)
I will post my code changes here and explain why it's not possible to polish the code ;) I only changed 2 files in the worst possible way to get this screen working. It's been almost a decade since i touched Android apps for the last time and i clearly have to admit that your nice and structured way of implementing things highly exceeds my android coding skills.
I added the ListUpdater class to the ReadoutApp. I was not able to properly reuse existing code from the CarDrivingStatsModel. This is why i'm parsing the json values manually. I added some more props to the CarDrivingStatsModel to be able to read them. Things that have to be polished... My code:
I think it's a very good idea to implement this into the ReadoutApp instead of creating a new one. The ReadoutApp is currently just showing a static message which doesn't bring any advantage to the user. :) So i'm totally fine with extending this app with some more infos. I also think having some selected values non configurable is a nice way to start this. Due to the fact that i am reusing the cds data that has been grabed by the advanced info app the update interval of the cds data is 1s (when running in foreground) and 5s (when running in background) Showing realtime data makes no real sense for me. but this is just my opinion. E.g. showing the current gear is a nice feature but not fast enough when the app is running in background.
Considerations for a full featured implementation: Create a config activity for this with the option
I wish i would be able to help out here, but i think i'm on the wrong level to contribute code...
ReadoutApp.kt:
package me.hufman.androidautoidrive.carapp.notifications
import android.os.Build
import android.os.Handler
import android.util.Log
import androidx.annotation.RequiresApi
import com.google.gson.Gson
import de.bmw.idrive.BMWRemoting
import de.bmw.idrive.BMWRemotingServer
import de.bmw.idrive.BaseBMWRemotingClient
import io.bimmergestalt.idriveconnectkit.CDS
import io.bimmergestalt.idriveconnectkit.IDriveConnection
import io.bimmergestalt.idriveconnectkit.Utils.rhmi_setResourceCached
import io.bimmergestalt.idriveconnectkit.android.CarAppResources
import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionStatus
import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess
import io.bimmergestalt.idriveconnectkit.rhmi.*
import me.hufman.androidautoidrive.*
import me.hufman.androidautoidrive.carapp.*
import me.hufman.androidautoidrive.phoneui.LiveDataHelpers.map
import me.hufman.androidautoidrive.utils.GsonNullable.tryAsInt
import me.hufman.androidautoidrive.utils.GsonNullable.tryAsJsonPrimitive
import org.json.JSONObject
@RequiresApi(Build.VERSION_CODES.O)
class ReadoutApp(val iDriveConnectionStatus: IDriveConnectionStatus, val securityAccess: SecurityAccess, carAppAssets: CarAppResources) {
val carConnection: BMWRemotingServer
val carApp: RHMIApplication
val infoState: RHMIState.PlainState
val readoutController: ReadoutController
init {
val cdsData = CDSDataProvider()
val listener = ReadoutAppListener(cdsData)
carConnection = IDriveConnection.getEtchConnection(iDriveConnectionStatus.host
?: "127.0.0.1", iDriveConnectionStatus.port ?: 8003, listener)
val readoutCert = carAppAssets.getAppCertificate(iDriveConnectionStatus.brand
?: "")?.readBytes() as ByteArray
val sas_challenge = carConnection.sas_certificate(readoutCert)
val sas_login = securityAccess.signChallenge(challenge = sas_challenge)
carConnection.sas_login(sas_login)
// create the app in the car
val rhmiHandle = carConnection.rhmi_create(null, BMWRemoting.RHMIMetaData("me.hufman.androidautoidrive.notification.readout", BMWRemoting.VersionInfo(0, 1, 0),
"me.hufman.androidautoidrive.notification.readout", "me.hufman"))
carConnection.rhmi_setResourceCached(rhmiHandle, BMWRemoting.RHMIResourceType.DESCRIPTION, carAppAssets.getUiDescription())
// no icons or text, so sneaky
carConnection.rhmi_initialize(rhmiHandle)
carApp = RHMIApplicationSynchronized(RHMIApplicationIdempotent(RHMIApplicationEtch(carConnection, rhmiHandle)), carConnection)
carApp.loadFromXML(carAppAssets.getUiDescription()?.readBytes() as ByteArray)
this.readoutController = ReadoutController.build(carApp, "NotificationReadout")
val destStateId = carApp.components.values.filterIsInstance<RHMIComponent.EntryButton>().first().getAction()?.asHMIAction()?.target!!
this.infoState = carApp.states[destStateId] as RHMIState.PlainState
initWidgets()
// register for readout updates
cdsData.setConnection(CDSConnectionEtch(carConnection))
cdsData.subscriptions[CDS.HMI.TTS] = {
val state = try {
Gson().fromJson(it["TTSState"], TTSState::class.java)
} catch (e: Exception) {
null
}
if (state != null) {
readoutController.onTTSEvent(state)
}
}
ListUpdater(Handler(), infoState).schedule()
}
class ReadoutAppListener(val cdsEventHandler: CDSEventHandler) : BaseBMWRemotingClient() {
override fun cds_onPropertyChangedEvent(handle: Int?, ident: String?, propertyName: String?, propertyValue: String?) {
cdsEventHandler.onPropertyChangedEvent(ident, propertyValue)
}
}
fun initWidgets() {
val list = infoState.componentsList.filterIsInstance<RHMIComponent.List>().first()
list.setEnabled(false)
list.setVisible(true)
val data = RHMIModel.RaListModel.RHMIListConcrete(1)
data.addRow(arrayOf(L.READOUT_DESCRIPTION))
list.getModel()?.setValue(data, 0, 1, 1)
}
fun disconnect() {
try {
IDriveConnection.disconnectEtchConnection(carConnection)
} catch (e: java.io.IOError) {
} catch (e: RuntimeException) {
}
}
class ListUpdater(val handler: Handler, val infoState: RHMIState.PlainState) {
companion object {
const val DELAY = 1000L
}
@RequiresApi(Build.VERSION_CODES.O)
val runnable = Runnable {
val list = infoState.componentsList.filterIsInstance<RHMIComponent.List>().first()
val data = RHMIModel.RaListModel.RHMIListConcrete(2)
val carInfo = CarInformationObserver()
try {
val engineTemp = JSONObject(carInfo.cdsData[CDS.ENGINE.TEMPERATURE].toString()).getJSONObject("temperature").getInt("engine").toString() + "°C Engine"
val oilTemp = JSONObject(carInfo.cdsData[CDS.ENGINE.TEMPERATURE].toString()).getJSONObject("temperature").getInt("oil").toString() + "°C Oil"
val bat = JSONObject(carInfo.cdsData[CDS.SENSORS.BATTERY].toString()).getInt("battery").toString() + "% Battery"
val fuel = JSONObject(carInfo.cdsData[CDS.SENSORS.FUEL].toString()).getJSONObject("fuel").getInt("tanklevel").toString() + "L Fuel" //range, reserve
val tempint = JSONObject(carInfo.cdsData[CDS.SENSORS.TEMPERATUREINTERIOR].toString()).getDouble("temperatureInterior").toString() + "°C Interior"
val tempext = JSONObject(carInfo.cdsData[CDS.SENSORS.TEMPERATUREEXTERIOR].toString()).getDouble("temperatureExterior").toString() + "°C Exterior"
val tempexch = JSONObject(carInfo.cdsData[CDS.CLIMATE.ACSYSTEMTEMPERATURES].toString()).getJSONObject(("ACSystemTemperatures")).getDouble("heatExchanger").toString() + "°C Exchange"
var gear = JSONObject(carInfo.cdsData[CDS.DRIVING.GEAR].toString()).getInt("gear")
//val t = carInfo.cdsData[CDS.CLIMATE.ACSYSTEMTEMPERATURES].toString()
var gearname = "-"
if (gear == 1) {
gearname = "N"
} else if (gear == 2) {
gearname = "R"
} else if (gear == 3) {
gearname = "P"
} else if (gear >= 5) {
gear -= 4
gearname = "D$gear"
}
gearname = "Gear " + gearname
data.addRow(arrayOf("Advanced Vehicle Information"))
data.addRow(arrayOf(""))
data.addRow(arrayOf(engineTemp, tempext))
data.addRow(arrayOf(oilTemp, tempint))
data.addRow(arrayOf(bat, tempexch))
data.addRow(arrayOf(fuel, gearname))
list.getModel()?.setValue(data, 0, data.height, data.height)
} catch (e: Exception) {
}
schedule()
}
@RequiresApi(Build.VERSION_CODES.O)
fun schedule() {
handler.removeCallbacks(runnable)
handler.postDelayed(runnable, DELAY)
}
}
}
CarDrivingStatsModel.kt:
class CarDrivingStatsModel(carInfoOverride: CarInformation? = null, val showAdvancedSettings: BooleanLiveSetting): ViewModel() {
companion object {
val CACHED_KEYS = setOf(
CDS.VEHICLE.VIN,
CDS.VEHICLE.UNITS,
CDS.DRIVING.ODOMETER,
CDS.DRIVING.AVERAGECONSUMPTION,
CDS.DRIVING.AVERAGESPEED,
CDS.DRIVING.DISPLAYRANGEELECTRICVEHICLE, // doesn't need unit conversion
CDS.DRIVING.DRIVINGSTYLE,
CDS.DRIVING.ECORANGEWON,
CDS.ENGINE.RANGECALC,
CDS.NAVIGATION.GPSPOSITION,
CDS.NAVIGATION.CURRENTPOSITIONDETAILEDINFO,
CDS.NAVIGATION.GPSEXTENDEDINFO,
CDS.SENSORS.BATTERY,
CDS.SENSORS.FUEL,
CDS.SENSORS.SOCBATTERYHYBRID,
CDS.VEHICLE.TIME,
CDS.ENGINE.TEMPERATURE,
CDS.CONTROLS.SUNROOF,
CDS.CONTROLS.WINDOWDRIVERFRONT,
CDS.CONTROLS.WINDOWPASSENGERFRONT,
CDS.CONTROLS.WINDOWDRIVERREAR,
CDS.CONTROLS.WINDOWPASSENGERREAR,
CDS.DRIVING.PARKINGBRAKE,
CDS.SENSORS.TEMPERATUREINTERIOR,
CDS.SENSORS.TEMPERATUREEXTERIOR,
CDS.CLIMATE.ACSYSTEMTEMPERATURES,
CDS.DRIVING.GEAR
)
}
...
Any updates on this?
I've been busy with other projects and haven't had time to look into this.
@Katana1234 can you maybe describe how you achieved the menu in the screenshot? this would be enough for my needs.
If it's a self compiled APK maybe just share it here?
@ogakul I already posted all my code changes necessary to show this menu + my objections why not to use this code above ;-)
I've posted a test build for this feature branch, try it out!
Nice! I tested it and it seems to be working fine
Do you plan to add an icon and a title to the "app" in the idrive? or is it not much possible? I can see that the Mapbox navi doesn't have a title
Nice! I tested it and it seems to be working fine
Do you plan to add an icon and a title to the "app" in the idrive? or is it not much possible? I can see that the Mapbox navi doesn't have a title
0°C oil & 255°C battery seems a bit off :D anyway I quickly tested it and it works, even if I have to test it more deeply in the next days and i'll let have my feedback on this
I had my car off, and that's what AAiDrive displays on the car info screen. When on, I have the correct oil temp.
I am not sure about the Battery though. It is fixed at 255C no matter if on or off. I also wonder how did I not fry inside :D
Thanks for the early feedback! In some places I have the code reject obvious bad data, I guess I need to add that for battery temp :) I can't change the app icon, but I plan on adding a shortcut icon like the Notifications app.
Added an app icon to the Vehicle Info screen of the car:
thanks! this looks pretty cool, also that it's within the My Car menu! I also noticed that you removed the battery temp info and added the Gear info. One thing that remained in the interface was the gap (empty cell) where the Batt temp was.
Do you know what the HVAC temp is?
Great work @hufman . That's really nice.
thanks! this looks pretty cool, also that it's within the My Car menu! I also noticed that you removed the battery temp info and added the Gear info. One thing that remained in the interface was the gap (empty cell) where the Batt temp was.
Do you know what the HVAC temp is?
@HaivanJV HVAC stands for Heat Ventilation and Air Condition. This value represents the temperature of the heat exchanger and is more or less the temperature of the air comming out of the air vents. (I assume!)
@hufman Nice implemenatation! Thank you very much for taking your time on this! I also like your concept of having multiple categories :) In theory the layout of the categories, rows and column could be configurable within the app. But i'm already super happy with this solution and everything that follows is just cosmetics!
Side note: The clutch value seems to be a state instead of a percentage value. At least for my car with an automatic gearbox. Values: 3 -> "Open / Idle?" -> This value is returned while gear P is engaged 2 -> "Uncoupled" -> This value is returned while a gear is engaged but the torque converter is uncoupled (when accellerating from standing still) 1 -> ??? -> not yet seen 0 -> "Coupled" -> This value is returned while a gear is engaged and the torque converter is coupled (typically while driving)
Thank you! I'm pleased with how it's shaping up, after I got over the motivational cliff to add a new data pipeline to the app :) The Battery Temp only shows a value if it's lower than 255C. If nobody sees any valid data for this field, it's easy enough to remove. I wasn't sure what the Clutch field would show, the CDS API just calls it clutchPedal.position, just like acceleratorPedal.position, so I guessed :) Similarly, the brakePedal in my car only goes from 10-25, somehow haha.
I guess battery temperature will only work for electric cars. Maybe we can determine if a car is electric when engine.electricVehicleMode returns a value. just guessing... :)
And i guess clutchPedal.position works different between manual and automatic gearboxes. BMW engineers are traditionally reusing properties in different ways for different configurations :D We can look into this in a later step. In theory we have the engine.info.gearboxType to determine what gearbox the car is equipped with. We'll probably have to compare some infos of different cars. but this is already too much for this GitHub issue :)
My Mini EV does have a battery temp there, it seems! I fixed a crash-on-disconnect I was seeing in analytics and merged it to the main branch, and will probably push it to the beta app store users after a week for translations to roll in.
@Katana1234 please experiment with the code and come up with more screens of data that you'd like to see :) I don't want to build a configuration system, but would accept ideas for how to organize the data into pre-built screens.
First of all, thank you for the great effort made to create this app, which allows us to obtain new features in our cars. Would it be possible to add the following data point to CDSMetrics: driving.acceleration {longitudinal= , lateral= } ?
I don't know if I'm wrong, but I think they are the different forces exerted on the car, when accelerating, braking or taking a curve in (m/s²), if we divide this value by 9.81 we could obtain the different G forces and display them on the screen.
It's just an idea and I don't know if it's correct.
Thank you.
@hufman
@hufman Feel free to try my branch: https://github.com/Katana1234/AAIdrive/tree/car_info_data_selection
It definitely needs a good polish. Strings are hardcoded and many parts are copied / modified from another class for my purposes.
Very cool! I posted a branch with your Driving Details updates and the Windows screen. However, isn't the GPS information is already available elsewhere in the car, like in one of the side panels? I'm reluctant to add more work for the translators than necessary :) I'm considering hiding the Windows and maybe GPS behind the Advanced Settings toggle, and then I can justify not translating them.
@hufman Nice refactoring, thanks :) Yes the GPS info is kind of redundant, but you will not be able to see all of this infos in one place within the IDRIVE system. And the altitude is inaccessible in IDRIVE.
Did you take a look at my brake interpretations? The brake value is not a percentage value. It is a bit coded state: https://github.com/Katana1234/AAIdrive/blob/bf259b1a284b956077e62676a21e53247b50505f/app/src/main/java/me/hufman/androidautoidrive/cds/CDSMetrics.kt#L201
I also added a interpretation for the clutch values, at least for automatic gearboxes. Otherwise the original number will be returned: https://github.com/Katana1234/AAIdrive/blob/bf259b1a284b956077e62676a21e53247b50505f/app/src/main/java/me/hufman/androidautoidrive/cds/CDSMetrics.kt#L236
Yes I saw the brake and clutch values. I feel my car has more of a smooth gradient for brake values than a bitfield would indicate, so I didn't want to commit to that interpretation and thus the strings, if they were incorrect. I should display them as binary in my car to see how it looks. As a rough approximation I suppose those labels would be more useful than the current arbitrary number. My car is electric so I don't know how the values correlate to a gas car.
Feel free to try your brakeContact values here: https://docs.google.com/spreadsheets/d/10FfyFMfW5VGKQGMyOlp4iCyeXoOleGbB5na0-ZFpMX8/edit?usp=sharing
This way you can validate my "interpratation" easily :)
Indeed, I see those values too, I'll code them up.
I updated my branch with these changes, what do you think? Any last ideas before I merge it to the main branch?
@hufman I didn't check the compiled version, but the values selected in the code looks good to me :)
dear @hufman , i tried this test branch. the only misreading I have to highlight is the sunroof one, since I do not have one :D
@berseker please try the latest build from the Readme, which should include a fix for your sunroof!
@hufman now seems fine
Hi there,
I wanted to show a few values on the IDrive system that you're not able to see by default. So i did some modifications (super quick and dirty!), mainly to the ReadoutApp, to get what i wanted. This is the result:
Wouldn't it be nice to properly implement a screen like this? :) My understanding of this whole project is sooooo bad so i guess i am not the right one to do this properly. I mostly achieved my goal by copy+paste and trial+error ;) But for any dev being more involved into this project it should be a easy task. I also don't know about the graphical capabilities of the system. I simply used the RHMI list.
In theory the output of the screen could be configurable in the main app. (Which values and where to place them + update interval of the cds data) Please let me know if i can help out any further.