Een laadpaal slim aansturen met OCPP en Python

Een ladende EV. Foto door CHUTTERSNAP op Unsplash.

Veel laadpalen op de markt ondersteunen het Open Charge Point Protocol (OCPP). Maar, wat houdt dit protocol in? En hoe kun je het zelf gebruiken om te communiceren met je eigen laadpaal? In deze blog laat ik je zien hoe je zelf OCPP in Python kan gebruiken om met je eigen laadpaal te communiceren.

 

Wat is OCPP?

OCPP is ontwikkeld door een samenwerking door meerdere partijen, waaronder ElaadNL. Elaad omschrijft het protocol als volgt:

Het Open Charge Point Protocol (OCPP) is een protocol waarin beschreven staat hoe de communicatie tussen het oplaadpunt voor elektrische auto’s en het backofficesysteem moet plaatsvinden. – elaad.nl

Het backoffice is een term die in de EV-wereld veel wordt gebruikt om een (cloud)dienst te omschrijven die op afstand laadpalen beheert. Via OCPP-berichten kan het backoffice met een laadpaal communiceren, om bijvoorbeeld laaddata op te halen of transacties te accepteren. Dit is een belangrijke ontwikkeling geweest, aangezien andere vormen van laadpaalcommunicatie erg variëren tussen modellen of zelfs slecht gedocumenteerd zijn. Waar we vroeger dus overgelaten zouden zijn aan obscure forums om onze laadpaal aan de praat te krijgen, kunnen we nu gebruik maken van het uitgebreid gedocumenteerde en breed ondersteunde OCPP. Wat voor ons nog belangrijker is, is dat er ook OCPP-berichten zijn om in te stellen met welk vermogen er geladen mag worden. Via dit bericht kunnen we een laadprofiel instellen in de laadpaal. Dit laadprofiel geeft aan wanneer en hoeveel geladen mag worden.

Hoe zien OCPP-berichten er uit?

De berichten die binnen OCPP worden verstuurd hebben het JSON-format. Het JSON-bericht om de maximale laadpaalcapaciteit in te stellen ziet er zo uit:

{
  "connectorId": 0,
  "csChargingProfiles": {
    "chargingProfileId": 1,
    "stackLevel": 0,
    "chargingProfilePurpose": "ChargePointMaxProfile",
    "chargingProfileKind": "Absolute",
    "chargingSchedule": {
      "chargingRateUnit": "Amps",
      "chargingSchedulePeriod": [
        {
          "startPeriod": 0,
          "limit": 16
        }
      ],
      "duration": 86400
    }
  }
}

Kort gezegd sturen we hier een laadprofiel om de maximale laadpaalcapaciteit in te stellen (ChargePointMaxProfile). Dit laadprofiel is absolute en gaat daarom gelijk in en stopt niet wanneer een laadsessie eindigt. Daarnaast zetten we met dit profiel een limiet van 16 Ampère en dit limiet duurt 86400 seconden (24 uur). Dit is maar een voorbeeld van een OCPP-bericht, aangezien er nog veel meer types zijn om verschillende vormen van informatie uit te wisselen.

OCPP in Python

Om deze berichten naar je eigen laadpaal te sturen zullen we in een aantal stappen een Python script maken dat een server opzet waarmee de laadpaal kan verbinden. Dit vereist wel dat de laadpaal op hetzelfde (lokale) netwerk is aangesloten als de computer waar we deze server op gaan laten draaien.

Voor de implementatie van OCPP maken we gebruik van een gelijknamige Python package, OCPP (GitHub). Deze kunnen we makkelijk installeren met PIP:

pip3 install ocpp

Aangezien OCPP het Websocket-protocol gebruikt om een verbinding op te zetten tussen het backoffice en de laadpaal is dit ook een vereiste voor ons project. Voor Websocket is gelukkig ook een simpele package beschikbaar die ook te installeren is met PIP:

pip install websockets
Websocket server opzetten

Wanneer we OCPP en Websocket hebben geïnstalleerd kunnen we een nieuw Python script maken, ocpp_server.py dat een OCPP server opzet waar de laadpaal mee kan verbinden:

import asyncio
import websockets

# We define the main function which is the entry point of this script
async def main():
    server = await websockets.serve(
        on_connect,  # Handles an incoming connection, we will define this next
        '0.0.0.0',
        9000,  # Port on which we run the server
        subprotocols=['ocpp1.6'],  # We use OCPP 1.6 in this demo
    )
    
    await server.wait_closed()

if __name__ == '__main__':
    # Start the server
    asyncio.run(main())

Hierboven definiëren we dat we een Websocket-server opzetten die draait op poort 9000. In deze demo maken we gebruik van OCPP 1.6. OCPP 2 is al uit, maar minder breed ondersteund.

Nieuwe verbindingen afhandelen

Nu we de server hebben kunnen we de OCPP package gebruiken om te bepalen wat er gebeurt wanneer er een laadpaal verbind:

# Set the logging configuration to give some insights into what happens when running
import logging
logging.basicConfig(level=logging.DEBUG)

from datetime import datetime, timezone

import asyncio
from ocpp.routing import on
from ocpp.v16 import ChargePoint as OCPPChargePoint
from ocpp.v16 import call_result
from ocpp.v16.enums import Action, RegistrationStatus

class ChargePoint(OCPPChargePoint):
    """Defines how OCPP messages from the charge point to our server are handled."""

    @on(Action.BootNotification)
    async def on_boot_notification(self, charge_point_vendor, charge_point_model, **kwargs):
        """Handles the boot notification which the charge point uses to register itself to our server."""
        return call_result.BootNotification(
            current_time=datetime.now(timezone.utc).isoformat(),
            interval=10,
            status=RegistrationStatus.accepted
        )

# Define what happens when a charging station connects
async def on_connect(websocket, path):
    await websocket.send('Connection made successfully.')
    logging.info(f'Charge point {path} connected')
    charge_point_id = path.strip('/')
    cp = ChargePoint(charge_point_id, websocket) # Create a new OCPP ChargePoint instance
    await asyncio.gather(cp.start(), set_limit(cp, 16)) # Start the ChargePoint and set the limit, we will define this function later

Hierboven definiëren we eerst een laadpaal object. Die is nu nog wat kaal, maar kan later uitgebreid worden met extra acties voor onze server om af te handelen. Op dit moment wordt alleen de BootNotification afgehandeld. Deze ontvangen we van de laadpaal wanneer hij zichzelf registreert bij onze server en is het eerste bericht dat wordt gestuurd. Na ChargePoint definiëren we on_connect waar wordt bepaald wat er gebeurt wanneer een laadpaal probeert te verbinden met onze server. Eerst sturen we dat de connectie succesvol was, waarna we het ChargePoint object opzetten om de communicatie tussen de laadpaal en onze server te beheren. Als laatste starten we de ChargePoint met .start() zodat deze gaat luisteren naar nieuwe berichten en roepen we set_limit aan om een limiet van 16 A te zetten.

Sturen van een laadprofiel

De laatste stap is het toevoegen van de functionaliteit om een laadprofiel te sturen naar de laadpaal:

from ocpp.v16 import call
from ocpp.v16 import call_result
from ocpp.v16.enums import ChargingProfileKindType, ChargingProfilePurposeType, \
    ChargingRateUnitType

async def set_limit(cp: ChargePoint, amps: float) -> None:
    request = call.SetChargingProfile(
        connector_id=0,
        cs_charging_profiles={
            "charging_profile_id": 1,
            "stack_level": 0,
            "charging_profile_purpose": ChargingProfilePurposeType.charge_point_max_profile,
            "charging_profile_kind": ChargingProfileKindType.absolute,
            "charging_schedule": {
                "charging_rate_unit": ChargingRateUnitType.amps,
                "charging_schedule_period": [{
                    "start_period": 0,
                    "limit": amps,
                }],
                "duration": 86400
            }
        }
    )
    
    response = await cp.call(request)
    
    if not isinstance(response, call_result.SetChargingProfile):
        logging.error("Response was not a SetChargingProfile response!")
        return
    
    if response.status != "Accepted":
        logging.error("Charging profile was not accepted!")
        return
    
    logging.info("Charging profile accepted!")

Hierboven definiëren we de functie, set_limit, die op het aangemaakte ChargePoint-object een limiet zet door een laadprofiel te sturen. Dit is het laadprofiel dat we eerder hebben besproken, alleen met iets andere syntax om te voldoen aan hoe de OCPP package het verwacht. Wanneer we het bericht hebben gespecificeerd, sturen we deze naar de laadpaal met cp.call(). Met twee checks verifiëren we daarna of de laadpaal het bericht goed heeft ontvangen.

Het starten van de server

We kunnen de server nu starten door het volgende commando uit te voeren:

python3 ocpp_server.py

De server draait als het goed is nu op poort 9000 (check of je firewall dit toelaat!), maar de laadpaal zal hier nog niet automatisch mee verbinden. Eerst moeten we de laadpaal vertellen waar onze server draait. Hiervoor hebben we het lokale IP-adres van de computer waar de server op draait nodig. Deze kun je vinden met de commando’s ifconfig op macOS en Linux, of ipconfig op Windows. Het adres heeft meestal het format 192.168.x.x.

Dit IP-adres, samen met de poort waarop onze server draait, maken het Websocket adres en ziet er zo uit: ws://192.168.1.10:9000 (in het geval het IP-adres van je computer 192.168.1.10 is). De volgende stap is om dit in je laadpaal te configureren. Hoe dit precies moet verschilt per merk, maar het komt er op neer dat het backofficeadres (ook wel eMSP-adres genoemd) aangepast moet worden in de systeemconfiguratie.

Als dit is gelukt en je je laadpaal opnieuw opstart zul je als het goed is in je server logs zien dat je laadpaal verbind en het laadprofiel wordt gestuurd. Wat mij betreft een moment om een biertje te pakken en dit te vieren.

Conclusie

In deze demo hebben we een OCPP-server gebouwd om te communiceren met een laadpaal. De functionaliteit van de server is nog niet heel veel soeps, maar aangezien we de volledige kracht van Python tot onze beschikking hebben is het makkelijk om deze uit te breiden. Denk bijvoorbeeld aan het ophalen van energieprijzen om alleen te laden wanneer de stroom goedkoop is, of om energiemetingen te gebruiken om alleen te laden wanneer je zonnepanelen genoeg zonnestroom genereren. De weg ligt open om je eigen Home Energy Management System (HEMS) te bouwen!

No data was found

Veel laadpalen op de markt ondersteunen het Open Charge Point Protocol (OCPP). Maar, wat houdt dit protocol in? En hoe kun je het zelf gebruiken om te communiceren met je eigen laadpaal? In deze blog laat ik je zien hoe je zelf OCPP in Python kan gebruiken om met je eigen laadpaal te communiceren.

 

No data was found

Dit artikel delen

Bekijk ook deze artikelen

Wat is GitVersion? GitVersion is een tool die automatisch Semantic Version nummers kan genereren op basis van de Git History. Een Semantic Version is als volgt opgebouwd: MAJOR.MINOR.PATCH. Hierbij wordt...
Waterstof (H2) is het lichtste gas van de wereld. Een groot bierglas vol (0,5L) weegt 0,045 gram. Vervolgens haal je uit 650 bierglazen waterstof genoeg energie om de frituurpan één...
Het probleem en de wens Het doel van CI/CD is om zoveel mogelijk werk uit de handen van het team te nemen: het stukje CI test en valideert de code,...

Samen met ons bouwen aan een duurzame toekomst? Neem contact op!

Maak impact. Samen. Jij ook?

"*" geeft vereiste velden aan

Privacyverklaring*
Dit veld is bedoeld voor validatiedoeleinden en moet niet worden gewijzigd.

×

Welkom bij Infiniot! Wij helpen je graag verder op weg.

× Stel hier jouw vraag