388 lines
19 KiB
Markdown
388 lines
19 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project are documented here. Format follows
|
||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
## [2.2.11] - 2026-09-30
|
||
|
||
### Changed
|
||
|
||
- Page tabs now switch immediately without ViewPager's multi-page sliding animation.
|
||
|
||
## [2.2.10] - 2026-09-30
|
||
|
||
### Fixed
|
||
|
||
- Matched DriveHub_Dort's proven install path: verify SHA-256 and package identity, then
|
||
let Android PackageInstaller perform the authoritative signing-certificate check. This
|
||
avoids the head unit's false signature mismatch while remaining fail-closed.
|
||
|
||
## [2.2.9] - 2026-09-30
|
||
|
||
### Changed
|
||
|
||
- Updated the check-in-progress copy for a fresh OTA upgrade test from 2.2.8.
|
||
|
||
## [2.2.8] - 2026-09-29
|
||
|
||
### Changed
|
||
|
||
- Updated the check-in-progress copy for an end-to-end OTA upgrade test from 2.2.7.
|
||
|
||
## [2.2.7] - 2026-09-29
|
||
|
||
### Changed
|
||
|
||
- OTA updates now require an APK-specific `.sha256` asset or matching `SHA256SUMS` entry,
|
||
adapted from DriveHub_Dort commit `3b043875`. Downloaded bytes are hashed while streaming
|
||
and rejected before PackageInstaller if integrity does not match.
|
||
- Startup performs a quiet availability check and never downloads by itself. Manual updates
|
||
show download progress and a final result dialog.
|
||
- Existing exact-host, same-certificate and same-package checks remain mandatory.
|
||
|
||
## [2.2.6] - 2026-09-29
|
||
|
||
### Changed
|
||
|
||
- Redesigned all five pages with an OLED-black background, charcoal translucent panels,
|
||
muted gold navigation and cyan controls. Static gradients avoid continuous rendering.
|
||
- Vehicle overview is now the first page. Energy, temperatures and tyre pressures appear
|
||
at the top, with compact battery, range, power, speed and energy cards below.
|
||
Unknown data stays a dash.
|
||
- Added a wide-screen navigation rail and paired cards, with stacked pages and bottom
|
||
navigation for narrower windows. Headers follow the selected page.
|
||
- Restyled credentials, transfer controls, frequency picker, Wi-Fi, logs and About;
|
||
the interface stays dark regardless of the system theme.
|
||
|
||
## [2.2.5] - 2026-09-29
|
||
|
||
### Removed
|
||
|
||
- Window closing: its page, service, door listener, pulse logic, resources and dedicated
|
||
permissions are removed. Previous opt-in preferences cannot restart the removed service.
|
||
|
||
### Fixed
|
||
|
||
- OTA checks now report API, network and response errors instead of saying the app is current.
|
||
- Release selection considers every asset, choosing the newest eligible version even when
|
||
it appears later in the same release. Drafts and stable releases are excluded.
|
||
- Concurrent launch/button update checks no longer start duplicate downloads. Installer
|
||
session handles are closed after commit; failed sessions are abandoned.
|
||
- Wi-Fi permission refusals are handled explicitly, satisfying the Android lint checks.
|
||
|
||
## [2.2.4] - 2026-09-25
|
||
|
||
### Fixed
|
||
|
||
- OTA installs now go through the platform `PackageInstaller` instead of shelling out to
|
||
`pm install -r`. The old path handed the package manager service a file inside the app's
|
||
private cache, which it cannot read from its own process, and every such failure was
|
||
reported as `Kurulum reddedildi (imza uyuşmuyor?)` — a signature verdict on a correctly
|
||
signed build. A session streams the archive from this process, so no second process has
|
||
to reach our cache. Approach taken from merthankaraman/DriveHub_Dort, which solved the
|
||
same problem on the same head unit.
|
||
- The four ways an install can be refused no longer share one message. The certificate
|
||
check, an unparseable or wrong-package archive, and a failed session each say so, and the
|
||
package manager's own `INSTALL_FAILED_*` text is surfaced verbatim rather than guessed at.
|
||
- The archive's package name is checked against ours. The platform key signs every app on
|
||
the unit, so a certificate match alone never meant the APK was this app.
|
||
|
||
## [2.2.3] - 2026-09-23
|
||
|
||
### Added
|
||
|
||
- Opt-in SWI69 Comfort window closing adapted from WinClose, independent of ABRP.
|
||
Door opening while in P at readable 0 km/h closes all four windows with a bounded
|
||
five-second pulse; STOP is attempted on completion, cancellation and failure.
|
||
No driving timer, speed threshold, delay or per-window settings. A wake lock is held
|
||
across the pulse, and WinClose's 60-second cooldown suppresses a second close when the
|
||
door is reopened. The state a fresh listener registration replays seeds the door edge
|
||
detector instead of triggering it. The service stops itself on a non-SWI69 generation
|
||
or without system uid. Not tested on a car.
|
||
|
||
## [2.2.2] - 2026-09-17
|
||
|
||
### Changed
|
||
|
||
- Updated EVHardware to the shared `master` catalogue and telemetry implementation.
|
||
|
||
## [2.2.1] - 2026-09-04
|
||
|
||
### Fixed
|
||
|
||
- **Speed was uploaded 3.6× too high.** `PERF_VEHICLE_SPEED` reports km/h on SWI68, not the
|
||
metres per second AAOS specifies, and EVHardware converted it a second time. Every telemetry
|
||
upload from this generation therefore carried a speed 3.6 times the real one, and ABRP's
|
||
consumption and arrival models were fed it. Proven on SWI68-29958-1300R67 by two recordings of
|
||
a 2.4 km route that came out at 7.69 km and 8.46 km — 129 and 124 km/h average over a few
|
||
minutes of town driving. The conversion is now evidence-gated per firmware generation in
|
||
EVHardware. Telemetry already sent to ABRP is not corrected by this.
|
||
|
||
## [2.2.0] - 2026-08-27
|
||
|
||
### Changed
|
||
|
||
- Energy acquisition now consumes EVHardware's coherent `EnergySnapshot`; duplicate SOC,
|
||
range, power, temperature, odometer and charge property ids were removed from the app.
|
||
- Charging-session integration and tyre-pressure reads moved to EVHardware. The local
|
||
car-property adapter is now unused by production and supports debug diagnostics only.
|
||
|
||
## [2.1.8] - 2026-08-26
|
||
|
||
### Fixed
|
||
|
||
- **The weather answers arrived all along; they were being thrown away.** EVHardware read the
|
||
reply parcel one int too early — a nullable parcelable argument is written as a presence flag
|
||
and then the object, so every field landed in the next one's place, the payload's type tag
|
||
never matched, and a reading that had arrived was reported as unreadable. That is precisely
|
||
the `map=bound` with nothing behind it that 2.1.7 timed the query to explain. Tracking
|
||
EVHardware 1.6.0 fixes it, and `ext_temp` has a working source that does not depend on
|
||
catching a broadcast at the right moment.
|
||
|
||
## [2.1.7] - 2026-08-26
|
||
|
||
### Fixed
|
||
|
||
- **The weather broadcast is real but rare, and the app forgot it between restarts.** The action
|
||
is confirmed: the head unit's weather app sends `com.saicmotor.weather` when *it* refreshes,
|
||
not on any cadence this app can ask for. 2.1.6 listened and gave up between refreshes, and the
|
||
app is restarted every time the driver opens it to look at the log — which is the worst moment
|
||
to start waiting. The last reading is now kept across restarts for two hours, and the platform
|
||
is asked at start-up and on every temperature-less upload for a held broadcast, which costs
|
||
nothing and answers immediately if the weather app sends its update sticky.
|
||
|
||
### Changed
|
||
|
||
- **`map=bound` in 2.1.6 corrected the release note that shipped with it.** That release said the
|
||
map service never bound; the log showed the binder alive and the *query* returning nothing,
|
||
which is a different failure with a different fix. The map query is now timed, and the elapsed
|
||
time appears in the log — the query is bounded by a two-second wait inside EVHardware, so a
|
||
null returning at once was refused or answered undecodably while a null returning at the bound
|
||
is a service that did not answer in time. Nothing outside the library distinguishes them, and
|
||
guessing which one it was is what produced the wrong note.
|
||
- The broadcast reading now carries its age in the log (`bcast=Toulouse/12min`): where a value
|
||
came from does not say whether it still describes the day.
|
||
|
||
## [2.1.6] - 2026-08-26
|
||
|
||
### Fixed
|
||
|
||
- **`ext_temp` was still absent: the weather service 2.1.5 added never bound.** The log added in
|
||
that release is what showed it — every upload listed `ext_temp` among the omitted fields, with
|
||
no place name beside the fix. Meanwhile the head unit's own status bar shows a temperature
|
||
continuously, and it does not get it from that service: it gets it from a broadcast. The
|
||
uploader now listens for the same one (`com.saicmotor.weather`) and prefers it, falling back
|
||
to the map service query and, before either, to the vehicle sensor on a car that has one.
|
||
|
||
The payload's shape is unverified, so it is read by searching for any key that names a
|
||
temperature at any depth rather than by a fixed layout, in either unit, whether the value
|
||
arrives as a number or as a string. A parser written against a guessed layout fails silently
|
||
and looks exactly like the bug it was meant to fix.
|
||
|
||
### Added
|
||
|
||
- **The temperature's source, on every log entry** — `[temp car]`, `[temp bcast:Toulouse]`,
|
||
`[temp map:...]`, or when nothing answered, why each one did not: `[temp none bcast=no
|
||
broadcast yet map=unbound]`. A payload that arrives but carries no readable temperature is
|
||
reported with a sample of what it did carry, which is what a fixed parser would have hidden.
|
||
|
||
### Notes
|
||
|
||
- Position is not the problem, and the log settled it: fixes arrive from the `fused` provider,
|
||
accurate to about a metre and seconds old, with the coordinates the car is actually at. An
|
||
address that looks wrong on ABRP is not coming from what this app sends.
|
||
|
||
## [2.1.5] - 2026-08-26
|
||
|
||
### Fixed
|
||
|
||
- **`ext_temp` reached ABRP as 0 degC, and 2.1.4 did not change that.** Guarding the value only
|
||
stopped the app sending a zero; ABRP shows 0 for an absent `ext_temp` too, so the symptom was
|
||
identical. The cause is the car: this VHAL does not implement `ENV_OUTSIDE_TEMPERATURE`, so
|
||
there was never a vehicle reading to correct. The outside temperature now comes from the head
|
||
unit's own weather service (`com.saicmotor.mapservice`, already bound by EVTasker through
|
||
`SaicWeather`), queried for the car's position and cached for 10 minutes — the query can reach
|
||
the network from inside the head unit and it blocks the upload thread while it does. The
|
||
vehicle sensor is still preferred whenever a car does implement it.
|
||
- **A GPS fix could be dropped for being stale when its age was never knowable.** 2.1.4 aged
|
||
fixes on the monotonic clock, but a provider that does not stamp `getElapsedRealtimeNanos()`
|
||
leaves it at zero, which dated every fix to the last boot and would have discarded all of
|
||
them — a staleness guard turning into a position blackout. An unstamped fix is now kept, and
|
||
a fix from a clock that jumped forward is no longer aged negatively.
|
||
|
||
### Added
|
||
|
||
- **The payload summary is in the app's Log tab, not only in logcat.** 2.1.4 put it where a car
|
||
with no adb attached cannot show it. Each attempt now carries the fields it sent and the ones
|
||
it left out, which is the only thing that separates "ABRP shows 0" from "the app sent 0".
|
||
- **Position diagnostics on each entry:** the fix's age, its accuracy, the provider that
|
||
supplied it, and the place name the weather service answered for. ABRP reverse-geocodes the
|
||
address it displays from the same coordinates, so a place name that disagrees with it means
|
||
the coordinates are wrong, and one that agrees means ABRP is showing something older.
|
||
|
||
## [2.1.4] - 2026-08-25
|
||
|
||
### Fixed
|
||
|
||
- **The car was reported where it used to be, not where it is.** A GPS fix had no expiry, so
|
||
`lastLocation` kept whatever it last held. Two paths turned that into a wrong position:
|
||
start-up seeded it from `getLastKnownLocation()` unconditionally — with a cold receiver that
|
||
is the *previous* drive's fix, uploaded stamped with the current time for the first minutes
|
||
of every trip — and once the receiver stopped delivering (garage, tunnel) the same fix was
|
||
resent forever. A fix is now dropped past `max(5 min, 3 x upload interval)`, measured on the
|
||
monotonic clock rather than the wall clock, which the head unit adjusts from GPS itself.
|
||
Position is then omitted and ABRP keeps its own last point instead of being moved backwards.
|
||
- **`ext_temp` could be sent as a freezing day the car never measured.** It was the only
|
||
temperature without a plausibility guard, while `cabin_temp`, `batt_temp` and `hvac_setpoint`
|
||
all had one. A VHAL that does not implement `ENV_OUTSIDE_TEMPERATURE` returns `0.0` rather
|
||
than throwing, so the read looked successful and `ext_temp: 0` went to ABRP's consumption
|
||
model. Now guarded like the others (-50..80 degC, exactly 0.0 excluded). A genuine 0 degC is
|
||
lost with it — the cheaper of the two errors, and the same trade-off already made three times.
|
||
|
||
### Added
|
||
|
||
- **A one-line summary of every upload in the log.** `TelemetryPayload.summarize()` renders
|
||
what actually went on the wire, derived from the JSON itself so it cannot drift from the
|
||
payload it describes, and names the fields that were left out. That is what separates a read
|
||
that failed from a read that returned something wrong — the question this release started from.
|
||
Dropped stale fixes are logged with their age.
|
||
|
||
## [2.1.3] - 2026-08-25
|
||
|
||
### Removed
|
||
|
||
- `android.car.permission.CAR_POWERTRAIN` — declared since the first release and never used.
|
||
The app reads no powertrain property: park state comes from the vendor gear services through
|
||
EVHardware, and nothing here touches the standard AAOS gear or ignition properties the
|
||
permission guards. Dropped from the manifest and from the security allowlist.
|
||
|
||
## [2.1.2] - 2026-08-25
|
||
|
||
### Fixed
|
||
|
||
- **The uploader starts on a car that has not granted position yet.** The service declares the
|
||
`location` foreground type, and since Android 14 every declared type is checked against the
|
||
permissions held at the moment it enters the foreground — so a fresh install, or a boot
|
||
before the app had ever been opened, killed the service in its own `onCreate` before the
|
||
first tick. The type is now claimed only while the permission behind it is held, and the
|
||
activity asks for the permission *before* starting the service rather than after.
|
||
- **Outside and cabin temperature reach ABRP again.** Both were declared and read since 2.1.0
|
||
but never requested at runtime, so both properties answered as unreadable and were dropped
|
||
from every payload.
|
||
- **The service notification is visible.** `POST_NOTIFICATIONS` was declared and never asked
|
||
for; the only status the service has was being dropped silently on Android 13 and later.
|
||
- A permission granted while the service is already running now takes effect at once instead
|
||
of at the next boot.
|
||
|
||
## [2.1.1] - 2026-08-23
|
||
|
||
### Fixed
|
||
|
||
- Restored stable release builds by approving the read-only odometer, tyre-pressure and
|
||
battery-capacity permissions in the security allowlist.
|
||
|
||
## [2.1.0] - 2026-08-23
|
||
|
||
### Added
|
||
|
||
- The telemetry payload now carries every ABRP field this vehicle can actually answer:
|
||
pack temperature, usable capacity, state of energy, odometer, climate setpoint, the four
|
||
tyre pressures, and `kwh_charged` for the running charge session. `kwh_charged` has no
|
||
counter in the VHAL, so it is integrated from the instantaneous charge rate on every
|
||
sample tick — including the ticks the cadence policy throttles away — and resets when a
|
||
new session starts.
|
||
- Range falls back to the standard AAOS `RANGE_REMAINING` property when the SWI68 vendor
|
||
cluster answers nothing, so range now reaches ABRP on other firmware generations too.
|
||
|
||
### Fixed
|
||
|
||
- Three vehicle property IDs were off by one and had silently disabled their fields: the
|
||
charge-port read was the flap (`EV_CHARGE_PORT_OPEN`) rather than the cable
|
||
(`EV_CHARGE_PORT_CONNECTED`), and the cabin-temperature ID was not a VehiclePropertyIds
|
||
value at all, so `cabin_temp` had never been sent.
|
||
|
||
## [2.0.1] - 2026-08-22
|
||
|
||
### Fixed
|
||
|
||
- Telemetry no longer stops while the car is charging. Charge detection relied solely on
|
||
the standard AAOS charge-port property, which this VHAL need not implement: a plugged-in
|
||
car reading "unplugged" was treated as parked and idle and throttled to one sample every
|
||
15 minutes, so ABRP never saw the SOC curve it needs to size a charging stop. The charge
|
||
rate and, failing that, a rising SOC on a parked car are now used as fallbacks.
|
||
- `power` is now signed ABRP's way — positive leaving the battery, negative charging. The
|
||
vehicle's charge rate uses the opposite convention and was being forwarded unchanged, so
|
||
ABRP saw a fast-charging car as discharging.
|
||
|
||
## [2.0.0] - 2026-08-15
|
||
|
||
### ⚠️ Breaking — existing users must install once more
|
||
|
||
- Renamed from MG4ABRPUploader to **EVABRPUploader**, and the application id changed from
|
||
`com.mg4.abrptelemetry` to **`com.evsuite.abrp`**. Android treats this as a different app,
|
||
so it does not update an existing install: it is added next to it and starts with no ABRP
|
||
credentials and no permissions. Re-enter your ABRP token here, confirm telemetry reaches
|
||
your account, and only then uninstall the old app.
|
||
|
||
## [1.1.0] - 2026-08-10
|
||
|
||
### Changed
|
||
|
||
- Reworked the main screen as three swipeable ABRP, Service and Log pages with a fixed,
|
||
high-contrast top navigation bar and consistent selected-state accessibility.
|
||
- Updated the shared EVHardware dependency to the current catalogue and diagnostics layer.
|
||
|
||
## [1.0.3] - 2026-08-09
|
||
|
||
### Fixed
|
||
|
||
- Corrected stable release packaging and metadata.
|
||
|
||
## [1.0.2] - 2026-08-04
|
||
|
||
### Added
|
||
|
||
- Add an in-app About surface with project information and a repository QR code.
|
||
|
||
### Security
|
||
|
||
- Keep unstable OTA downloads in private cache, validate every redirect, verify the APK
|
||
signing certificate, and require an explicit successful `pm install` result.
|
||
- Remove the public-download update path and its storage permission exposure.
|
||
|
||
## [1.0.1] - 2026-08-03
|
||
|
||
### Changed
|
||
|
||
- Prefill the ABRP API-key input with the public open-source telemetry key while
|
||
preserving any custom key already saved by the user.
|
||
|
||
## [1.0.0] - 2026-08-02
|
||
|
||
### Added
|
||
|
||
- API key and token inputs are masked by default, with a password visibility
|
||
toggle sized for in-car touch targets.
|
||
- CONTRIBUTING.md, DESIGN.md and this CHANGELOG.
|
||
- Security issue template.
|
||
|
||
### Changed
|
||
|
||
- Adopted the EVSuite design system. Colour, spacing, type and component styles now
|
||
come from shared tokens (`values/colors.xml`, `values/dimens.xml`,
|
||
`values/styles_ev.xml`), generated by `tools/sync-tokens.mjs` and specified in
|
||
[DESIGN.md](DESIGN.md).
|
||
- Theme is now `Theme.Material3.DayNight.NoActionBar`: the app follows the vehicle's
|
||
day/night setting, and the light palette is held to the same 7:1 contrast floor as
|
||
the dark one.
|
||
- `ev_outline` raised from `#4A525B` to `#7A8492`. The old value was 2.25:1 against
|
||
`ev_surface`, below the 3:1 floor for non-text UI, so the card border it was meant
|
||
to provide effectively was not there.
|
||
- Launcher icon replaced with the EVSuite adaptive icon: charcoal tile, white glyph
|
||
with a single red accent, product caption. Vector only — the five legacy density
|
||
bitmap buckets are gone.
|
||
- README restructured to the shared EVSuite skeleton; table of contents is now
|
||
generated by `tools/sync-docs.mjs`.
|
||
- Repaired bug/feature issue templates (duplicate YAML key, field ordering).
|