maps-and-more.md
⎇
Raw

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 <content-location>. 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": "<profile id>", "cyclic", "weekdays"}. State alone: {"state": "scheduled" | "idle"}.
  • Profile: changed fields of name, operations, maxCurrent, targetLevel, powerProvider, temperature, minLevel. Create: POST /chargingmanager/profiles/. Delete: DELETE <uri>.
  • 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":"<path>[?fields=a,b]#<sessionId>","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.