Filed under: kind of insane code, be careful about doing this at home.
Recently we went electric, and got a Chevy Bolt to replace our 12 year old Toyota Prius (who has and continues to be a workhorse). I had a spot in line for a Tesla Model 3, but due to many factors, we decided to go test drive and ultimately purchase the Bolt. It’s a week in and so far so good.
One of the things GM does far worse than Tesla, is make its data available to owners. There is quite a lot of telemetry captured by the Bolt, through OnStar, which you can see by logging into their website or app. But, no API (or at least no clear path to get access to the API).
However, it’s the 21st century. That means we can do ridiculous things with software, like use python to start a full web browser, log into their web application, and scrape out data….. so I did that.
#!/usr/bin/env python import configparser import os from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By config = configparser.ConfigParser() config.read("config.ini") chrome_options = Options() # chrome_options.add_argument("--headless") driver = webdriver.Chrome(executable_path=os.path.abspath("/usr/local/bin/chromedriver"), chrome_options=chrome_options) driver.get("https://my.chevrolet.com/login") user = driver.find_element_by_id("Login_Username") passwd = driver.find_element_by_id("Login_Password") user.send_keys(config["default"]["user"]) passwd.send_keys(config["default"]["passwd"]) driver.find_element_by_id("Login_Button").click() timeout = 120 try: element_present = EC.presence_of_element_located((By.CLASS_NAME, 'status-box')) WebDriverWait(driver, timeout).until(element_present) print(driver.find_element_by_class_name("status-box").text) print(driver.find_element_by_class_name("status-right").text) except TimeoutException: print("Timed out waiting for page to load") print("Done!")
This uses selenium, which is a tool used to test websites automatically. To get started you have to install selenium python drivers, as well as the chrome web driver. I’ll leave those as an exercise to the reader.
After that, the process looks a little like one might expect. Start with the login screen, find the fields for user/password, send_keys (which literally acts like typing), and submit.
The My Chevrolet site is an Angular JS site, which seems to have no stateful caching of the telemetry data for the car. Instead, once you log in you are presented with an overview of your car, and it makes an async call through the OnStar network back to your car to get its data. That includes charge level, charge state, estimated range. The OnStar network is a CDMA network, proprietary protocol, and ends up taking at least 60 seconds to return that call.
This means that you can’t just pull data out of the page once you’ve logged in, because the data isn’t there, there is a spinner instead. Selenium provides you a WebDriverWait class for that, which will wait until an element shows up in the DOM. We can just wait for the status-box to arrive. Then dump its text.
The output from this script looks like this:
Current Charge: 100% Plugged in(120V) Your battery is fully charged. Estimated Electric Range: 203 Miles Estimated Total Range: 203 Miles Charge Mode: Immediate Change Mode Done!
Which was enough for what I was hoping to return.
Honestly, I really didn’t want to write any of this code. I really would rather get access to the GM API and do this the right way. Ideally I’d really like to make the Chevy Bolt in Home Assistant as easy as using a Tesla. With chrome inspector, I can see that the inner call is actually returning a very nice json structure back to the angular app. I’ve sent an email to the GM developer program to try to get real access, thus far, black hole.
Lots of Caveats on this code. That OnStar link and the My Chevrolet site are sometimes flakey, don’t know why, so running something like this on a busy loop probably is not a thing you want to do. For about 2 hours last night I just got “there is no OnStar account associated with this vehicle”, which then magically went away. I’d honestly probably not run it more than hourly. I made no claims about the integrity of things like this.
Once you see the thing working, it can be run headless by uncommenting line 18. Then it could be run on any Linux system, even one without graphics.
Again, this is one of the more rediculous pieces of code I’ve ever written. It is definitely a “currently seems to work for me” state, and don’t expect it be robust. I make no claims about whether or not it might damage anything in the process, though if logging into a website damages your car, GM has bigger issues.