Electric car chargers

After renting and returning an electric van (described in a previous post), I looked into electric charging. First I learned that there is a lot already solved with a pretty good map (openchargemap) that even includes an API with lots of information about all charging stations. And secondly apps that seem to have solved the spending optimizations by knowing where you have premium plans and reduced costs. The best one seems to be ABRP. (Disclaimer: Without a car, I haven't used it to actually route a tour.)

In a talk at MRMCD I was reminded that there is an open API about all highway resting places. OpenAPI for view the API: https://autobahn.api.bund.dev/.

For example let's look at Gruibingen, which I used more than once. It is next to the highway A8:

# download A8
curl -X 'GET' \
'https://verkehr.autobahn.de/o/autobahn/A8/services/electric_charging_station' \
-H 'accept: application/json' > A8.json

# filter for Gruibingen
jq '[.electric_charging_station.[] | select(.title|contains("Gruibingen"))]' A8.json > A8_gruibingen.json

There are two datasets in there for Gruibingen. But they look the same except for a (1) and (2). Let's compare the datasets:

$ diff <(jq '.[0]' A8_gruibingen.json) <(jq '.[1]' A8_gruibingen.json)
2c2
<   "identifier": "RUxFQ1RSSUNfQ0hBUkdJTkdfU1RBVElPTl9fODk4",
---
>   "identifier": "RUxFQ1RSSUNfQ0hBUkdJTkdfU1RBVElPTl9fODk3",
10c10
<   "title": "A8 | Ulm | Raststätte Gruibingen Süd (2)",
---
>   "title": "A8 | Ulm | Raststätte Gruibingen Süd (1)",
16c16
<     "A8 | Ulm | Raststätte Gruibingen Süd (2)",
---
>     "A8 | Ulm | Raststätte Gruibingen Süd (1)",

So both entries actually share the same location and both have 3 chargers. Ionity has 6 chargers there, which I verified on openchargemap. Seems like these 2x3 for Ionity. Because the other charger (the one from E.On) has only 2 stations/bays. The geo coordinate in the dataset for both chargers is 48.605276,9.632882. And the one in openchargemap for the Ionity charger is 48.60532,9.632618 and the one for E.On is 48.6059,9.6317. Which again hints on both datasets are for Ionity. But where is the E.On charger?

After some scrolling over the data, I recognized that they misspelled Gruibingen with Grubingen:

/images/chargers_autobahn_api.png

The coordinates and the data of the wrongly spelled Gru(i)bingen matches the E.On charger: 48.605956,9.632872 and 2 stations/bays.


Let's verify a second/different resting place, i.e. Denkendorf Nord. There are 2 different types of EnBW chargers and some Ionity ones.

# filter for Denkendorf
jq '[.electric_charging_station.[] | select(.title|contains("Denkendorf"))]' A8.json > A8_denkendorf.json

Only one entry in there. And it is the slow charger from EnBW. No EnBW fast charger and no Ionity.


Another thing I explored is looking at the data from Ionity from there "API". All their charging stations: https://ionity.eu/location.json (this is used on their website for the map). I searched for Gruibingen and there are 6 stations/bays. So for Ionity only, this is a good datasource, with stations and lat/lon.

ABRP is even better. It knows how many stations are already in use.
For example ABRP view for the Ionity station at the Gruibingen resting place:
/images/chargers_abrp.png

Overall it seems like the Autobahn API needs some maintenance. If I would build something on charger station data, I would probably use the API from openchargemap. And for my next drive with an electric car I will use ABRP (Android).

Renting an electric Van

My holidays in recent years have been bikepacking holidays with hotels. Either with a longer route staying one day at a hotel or one location and cycling around there.

This summer I waited a lot at train stations. Deutsche Bahn obviously needs to improve and they plan on fixing their network over the next years. So I wanted to experiment with a solution I normally detest: using a car. I looked around what is available and rented a Citroen e-Spacetourer.

Some experiences from my week using an electic van

Driving electric is clearly the future and feels better than driving a combustion based car. I don't have a lot of electric cars used in the past, but compared to them the e-Spacetourer feels heavy and starts slowly.

The van had a camping setup, so there is not that much space for other luggage. Of course I wanted to transport my folding bike but it didn't fit in the rest of the space in the trunk. So I had to store it behind the front seats while driving and on the passenger seat while sleeping:

/images/van-brompton-backseat.png/images/van-brompton-frontseat.png

I didn't sleep that well in the van. Of course, it would have helped to go to bed early and not after midnight, but being woken up by the sun shortened the night considerably. Converting the van from drive mode to sleep mode doesn't take that long, but can be annoying in the middle of the night. One of the other things that annoyed me the whole week is the entrance height of the van. I couldn't get used to the two steps to get down from the drivers seat when leaving the car.

The whole trip is of course more expensive than using the Deutschlandticket and hotels. Renting the van costed more than a week of (cheap) hotels. And even compared to (pre booked) fast train tickets, charging the car was more expensive. For the nerd in me the whole trip was an experiment, so I tested different chargers from different companies without optimizing for the price. The pricing is quite different: between 0.49 €/kwh and 0.89 €/kwh (EnBW!). I only used adhoc charging and the app for Ionity. Because I only charged the week I had the van, I didn't subscribe to any better optimized tariffs. In hindsight, for Ionity a subscription would have saved me money.

For the 1905 km I paid 275€ overall. The overall is not absolutely correct, because the car was not full when I returned it. The final charging before returning was about 80 km before I returned the car. The 1905km and 275€ results in about 14€/100 km -- quite expensive. In detail I spent for each charging (always stopped below 85%): 20.72€, 10.05€ (EnBW), 15.06€, 18.24€, 33.05€, 29.99€, 25.53€ (Ionity), 18.31€, 20.99€ (Stadtwerke Stuttgart), 19.66€ (Stadtwerke Lindau), 24.51€, 9.62€ (E.ON), 29.69€ (Aral Pulse).

Charging Stations near the highways (at Rastplätzen) was not only with faster chargers, but a lot easier to use, because the charging spaces are not as small as the ones in the cities. The 5 meter long van should fit into all parking spaces, but it felt too risky to squeeze into the small charging parking lots in the cities. Finding a parking space in general is another annoying point why I don't miss owning a car. The best charging stations I used were from Aral Pulse, because it is drive through and wide enough for the van. The best app and user experience had Ionity -- so I used them the most (and even returned to some chargers days later).

Conclusion

Is the freedom to move around worth more money and sitting on the drivers seat? In a train I would have listened to podcasts the same as I did in the car. But driving a car is more tiring than sitting in a train. And for me driving doesn't spark joy.

So as a result: for me this wasn't worth it. But I can totally see why others like it. The experience on the other hand helped me to judge that I don't like it enough to buy a car in the near future. Maybe I will try this again with a bigger van (no conversion of the car for sleeping; enough space for my bicycle). But this kind of van is not electric yet.

Scripting local Library Website

My local library sends me an email when a book is due three days in advance. But I want this information in Homeassistant, of course without manually inserting the date there.

By using playwright I login to their website and get the first entry of the books I borrowed. The secrets (username and password) are stored in a json file and loaded at the beginning. When successfully retrieved the next return date, an upload function is called with the date. The code for this function is at the end of this post.

Code to get the next return date for my local library:

import asyncio
import datetime
import json

from playwright.async_api import async_playwright

async def get_return_dates():
    async with async_playwright() as p:
        # load username / password from a json file
        # format: {"username": "0123456", "password": "your_password"}
        secrets = json.load(open(".secrets.json"))

        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto("https://stadtbibliothek-stuttgart.de")

        # go to login mask
        await page.get_by_role("link", name="Mein Konto").click()
        # fill login form
        await page.locator("#IDENT_1").fill(secrets.get("username"))
        await page.locator("#LPASSW_1").fill(secrets.get("password"))
        # click on Anmelden button
        await page.get_by_role("button", name="Anmelden").click()
        # goto list of rentals
        await page.get_by_role("link", name="Ausleihen").click()
        # there is only one table; this could be more granular - currently this is good enough
        lines = await page.query_selector_all("tr")
        for item in lines:
            columns = await item.query_selector_all("td")
            # filter empty columns
            if len(columns):
                # second column is the date
                dt = datetime.datetime.strptime(
                    (await columns[1].inner_html()).strip(), "%d.%m.%Y"
                )
                date = str(dt.date())
                # print result
                print("next_return_date:", date))
                # upload to homeassistant
                upload(date)
                # first line with a date is all we need
                break
        else:
            # nothing found; untested - because I have no empty list here atm
            print("no return date found.")

        await browser.close()

asyncio.run(get_return_dates())

Add this trigger to your Homeassistant configuration.yaml:

trigger:

  - trigger:
      - platform: webhook
        webhook_id: !secret stadtbib
        allowed_methods:
          - POST
    unique_id: "stadtbib"
    sensor:
      - name: "next return date"
        state: "{{ trigger.json.stadtbib_next_return }}"
        device_class: date
        unique_id: "stadtbib_next_return"

And add a (unique) secret to use in the upload function.

Function references above upload the date to Homeassistant. The code needs httpx (or requests by replacing every httpx with requests).

def upload(date: datetime.date):
    import httpx

    r = httpx.post(
        f"http://IP_OF_HOMEASSISTANT:8123/api/webhook/your_unique_secret",
        json={"stadtbib_next_return": date},
    )
    assert r.status_code == 200

Calling this once a day is enough. The next return date could only change when I return the book with the earliest return date.

Of course, this pattern can be used for any other website to get the desired information into Homeassistant. Some time ago I scraped the water levels of rivers in Baden Württemberg with playwright (code). I didn't use the data over years so I stopped the Github Action that downloaded the data. But scraping the river near your home could be an interesting datapoint to upload to Homeassistant.