When I got my Renault Zoe, I was excited to see that it came with an accompanying app, which let’s you remotely pre-heat (or cool) the car and also check the charge status of the battery. Being able to pre-condition the car is great as the process takes a few minutes and it’s nice when it’s finished by the time you’re getting into the car. And having remote access to the charging status is especially handy on road trips: Firstly, it’s annoying to go back to the car and find that it still needs to charge for another half hour and you could have gotten a coffee or went for a nice walk. Secondly, you might be wasting money by remaining plugged in when the car is already full, if you’re getting charged by the minute. So there’s real utility to such an app.

The excitement about the app didn’t last too long. It feels neglected, is slow to use, and routinely forgets my login credentials. It doesn’t support current OS features such as the iPhone X-sized screen, Siri or Shortcuts either.

However, luckily the app is built using an API that other curious minds have since reverse-engineered – Kudos in particular to Terence Eden and aironaut.ch for sharing their insights. This allows you to replicate the features of the app in a different package.

What I wanted to do was this: Be able to use Siri on my phone or watch to ask “How’s my car” to get the charging status, and “Pre-heat my car” to start pre-heating it.

Knowing the API is the first half, the second half is picking a tool that let’s me query the API from Siri. This should be possible with Apple’s Shortcuts app, but I prefer to use a textual rather than visual programming language. The right tool for me was the excellent Pythonista which has a beta version available that adds support for running scripts via Siri and the Shortcuts app.

The result: I can now say “Hey Siri, pre-heat Moritz” and a few seconds later it’ll start pre-heating, or say “How’s Moritz?” and get the current charge, current range, and when it’ll reach 100% charge. I can also trigger both of those without saying a word through the Shortcuts widget on my lock screen. Works like a charm!


If anyone wants to replicate this, below is the script. Copy it into Pythonista, adjust your username and password, optionally set a custom name for the car. Then use Pythonista’s built-in feature to add Siri Shortcut to use it with Siri. By default this will run the “battery status” part, for the pre-conditioning use a single argument precondition. Once you’ve added the command to Siri, you can also use it in the Shortcuts app.

#!python3

import datetime
import requests
import shortcuts
import sys

username = "you@domain.com"
password = "YourPassw0rd"
carname = "Zoe"

### Fetches auth token and VIN
def login():
  headers = {"Content-Type": "application/json"}
  body = {"username": username, "password": password}
  url = f"https://www.services.renault-ze.com/api/user/login"
  
  result = requests.post(url, json=body, headers=headers).json()
  token = result['token']
  vin = result['user']['vehicle_details']['VIN']
  return {"token": token, "vin": vin}
  
def get_battery():
  creds = login()
  headers = {"Authorization": f"Bearer {creds['token']}"}
  url = f"https://www.services.renault-ze.com/api/vehicle/{creds['vin']}/battery"
  
  result = requests.get(url, headers=headers).json()
  if result['charging']:
    minutes = result['remaining_time']
    remaining = fuzzy_duration(minutes)
    status = f"done charging in {remaining}"
  else:
    status = "is not charging"
  return f"{carname} can go {result['remaining_range']}km. Battery is {result['charge_level']}% full and {status}."
  
def fuzzy_duration(minutes):
  if minutes < 90:
    return f"{minutes} minutes"
  if minutes < 3 * 60:
    half_hours = round(minutes / 30) / 2
    return f"{half_hours} hours"
  else:
    hours = round(minutes / 60)
    return f"{hours} hours"
    
def precondition():
  creds = login()
  headers = {"Authorization": f"Bearer {creds['token']}"}
  url = f"https://www.services.renault-ze.com/api/vehicle/{creds['vin']}/air-conditioning"
  result = requests.post(url, headers=headers)
  if 200 <= result.status_code < 300:
    return f"{carname} is warming up"
  else:
    return f"{carname} is not responding"
  
def main():
  if len(sys.argv) > 1 and sys.argv[1] == "precondition":
    text = precondition()
  else:
    text = get_battery()
    
  if shortcuts.is_shortcut():
    shortcuts.set_spoken_output(text)
  else:
    # For debugging in the main app (normally, this script would run in a Siri shortcut):
    print(text)
      
if __name__ == '__main__':
  main()