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
211 lines
11 KiB
Markdown
211 lines
11 KiB
Markdown
# EVHardware
|
|
|
|
<p align="center"><img src="docs/logo.svg" width="440" alt="EVHardware"></p>
|
|
|
|
[](https://github.com/malys/EVHardware/actions/workflows/tests.yml)
|
|
[](https://github.com/malys/EVHardware/actions/workflows/security.yml)
|
|
[](https://github.com/malys/EVHardware/actions/workflows/publish.yml)
|
|
[](https://github.com/malys/EVHardware/releases)
|
|
[](LICENSE)
|
|
[](https://malys.github.io/EVSuite/)
|
|
|
|
> ⚠️ **This library reads and writes a car's settings.** It runs inside apps installed on
|
|
> an MG4 head unit. Read [DISCLAIMER.md](DISCLAIMER.md) before depending on it.
|
|
> MG and MG4 are third-party marks used only to identify compatibility; this independent
|
|
> project is not affiliated with or approved by SAIC Motor or MG Motor.
|
|
|
|
Shared vehicle-access layer for the SAIC MG4 (Android Automotive OS 9, MT2712). One
|
|
implementation of the reflection-based hardware layer, the 0 km/h safety gate, the driving
|
|
models, and the **condition/action catalogue** — consumed by the EVSuite apps so they do not
|
|
each re-derive it and drift apart.
|
|
|
|
The design line: **the apps are thin. EVHardware provides the vehicle.** EVTasker is a rule
|
|
engine between shared conditions and actions; EVChargePilot and EVABRPUploader consume the
|
|
same energy snapshot instead of carrying their own property ids, units and fallback order.
|
|
|
|
Consumers: EVProfile (drive-profile manager; owns the signature-protected TaskerBridge), EVTasker, EVABRPUploader
|
|
and EVChargePilot. Each app consumes EVHardware as a git submodule (`EVHardware/lib` as the
|
|
`:evhardware` subproject). Common toolchain: **AGP 9.1.1 / Gradle 9.3.1 / compileSdk 36 / JDK 17**.
|
|
|
|
## Part of EVSuite
|
|
|
|
EVHardware is the shared vehicle library of [**EVSuite**](https://malys.github.io/EVSuite/), a family of independent,
|
|
offline-first apps for the MG4 head unit (Android Automotive OS 9). Each app installs on its
|
|
own — pick only what you need. User guides and install instructions:
|
|
<https://malys.github.io/EVSuite/>.
|
|
|
|
Discover the rest of the suite:
|
|
|
|
[](https://github.com/malys/EVProfile)
|
|
[](https://github.com/malys/EVTasker)
|
|
[](https://github.com/malys/EVABRPUploader)
|
|
[](https://github.com/malys/EVChargePilot)
|
|
[](https://github.com/malys/EVLauncher)
|
|
[](https://github.com/malys/EVSwipe)
|
|
|
|
---
|
|
|
|
## Contents
|
|
|
|
- [Part of EVSuite](#part-of-evsuite)
|
|
- [Overview](#overview)
|
|
- [How it works](#how-it-works)
|
|
- [Install](#install)
|
|
- [Building](#building)
|
|
- [Project documents](#project-documents)
|
|
- [Security](#security)
|
|
- [Contributing](#contributing)
|
|
- [Legal](#legal)
|
|
|
|
## Overview
|
|
| Area | Types |
|
|
|---|---|
|
|
| Vehicle access | `EVHardware` — Katman1/4/5 reflection over `android.car` + SAIC SDK, per-firmware routing |
|
|
| Safety | `VehicleWriteGate` (0 km/h, fail-closed) + `@RequiresStandstill` on every gated setter |
|
|
| Vendor services | `saic.*` — the AIDL the head unit's own HVAC, charging, radio, hands-free and TTS apps use |
|
|
| Radio | `SaicRadio` — play, silence, fail-closed play/pause toggle, FM/AM tune, station stepping (DAB included), screen |
|
|
| Energy telemetry | `EnergyTelemetryReader`, `EnergySnapshot` — coherent nullable SOC, range, power, temperature, charging, tyre and climate reads |
|
|
| Energy accounting | `TripDetector`, `EnergyTripAccumulator`, `ChargingSessionMeter`, `EnergyTripHistoryStore` — tested automatic boundaries, integration and atomic local persistence |
|
|
| Firmware | `FirmwareInfo` (detection + capability helpers), `FirmwareGen` |
|
|
| Models | `DrivingProfile`, `DriveMode`, `RegenLevel`, `ProfileBackup` |
|
|
| Catalogue | `ConditionType`, `ActionType`, `ValueSpec`, `VehicleEnums`, `SnapshotKeys` |
|
|
| Compatibility | `@SupportedOn`, `FirmwareSupport`, `FirmwareMatrix` → [docs/firmware-matrix.md](docs/firmware-matrix.md) |
|
|
| Diagnostics | `AppLogger` (ring buffer, no ADB needed), `diag.CrashLogger`, `diag.PrivateBin` |
|
|
|
|
### The safety gate and `@RequiresStandstill`
|
|
|
|
`VehicleWriteGate` refuses any road-behaviour write above 0 km/h, and fails closed when the
|
|
speed is unreadable. Every gated setter in `EVHardware` carries `@RequiresStandstill`, and
|
|
a unit test asserts that exact set — a setter cannot silently gain or lose gating. Comfort
|
|
writes (seat/steering heating, volume, brightness, audio) are not gated and not annotated.
|
|
|
|
The threshold is fixed at exactly 0 km/h. It is not configurable, and no secondary signal
|
|
(including park) can rescue an unreadable speed. Every decision reads the speed again so a
|
|
sequence is cancelled as soon as the vehicle moves or the signal disappears.
|
|
|
|
### Firmware compatibility is generated, not written
|
|
|
|
Each vehicle catalogue entry carries `@SupportedOn(...)`, derived from `FirmwareInfo` and
|
|
`EVHardware`'s per-generation routing. [docs/firmware-matrix.md](docs/firmware-matrix.md)
|
|
is rendered from those annotations by `FirmwareMatrix` and checked by a test — never edited
|
|
by hand. Consumers use the same annotations to hide entries unsupported on the connected
|
|
car.
|
|
|
|
> The matrix is derived from code, **not** on-vehicle testing. Consumers should surface a
|
|
> diagnostic that reads each signal live; unreadable ≠ unsupported.
|
|
|
|
### Climate, glass and charging go through the vendor services
|
|
|
|
The AOSP property ids for HVAC and window position match the R69 runtime names but are not
|
|
confirmed on every MG4 generation. They are still used for *reads*, where a wrong id returns null
|
|
and nothing else happens — but nothing is written through them, because writing an unverified
|
|
id to a vehicle is a different kind of mistake.
|
|
|
|
Writes go to `saic.*` instead: the binder interfaces the car's own HVAC, charging and vehicle
|
|
apps call. The integration contract is known; whether a
|
|
given firmware answers is a bind, which consumers report live rather than tabulate. The
|
|
electric tailgate stays out — the launcher defines OPEN and CLOSE as the same value, so it is
|
|
a pulse whose direction depends on state this cannot read.
|
|
|
|
---
|
|
|
|
## How it works
|
|
EVHardware is consumed as a **git submodule** exposed as a Gradle subproject. In a
|
|
consumer app:
|
|
|
|
```bash
|
|
git submodule add https://github.com/malys/EVHardware EVHardware
|
|
```
|
|
|
|
`settings.gradle.kts`:
|
|
|
|
```kotlin
|
|
include(":evhardware")
|
|
project(":evhardware").projectDir = file("EVHardware/lib")
|
|
```
|
|
|
|
`app/build.gradle.kts`:
|
|
|
|
```kotlin
|
|
dependencies { implementation(project(":evhardware")) }
|
|
```
|
|
|
|
Vehicle writes require the app to be signed with the ROM **platform key** (the car
|
|
permissions are `signature|privileged`); reads of standard AAOS properties work with less.
|
|
The library itself declares no permissions and no `sharedUserId` — that is the consuming
|
|
app's decision.
|
|
|
|
## Install
|
|
|
|
EVHardware is a library, not an APK: it ships inside the consumer apps (EVProfile, EVTasker, EVABRPUploader,
|
|
EVChargePilot). Those apps are sideloaded on the head unit via the keyboard route: open a
|
|
text field, long-press `,` on the on-screen keyboard → **Language settings** → search
|
|
`backup` then press back to reach Android Settings → enable **Developer options** +
|
|
**Install unknown apps** → search `storage` and open the APK. See each app's README for
|
|
the full steps.
|
|
|
|
### Consume the AAR
|
|
|
|
Projects outside this org can depend on EVHardware as a binary instead of a submodule. A
|
|
version tag publishes the AAR to GitHub Packages (`com.evsuite:evhardware:<version>`), and
|
|
`./gradlew :lib:publishToMavenLocal` installs it to `~/.m2` for local experimentation:
|
|
|
|
```kotlin
|
|
repositories {
|
|
mavenLocal()
|
|
maven { url = uri("https://maven.pkg.github.com/malys/EVHardware") } // GitHub Packages
|
|
}
|
|
dependencies { implementation("com.evsuite:evhardware:0.1.0-SNAPSHOT") }
|
|
```
|
|
|
|
The AAR carries `consumer-rules.pro`, so a consumer's R8 keeps the reflected names. Vehicle
|
|
writes still require the consuming app to be signed with the ROM platform key.
|
|
|
|
## Building
|
|
```bash
|
|
./gradlew :lib:testDebugUnitTest # unit tests + regenerates docs/firmware-matrix.md
|
|
./gradlew :lib:assembleDebug # the AAR
|
|
```
|
|
|
|
JDK 17 (AGP 9.1.1 / Gradle 9.3.1 / compileSdk 36). `android.car` is not on the compile SDK, so all vehicle access is
|
|
reflection — there is nothing to test on an emulator; the JVM tests cover the decision
|
|
logic (gate, firmware annotations, catalogue consistency, telemetry validation and energy
|
|
integration).
|
|
|
|
### Adding a condition or action
|
|
|
|
One enum entry in `catalog/ConditionType` or `catalog/ActionType`, one string in
|
|
`lib/src/main/res/values/strings.xml` (+ `values-fr/`), and — for a vehicle entry — one
|
|
`@SupportedOn(...)`. A test fails if a vehicle entry lacks it; the matrix regenerates on the
|
|
next test run.
|
|
|
|
---
|
|
|
|
## Project documents
|
|
| Document | What it covers |
|
|
|---|---|
|
|
| [DESIGN.md](DESIGN.md) | The EVSuite design system — colour, type, touch targets, icons |
|
|
| [AGENTS.md](AGENTS.md) | Context for AI agents working in this repository |
|
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to build, test and submit a change |
|
|
| [SECURITY.md](SECURITY.md) | Threat model and vulnerability disclosure |
|
|
| [DISCLAIMER.md](DISCLAIMER.md) | Vehicle-safety disclaimer — read before installing |
|
|
| [docs/radio-action-gap.md](docs/radio-action-gap.md) | Which radio behaviours the catalogue carries, and which were rejected |
|
|
| [CHANGELOG.md](CHANGELOG.md) | Release history |
|
|
| [LICENSE.md](LICENSE.md) | Licence text |
|
|
|
|
## Security
|
|
See [SECURITY.md](SECURITY.md). The library is the one place vehicle writes happen for its
|
|
consumers, so the gate, the closed action vocabulary, and the per-firmware routing all live
|
|
here rather than being reimplemented per app.
|
|
|
|
## Contributing
|
|
Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. In short: this
|
|
code runs in a moving vehicle, so changes stay small, carry tests, and say in the diff
|
|
what would break without them. Anything touching the interface follows
|
|
[DESIGN.md](DESIGN.md).
|
|
|
|
## Legal
|
|
Source-available under the PolyForm Noncommercial License 1.0.0. Commercial use is not
|
|
permitted. See [LICENSE](LICENSE) and [LICENSE.md](LICENSE.md). Runs on a vehicle; see
|
|
[DISCLAIMER.md](DISCLAIMER.md).
|