App 2.19.0: EVHardware updated from upstream (malys/EVHardware e4e1158)
Three-way merge from b595ccb, the commit Fatih copied, to upstream's current state; Fatih's vendor reads (tyres, mileage, charge status, SWI69) stay. LICENSE.md records the base for the next update. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -3,20 +3,44 @@ package com.evsuite.hardware
|
||||
import com.evsuite.hardware.catalog.SnapshotKeys
|
||||
|
||||
/**
|
||||
* Decodes `com.saic.keyevent.hardkey.report` payloads.
|
||||
* Decodes the head unit's two hard-key broadcasts.
|
||||
*
|
||||
* Codes 5/23/24/25/87/88/164/286/287 come from R69 EOL's
|
||||
* PhysicalKeysBroadcastReceiver; 287 is also named VOICE_KEY_CODE by VrSpeechService.
|
||||
* Codes 17 and 18 are firmware aliases already observed by EVProfile.
|
||||
* They number their keys in two different spaces, so a code only means something together
|
||||
* with the broadcast it came on ([Source]):
|
||||
*
|
||||
* - `com.saic.keyevent.hardkey.report` carries Android key codes. 5/23/24/25/87/88/164/286/287
|
||||
* come from R69 EOL's PhysicalKeysBroadcastReceiver; 287 is also VrSpeechService's
|
||||
* VOICE_KEY_CODE. 17 and 18 are firmware aliases already observed by EVProfile.
|
||||
* - `com.android.systemui.ACTION_HARD_KEY_EVENT` carries SAIC's own numbering: 1/2 volume,
|
||||
* 3 mute, 4/5 previous/next (mediaservice HardKeyMonitor, radioservice ReceiveUtils),
|
||||
* 6–10 the instrument-cluster pad (engineermodeservice KeyEventReceiver, `_IPK`), 16 phone
|
||||
* (btcallservice).
|
||||
*
|
||||
* The same physical press arrives on both. Read as one space, SystemUI's 5 (next track) was
|
||||
* decoded as the report's 5 (phone), and a phone press arriving twice read as a double.
|
||||
* So SystemUI keys the report already carries are dropped, and only the cluster pad — which
|
||||
* the report does not carry — is read from SystemUI.
|
||||
*
|
||||
* A key neither list names is still reported, as an unknown key with its own [Event.keyId],
|
||||
* so a driver can bind a button this table has not met yet.
|
||||
*/
|
||||
class PhysicalButtonEventDecoder {
|
||||
enum class Button(val codes: Set<Int>, val shortKey: String, val longKey: String) {
|
||||
PHONE(setOf(5, 16), SnapshotKeys.KEY_PHONE_SHORT, SnapshotKeys.KEY_PHONE_LONG),
|
||||
UP(setOf(6), SnapshotKeys.KEY_UP_SHORT, SnapshotKeys.KEY_UP_LONG),
|
||||
DOWN(setOf(7), SnapshotKeys.KEY_DOWN_SHORT, SnapshotKeys.KEY_DOWN_LONG),
|
||||
OK(setOf(8), SnapshotKeys.KEY_OK_SHORT, SnapshotKeys.KEY_OK_LONG),
|
||||
LEFT(setOf(9), SnapshotKeys.KEY_LEFT_SHORT, SnapshotKeys.KEY_LEFT_LONG),
|
||||
RIGHT(setOf(10), SnapshotKeys.KEY_RIGHT_SHORT, SnapshotKeys.KEY_RIGHT_LONG),
|
||||
enum class Source { HARDKEY_REPORT, SYSTEM_UI }
|
||||
|
||||
enum class Button(
|
||||
/** On the report broadcast. */
|
||||
val codes: Set<Int>,
|
||||
val shortKey: String,
|
||||
val longKey: String,
|
||||
/** On the SystemUI broadcast, for keys the report does not carry. */
|
||||
val systemUiCodes: Set<Int> = emptySet(),
|
||||
) {
|
||||
PHONE(setOf(5), SnapshotKeys.KEY_PHONE_SHORT, SnapshotKeys.KEY_PHONE_LONG),
|
||||
UP(emptySet(), SnapshotKeys.KEY_UP_SHORT, SnapshotKeys.KEY_UP_LONG, setOf(6)),
|
||||
DOWN(emptySet(), SnapshotKeys.KEY_DOWN_SHORT, SnapshotKeys.KEY_DOWN_LONG, setOf(7)),
|
||||
OK(emptySet(), SnapshotKeys.KEY_OK_SHORT, SnapshotKeys.KEY_OK_LONG, setOf(8)),
|
||||
LEFT(emptySet(), SnapshotKeys.KEY_LEFT_SHORT, SnapshotKeys.KEY_LEFT_LONG, setOf(9)),
|
||||
RIGHT(emptySet(), SnapshotKeys.KEY_RIGHT_SHORT, SnapshotKeys.KEY_RIGHT_LONG, setOf(10)),
|
||||
SOURCE(setOf(110), SnapshotKeys.KEY_SOURCE_SHORT, SnapshotKeys.KEY_SOURCE_LONG),
|
||||
CENTER(setOf(23), SnapshotKeys.KEY_CENTER_SHORT, SnapshotKeys.KEY_CENTER_LONG),
|
||||
VOLUME_UP(setOf(24), SnapshotKeys.KEY_VOLUME_UP_SHORT, SnapshotKeys.KEY_VOLUME_UP_LONG),
|
||||
@@ -26,15 +50,28 @@ class PhysicalButtonEventDecoder {
|
||||
MUTE(setOf(164), SnapshotKeys.KEY_MUTE_SHORT, SnapshotKeys.KEY_MUTE_LONG),
|
||||
STAR_LEFT(setOf(17), SnapshotKeys.KEY_STAR_LEFT_SHORT, SnapshotKeys.KEY_STAR_LEFT_LONG),
|
||||
STAR_RIGHT(setOf(286, 18), SnapshotKeys.KEY_STAR_RIGHT_SHORT, SnapshotKeys.KEY_STAR_RIGHT_LONG),
|
||||
ASSISTANT(setOf(287), SnapshotKeys.KEY_ASSISTANT_SHORT, SnapshotKeys.KEY_ASSISTANT_LONG)
|
||||
ASSISTANT(setOf(287), SnapshotKeys.KEY_ASSISTANT_SHORT, SnapshotKeys.KEY_ASSISTANT_LONG);
|
||||
|
||||
/** The number a rule stores for this button — the first code, as rules always stored. */
|
||||
val id: Int get() = (codes + systemUiCodes).first()
|
||||
|
||||
companion object {
|
||||
fun byId(id: Int): Button? = entries.firstOrNull { it.id == id }
|
||||
}
|
||||
}
|
||||
enum class Press { SHORT, LONG, DOUBLE }
|
||||
data class Event(val button: Button, val press: Press) {
|
||||
val value: String get() = "${button.name}:${press.name}"
|
||||
|
||||
/**
|
||||
* One decoded press. [keyId] is [Button.id] for a known button, and an [unknownKeyId]
|
||||
* otherwise.
|
||||
*/
|
||||
data class Event(val keyId: Int, val press: Press) {
|
||||
val button: Button? get() = Button.byId(keyId)
|
||||
val value: String get() = "${keyName(keyId)}:${press.name}"
|
||||
fun readings(): Map<String, Any> = mapOf(SnapshotKeys.KEY_PHYSICAL_BUTTON_EVENT to value)
|
||||
}
|
||||
private enum class State { DOWN, LONG_REPORTED }
|
||||
private val states = mutableMapOf<Button, State>()
|
||||
private val states = mutableMapOf<Int, State>()
|
||||
|
||||
/**
|
||||
* How long a second press has to arrive within to count as a double.
|
||||
@@ -42,7 +79,7 @@ class PhysicalButtonEventDecoder {
|
||||
* The stock steering controls use the same window, and it is short on purpose: longer
|
||||
* would make two deliberate single presses merge into one double.
|
||||
*/
|
||||
private val lastShortAt = mutableMapOf<Button, Long>()
|
||||
private val lastShortAt = mutableMapOf<Int, Long>()
|
||||
|
||||
/**
|
||||
* @param atMillis when the event happened. A monotonic default, and JVM-only so the
|
||||
@@ -53,27 +90,28 @@ class PhysicalButtonEventDecoder {
|
||||
down: Boolean,
|
||||
longPress: Boolean,
|
||||
atMillis: Long = System.nanoTime() / 1_000_000,
|
||||
source: Source = Source.HARDKEY_REPORT,
|
||||
): Event? {
|
||||
val button = Button.entries.firstOrNull { keyCode in it.codes } ?: return null
|
||||
val key = keyId(source, keyCode) ?: return null
|
||||
return when {
|
||||
down && longPress && states[button] != State.LONG_REPORTED -> {
|
||||
states[button] = State.LONG_REPORTED
|
||||
down && longPress && states[key] != State.LONG_REPORTED -> {
|
||||
states[key] = State.LONG_REPORTED
|
||||
// A long press ends the pairing: the release that follows must not become the
|
||||
// second half of a double the driver never made.
|
||||
lastShortAt.remove(button)
|
||||
Event(button, Press.LONG)
|
||||
lastShortAt.remove(key)
|
||||
Event(key, Press.LONG)
|
||||
}
|
||||
down -> { states.putIfAbsent(button, State.DOWN); null }
|
||||
states.remove(button) == State.DOWN -> {
|
||||
val previous = lastShortAt[button]
|
||||
down -> { states.putIfAbsent(key, State.DOWN); null }
|
||||
states.remove(key) == State.DOWN -> {
|
||||
val previous = lastShortAt[key]
|
||||
if (previous != null && atMillis - previous <= DOUBLE_TAP_MS) {
|
||||
// Consumed, so three presses read as one double and one single rather than
|
||||
// as two overlapping doubles.
|
||||
lastShortAt.remove(button)
|
||||
Event(button, Press.DOUBLE)
|
||||
lastShortAt.remove(key)
|
||||
Event(key, Press.DOUBLE)
|
||||
} else {
|
||||
lastShortAt[button] = atMillis
|
||||
Event(button, Press.SHORT)
|
||||
lastShortAt[key] = atMillis
|
||||
Event(key, Press.SHORT)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
@@ -91,5 +129,37 @@ class PhysicalButtonEventDecoder {
|
||||
* double — see EVTasker's vehicle service.
|
||||
*/
|
||||
const val DOUBLE_TAP_MS = 300L
|
||||
|
||||
/** SystemUI codes for keys the report broadcast already carries — see the class doc. */
|
||||
private val SYSTEM_UI_DUPLICATES = setOf(1, 2, 3, 4, 5, 16)
|
||||
private const val UNKNOWN_REPORT_BASE = 10_000
|
||||
private const val UNKNOWN_SYSTEM_UI_BASE = 20_000
|
||||
|
||||
/** Kept apart from every [Button.id] and from the other broadcast's codes. */
|
||||
fun unknownKeyId(source: Source, keyCode: Int): Int = keyCode + when (source) {
|
||||
Source.HARDKEY_REPORT -> UNKNOWN_REPORT_BASE
|
||||
Source.SYSTEM_UI -> UNKNOWN_SYSTEM_UI_BASE
|
||||
}
|
||||
|
||||
/** Where an unknown key id came from, as `(source, code)` — null for a known button. */
|
||||
fun unknownKey(keyId: Int): Pair<Source, Int>? = when {
|
||||
Button.byId(keyId) != null -> null
|
||||
keyId >= UNKNOWN_SYSTEM_UI_BASE -> Source.SYSTEM_UI to keyId - UNKNOWN_SYSTEM_UI_BASE
|
||||
keyId >= UNKNOWN_REPORT_BASE -> Source.HARDKEY_REPORT to keyId - UNKNOWN_REPORT_BASE
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun keyName(keyId: Int): String = Button.byId(keyId)?.name ?: "KEY_$keyId"
|
||||
|
||||
private fun keyId(source: Source, keyCode: Int): Int? {
|
||||
if (keyCode < 0) return null
|
||||
return when (source) {
|
||||
Source.HARDKEY_REPORT -> Button.entries.firstOrNull { keyCode in it.codes }?.id
|
||||
Source.SYSTEM_UI -> {
|
||||
if (keyCode in SYSTEM_UI_DUPLICATES) return null
|
||||
Button.entries.firstOrNull { keyCode in it.systemUiCodes }?.id
|
||||
}
|
||||
} ?: unknownKeyId(source, keyCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user