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:
2026-10-19 09:44:00 +02:00
co-authored by Claude Opus 5.5
parent c6e6b3e88e
commit f0f4f57fee
71 changed files with 4646 additions and 191 deletions
@@ -8,12 +8,6 @@ class PhysicalButtonEventDecoderTest {
@Test fun `every R69 hardkey code decodes`() {
val expected = mapOf(
5 to PhysicalButtonEventDecoder.Button.PHONE,
6 to PhysicalButtonEventDecoder.Button.UP,
7 to PhysicalButtonEventDecoder.Button.DOWN,
8 to PhysicalButtonEventDecoder.Button.OK,
9 to PhysicalButtonEventDecoder.Button.LEFT,
10 to PhysicalButtonEventDecoder.Button.RIGHT,
16 to PhysicalButtonEventDecoder.Button.PHONE,
23 to PhysicalButtonEventDecoder.Button.CENTER,
24 to PhysicalButtonEventDecoder.Button.VOLUME_UP,
25 to PhysicalButtonEventDecoder.Button.VOLUME_DOWN,
@@ -32,6 +26,72 @@ class PhysicalButtonEventDecoderTest {
assertEquals(button, decoder.accept(code, false, false)?.button)
}
}
@Test fun `the SystemUI broadcast adds only the cluster pad`() {
val systemUi = PhysicalButtonEventDecoder.Source.SYSTEM_UI
val expected = mapOf(
6 to PhysicalButtonEventDecoder.Button.UP,
7 to PhysicalButtonEventDecoder.Button.DOWN,
8 to PhysicalButtonEventDecoder.Button.OK,
9 to PhysicalButtonEventDecoder.Button.LEFT,
10 to PhysicalButtonEventDecoder.Button.RIGHT
)
expected.forEach { (code, button) ->
val decoder = PhysicalButtonEventDecoder()
decoder.accept(code, true, false, source = systemUi)
assertEquals(button, decoder.accept(code, false, false, source = systemUi)?.button)
}
}
@Test fun `a press on both broadcasts is one press`() {
// Next track: 87 on the report, 5 on SystemUI. Read as one space, SystemUI's 5 was a
// phone press; and the phone itself (5 and 16) arrived twice, i.e. as a double.
val decoder = PhysicalButtonEventDecoder()
val systemUi = PhysicalButtonEventDecoder.Source.SYSTEM_UI
val events = listOfNotNull(
decoder.accept(87, true, false, atMillis = 0),
decoder.accept(5, true, false, atMillis = 0, source = systemUi),
decoder.accept(87, false, false, atMillis = 50),
decoder.accept(5, false, false, atMillis = 50, source = systemUi),
decoder.accept(5, true, false, atMillis = 1_000),
decoder.accept(16, true, false, atMillis = 1_000, source = systemUi),
decoder.accept(5, false, false, atMillis = 1_050),
decoder.accept(16, false, false, atMillis = 1_050, source = systemUi)
)
assertEquals(listOf("MEDIA_NEXT:SHORT", "PHONE:SHORT"), events.map { it.value })
}
@Test fun `an unlisted key is reported with an id of its own`() {
val decoder = PhysicalButtonEventDecoder()
decoder.accept(3, true, false)
val event = decoder.accept(3, false, false)!!
assertNull(event.button)
assertEquals(
PhysicalButtonEventDecoder.Source.HARDKEY_REPORT to 3,
PhysicalButtonEventDecoder.unknownKey(event.keyId)
)
// Same code, other broadcast, other key.
val systemUi = PhysicalButtonEventDecoder.Source.SYSTEM_UI
decoder.accept(30, true, false, source = systemUi)
val other = decoder.accept(30, false, false, source = systemUi)!!
assertEquals(PhysicalButtonEventDecoder.Source.SYSTEM_UI to 30, PhysicalButtonEventDecoder.unknownKey(other.keyId))
assertEquals("KEY_${event.keyId}:SHORT", event.value)
}
@Test fun `stored button ids are unchanged`() {
// Rules on disk hold these numbers; changing one silently rebinds a rule.
val ids = PhysicalButtonEventDecoder.Button.entries.associate { it.name to it.id }
assertEquals(
mapOf(
"PHONE" to 5, "UP" to 6, "DOWN" to 7, "OK" to 8, "LEFT" to 9, "RIGHT" to 10,
"SOURCE" to 110, "CENTER" to 23, "VOLUME_UP" to 24, "VOLUME_DOWN" to 25,
"MEDIA_NEXT" to 87, "MEDIA_PREVIOUS" to 88, "MUTE" to 164, "STAR_LEFT" to 17,
"STAR_RIGHT" to 286, "ASSISTANT" to 287
),
ids
)
}
@Test fun `two releases inside the window make one double`() {
val decoder = PhysicalButtonEventDecoder()
decoder.accept(17, true, false, atMillis = 0)