# Findings from the maps+more app Source: `de.volkswagen.mapsandmore`, decompiled with jadx 1.5.6. The car protocol lives in `de.vwag.viwi.*`. The app layer lives in `vwg.vw.prerelease.app4entry.*`. TLS runs on wolfSSL through JNI. ## Bluetooth - `createRfcommSocketToServiceRecord(3728ED40-E416-11E4-80A0-0002A5D5C51B)`. Secure socket, bonded devices only. - 4 attempts with 2, 4, 6, 8 s back-off (`SPPConnectThread`). ## Tunnel (`proxy/SPPConnection`) - Same framing as MIBBridge: `port u16le | type u8`. DATA 0, CONNECT 1, CLOSE 2, HEARTBEAT 3. - The app sends a heartbeat (`00 00 03`) every 5 s. It does not echo heartbeats. - It drops the link after 15 s without heartbeats from the car. - The virtual port is any unique u16. ## Registration (`ViwiRegistrationProcess`) 1. `GET /car/info/vin` on port 80 with `Connection: close`. 2. `POST /auth/registration` without a body. Expects 303 and `content-location`. 3. TLS 1.2 `ECDHE-RSA-AES128-GCM-SHA256` on port 443, then `GET `. The body is `username,password`. The app verifies the car certificate against a bundled root CA (`TlsSocket.ROOT_CA`, `SSL_VERIFY_PEER`): - `CN=ViWi Certificate Authority, OU=EECF/1, O=Volkswagen GmbH Deutschland, L=Wolfsburg, ST=Niedersachsen, C=DE` - Valid 2015-02-24 to 2055-02-14. - SHA-256 `E7:03:40:9C:26:FB:B7:3F:57:9C:EE:6E:0C:3E:63:D1:E5:1C:E5:29:E2:66:7A:57:F1:E6:95:CA:4B:78:F7:66` PIN (`computePin`), from the registration handshake key block: ``` digest = SHA-256(client_write_key || server_write_key) # 16 bytes each t = little-endian uint32 of digest[0..4] pin = t mod 1000000, left-padded to 6 digits ``` ## Session - TLS 1.2 `PSK-AES128-CBC-SHA256`. Identity = username. Key = UTF-8 bytes of the password. - The car sends the VIN as the PSK identity hint. - maps+more keeps one HTTP connection open. upApp does the same and reconnects once if the car closed it. ## HTTP - Paths have one leading and one trailing slash, for example `/chargingmanager/profiles/`. - No custom headers. POST bodies are `text/plain; charset=UTF-8`. - Bluetooth mode uses `127.0.0.1:4080` and `127.0.0.1:4443` as hosts. ## Charging manager writes All writes are POSTs to the element `uri`. Updates send only the changed fields. - Timer: `{"departureDate", "departureTime", "profile": "", "cyclic", "weekdays"}`. State alone: `{"state": "scheduled" | "idle"}`. - Profile: changed fields of `name, operations, maxCurrent, targetLevel, powerProvider, temperature, minLevel`. Create: `POST /chargingmanager/profiles/`. Delete: `DELETE `. - Provider: `{"weekdays", "preferredTimeStart", "preferredTimeEnd"}`. - Timer and provider times are UTC `HH:mm:ss`. Only the time moves. Weekdays and dates are not shifted. - `climateExtSupply` in the first profile: "Allow AC and heating from battery" / "Klimatisieren mit Batteriestrom". - maxCurrent: 5, 10, 13, 16. 32 only for the e-up! 300: VIN not `WVW`, or VIN[9] >= `L`. - Temperature: 15.5 to 30.0 °C in 0.5 steps. - Names: must not be empty. The app has no length limit. ## Other endpoints - `/car/{info,batteries,ranges,engines,consumptions,distances,drivingstates,environments,gearboxes}/` - `/chargingmanager/{profiles,timers,providers,batteryCharges,batteryClimates,batteryPlugs}/` - `/media/collections/`, `/media/renderers/`, `/medialibrary/sources/`, `/mixer/audiosources/`, `/mixer/positioners` - WebSocket `wss://host/` over the PSK session. Subscribe with `{"type":"subscribe","event":"[?fields=a,b]#","interval":n,"updatelimit":n}`. Server frames have `type` subscribe, unsubscribe or data. ## Car test, 2026-10-07 (e-up! 2020, MIB2 Entry, Pixel 8a with Android 17) - Bluetooth by service UUID, registration, PIN, certificate check and PSK session work. - The car certificate is `CN=MIB2 Entry ViWi Server`, valid 2015-02-24 to 2055-02-14. - Without ignition, all car values are empty strings and the profile and provider lists are empty. Timers are placeholders with empty fields. - With ignition on, all data arrives. `batteryCharges` also has `mode` (for example `DC`), `remainingTime` (minutes), `targetSOC`, `isActive`. - `batteryClimates.state` is `abortedIgnitionOn` while the ignition is on. - Normal profiles can contain `climateExtSupply`. The options profile contains `charge`. - Profiles have no `powerProvider` link on this car. There is one provider per profile, without a visible link. - WebSocket: the car puts several JSON messages into one frame without a separator. `data` is a JSON array, not a string. - WebSocket: subscriptions must be sent one at a time. Overlapping ones get `409 Conflict`. - Element GETs are wrapped too: `{"status": "ok", "data": {...}}`. POST answers `{"status": "ok"}`. - Writing a timer `state` (`scheduled` / `idle`) works and the car sends a timer event. ### Infotainment keys (`/mechanicalinput/`) Subscribe to each element with `updatelimit` 100. With a higher limit the car merges `pushed` and `released`. | Key | `meaning` | |---|---| | RADIO | `radio` | | MEDIA | `media` | | PHONE | `phone` | | Soft key with flag icon | `navigation` | | Soft key with gauge icon | `thinkBlueTrainer` | | Soft key with car icon | `car` | | Soft key with battery icon | `energyflow` | | Soft key with search icon | `search` | | MENU, X | no event | The car also lists `gauges`, which has no key on this car. `Right Rotary Encoder` (the right knob) sends `ticks` +1/-1 per click and `pushed`/`released`. `Left Rotary Encoder` (volume) was not tested. - Off-peak times: maps+more has no off switch, only start and end. Start = end means off. The car uses `00:00:00`–`00:00:00` UTC for unset entries. - The off-peak entry at the position of the options profile answers `403 Forbidden` to POST. Only the location entries are writable. - Writing provider `weekdays`, profile create and DELETE work on the car.