OCPP
Konzept
Der OCPP-Treiber macht den myGEKKO-Controller zum Central System (CSMS). Ladestationen (Charge Points) verbinden sich per WebSocket mit dem Controller (ws://<CONTROLLER>:<LISTENER_PORT>/<ChargePointId>). Unterstützt werden OCPP 1.6 und OCPP 2.0.1. Die Version wird beim Handshake über Sec-WebSocket-Protocol ausgehandelt.
PROTOCOL beginnt mit OCPP. Der Treiber baut auf der WebSocket-Basis auf.
sequenceDiagram
participant CP as Ladestation
participant GK as myGEKKO (OCPP-Server)
participant F as DDF-Formeln
CP->>GK: WebSocket-Upgrade (ocpp1.6 / ocpp2.0.1)
CP->>GK: BootNotification / Heartbeat / StatusNotification
GK->>F: *LISTENER CP (Formel)
CP->>GK: Authorize(idTag)
GK->>F: *FUNCTION AUTHORIZE
F-->>GK: CP.AUTHORIZE_STATE := 1 / -1
GK-->>CP: Accepted / Blocked
CP->>GK: StartTransaction / MeterValues / StopTransaction
GK->>F: *FUNCTION START / METER / STOP
Parameter
| Key | Beschreibung |
|---|---|
LISTENER_PORT |
Port des OCPP-Servers. Der Server wird nur von Instanz 1 registriert. |
HEARTBEAT_INTERVAL |
Heartbeat-Intervall in s (wird der Station mitgeteilt) |
SLAVESMAX |
max. Anzahl Ladepunkte, z. B. 10 |
SLAVE |
je Instanz: <ChargePointId>:<ConnectorId>. Leer = Instanz gilt für alle Ladepunkte der Station. |
AUTHENTIFICATION |
Authentifizierung (Details zu klären) |
Zuordnung von Ladepunkten
Intern führt der Treiber für jeden Ladepunkt einen Schlüssel <cpId>:<connector>. :0 steht für die Station selbst.
- Ereignisformeln (
AUTHORIZE,START,STOP,METER) laufen in jeder Instanz, derenSLAVEdem Schlüssel entspricht oder leer ist. - Listener ohne
SLAVEübernehmen die Basisinformation (cpId:0) und sind auf allen Connectors aktiv.
Blöcke
*LISTENER;ALIAS;METHOD;FORMULA
;CP;LISTEN;"<Formel bei jeder eingehenden Nachricht>"
*FUNCTION;ALIAS;FORMULA
;AUTHORIZE;"<Formel bei Authorize>"
;START;"<Formel bei StartTransaction>"
;STOP;"<Formel bei StopTransaction>"
;METER;"<Formel bei MeterValues>"
Die Aliase AUTHORIZE, START, STOP und METER sind feste Namen. Der Treiber ruft genau diese Funktionen auf.
Autorisierung
Bei Authorize setzt der Treiber CP.AUTHORIZE_STATE := 0;, ruft die Formel AUTHORIZE auf und wartet (mit Wiederholungen, CP.AUTHORIZE_TRYCOUNT), bis die Formel CP.AUTHORIZE_STATE setzt:
>= 1→ Accepted (CP.AUTHORIZE_ID_ACCEPTED="<state>:<idTag>")-1→ Blocked
;AUTHORIZE;"IF ISEQUAL(CP.AUTHORIZE_IDTAG, ITEM.RFID1) THEN
CP.AUTHORIZE_STATE:=1;
ELSE
CP.AUTHORIZE_STATE:=-1;
ENDIF;"
Variablen CP.* (Alias der Listener-Funktion)
Messwerte (lesen)
| Variable | Bedeutung |
|---|---|
CP.METER_WH |
aktueller Zählerstand [Wh] |
CP.METER_START_WH / CP.METER_STOP_WH |
Zählerstand bei Start/Stopp |
CP.ENERGY_WH |
Energie der Transaktion [Wh] |
CP.ACTIVE_POWER_W |
Wirkleistung [W] |
CP.ACTIVE_POWER_OK |
Leistungswert gültig |
CP.TIME |
Ladezeit der Transaktion [s] |
CP.TIME_START / CP.TIME_STOP |
Unix-Zeit Start/Stopp |
Status (lesen)
| Variable | Bedeutung |
|---|---|
CP.ID, CP.MODEL, CP.VENDOR, CP.FIRMWARE_VERSION, CP.ICCID |
Stationsdaten (String) |
CP.STATUS |
OCPP-Status als Text |
CP.STATUSID |
Status als Zahl (0 = Available … Faulted; genaue Zuordnung zu klären) |
CP.ACTION |
letzte OCPP-Action als Zahl |
CP.CONNECTOR_ID |
Connector |
CP.ERROR_CODE |
letzter Fehlercode |
CP.LAST_HEARTBEAT |
Zeitstempel des letzten Heartbeats |
CP.MSG.action, CP.MSG.payload |
letzte Nachricht (Action-Name, JSON-Payload) |
Transaktion (lesen)
| Variable | Bedeutung |
|---|---|
CP.TRANSACTION_RUNNING |
1 bei aktiver Transaktion |
CP.TRANSACTION_ID |
Transaktions-ID |
CP.IDTAG, CP.IDTAG_START |
aktuelles/startendes ID-Tag (RFID) |
CP.TRANSACTION_INFO |
JSON der letzten Transaktion |
CP.STOPREASON |
Stoppgrund |
Autorisierung
| Variable | r/w | Bedeutung |
|---|---|---|
CP.AUTHORIZE_IDTAG |
r | angefragtes ID-Tag |
CP.AUTHORIZE_STATE |
r/w | 1 = ok, -1 = blockiert, 0 = offen |
CP.AUTHORIZE_TRYCOUNT |
r/w | Anzahl Versuche |
CP.AUTHORIZE_USER |
w | Benutzer zur Autorisierung |
CP.AUTHORIZE_ID_ACCEPTED, CP.AUTHORIZE_ID_ACCEPTED_USER |
r | zuletzt akzeptiert |
HEMS / Smart Charging
| Variable | r/w | Bedeutung |
|---|---|---|
CP.HEMS_MAX_CURRENT_A |
r/w | max. Ladestrom [A]. Schreiben sendet SetChargingProfile (TxDefaultProfile, Absolute, Einheit A, Phasen aus HEMS_PHASES_SET). Beim Lesen wird ein W-Profil mit 230 V je Phase umgerechnet. |
CP.HEMS_PHASES_SET |
r/w | 1 oder 3 Phasen für das nächste Profil. Wirksam erst mit dem nächsten Schreiben von HEMS_MAX_CURRENT_A. |
CP.HEMS_PHASES_SET_SUPPORTED |
r | -1 unbekannt, 0 nicht unterstützt, 1 unterstützt (aus GetConfiguration) |
CP.HEMS_PHASES |
r | Phasen im aktiven Profil |
CP.HEMS_MAX_POWER_W |
r | max. Ladeleistung [W] |
CP.HEMS_PROFILE_ID |
r | aktive Profil-ID |
CP.HEMS_RATE_UNIT |
r | A oder W |
CP.HEMS_CURRENT_OFFERED_A |
r | Current.Offered aus MeterValues |
Steuerung (schreiben)
| Variable | Wirkung |
|---|---|
CP.STARTSTOPTRANSACTION := '<idTag>'; |
RemoteStartTransaction mit idTag |
CP.STARTSTOPTRANSACTION := ''; |
RemoteStopTransaction der laufenden Transaktion |
CP.METER_VALUE_INTERVAL := s; |
sendet ChangeConfiguration MeterValueSampleInterval |
CP.GETCONFIGURATION := '<key>'; |
sendet GetConfiguration für den Key |
Kommandos _CMD_*
Intern stellt der Treiber die Kommandos _CMD_RemoteStartTransaction, _CMD_RemoteStopTransaction, _CMD_ChangeAvailability, _CMD_Reset, _CMD_GetConfiguration, _CMD_ChangeConfiguration, _CMD_ClearCache, _CMD_UnlockConnector, _CMD_TriggerMessage, _CMD_SetChargingProfile, _CMD_ClearChargingProfile und _CMD_GetCompositeSchedule mit JSON-Payload in eine Warteschlange. Ob und mit welcher Syntax diese direkt aus der DDF aufrufbar sind, ist zu klären. Belegt sind nur die oben genannten Schreibvariablen.
Aufbau der realen DDF „myGEKKO OCPP GENERAL“ (zusammengefasst)
*GENERAL:PROTOCOLOCPP,SLAVESMAX;10,LISTENER_PORT;8550,HEARTBEAT_INTERVAL;30,AUTHENTIFICATION,REVISION.*FUNCTION: Formeln fürAUTHORIZE,START,STOPundMETER.*LISTENER;CP;LISTEN: Die Formel bildetCP.STATUSIDauf ein Status-Item (ITEM.CPS) ab und berechnet die Leistung.*ITEM: DieWFORMULAs schreibenCP.STARTSTOPTRANSACTION,CP.HEMS_MAX_CURRENT_A,CP.HEMS_PHASES_SETundCP.METER_VALUE_INTERVAL.
Der folgende Auszug zeigt die Datei im Detail.
Realer Auszug: myGEKKO OCPP GENERAL
Datei myGEKKO.Wallbox.OCPP (DDF).OCPP GENERAL.1(0x1200000100080100).csv (gekürzt):
*GENERAL;
;DEVICE;OCPP GENERAL;
;MANUFACTURER;myGEKKO;
;TYPE;Wallbox;
;PROTOCOL;OCPP (DDF);
;MODEL_NR;8;
;VERSION_NR;1;
;ID;0x1200000100080100;
;AUTHENTIFICATION;NONE;0:NONE,1:PASSWORD,2:DEFPASSWORD,3:BASIC_AUTH;2:TOKEN;
;SLAVESMAX;10;
;LISTENER_PORT;8550;
;HEARTBEAT_INTERVAL;30;
*FUNCTION;ALIAS;FORMULA;
;AUTHORIZE;"""
IF CP.AUTHORIZE_TRYCOUNT<=0 THEN
ITEM.RFID_AUTH_EN:=0;
ENDIF;
ITEM.RFID_AUTH:=CP.AUTHORIZE_IDTAG;
CP.AUTHORIZE_STATE:=ITEM.RFID_AUTH_EN;
CP.AUTHORIZE_USER:=ITEM.RFID_AUTH_EN_USER;
""";
;STOP;"""
meter:=(CP.METER_STOP_WH - CP.METER_START_WH)/1000;
tstart:=ISO8601(CP.TIME_START,0);
tend:=ISO8601(CP.TIME_STOP,0);
energy:=DECIMAL_TO_STRING(meter);
info:=CONCAT(energy,' kWh (',tstart,'-',tend,')');
/* … JSON-Meldung zusammenbauen … */
ITEM.RFID_MESSAGE:=REPLACEWITHASCII(message,'0x22',0x22);
""";
;START;"""
STARTIDTAG:=CP.IDTAG;
/* … */
""";
*LISTENER;ALIAS;METHOD;FORMULA;
;CP;LISTEN;"""
ITEM.RFID:=CP.IDTAG;
IF CP.HEMS_PHASES==1 THEN
ITEM.PHASES:=1;
ELSE
ITEM.PHASES:=7;
ENDIF;
ITEM.CPSSTATUS:=CP.STATUSID;
SWITCH CP.STATUSID
CASE 0: ITEM.CPS:=65;
CASE 1: ITEM.CPS:=66;
CASE 2: ITEM.CPS:=67;
CASE 3: ITEM.CPS:=66;
CASE 4: ITEM.CPS:=67;
CASE 5: ITEM.CPS:=66;
DEFAULT: ITEM.CPS:=69;
ENDSWITCH;
/* … Leistungsberechnung … */
ITEM.PHASES_SET_SUPPORTED:=CP.HEMS_PHASES_SET_SUPPORTED;
ITEM.ERROR:= CP.ERROR_CODE;
DEBUG(ITEM.CONNECTOR, CP.MSG.action,CP.MSG.payload);
""";
*ITEM;ALIAS;NAME;ID;VISIBILITY;UNIT;DIR;TYPE;DEFAULT;WFORMULA;RFORMULA;POLLING;COMMENT
;CPS;CPS;0;;65= A (no car), 66= B (car connected), …;IN;
;RFID_START;RFID_START;22;;;OUT;;;"""
IF LEN(ITEM.RFID_START)>0 THEN
CP.STARTSTOPTRANSACTION:=ITEM.RFID_START;
ENDIF;
/* … */
""";
;CURRENT_SET;Current_Set;7;;A;OUT;;;"""CP.HEMS_MAX_CURRENT_A:=ITEM.CURRENT_SET;""";
;PHASES_SET;PHASES_SET;25;;;IN;;0;"""
IF ITEM.PHASES_SET ==1 THEN
CP.HEMS_PHASES_SET:=1;
CP.HEMS_MAX_CURRENT_A:=ITEM.CURRENT_SET;
ELSE IF ITEM.PHASES_SET >0 THEN
CP.HEMS_PHASES_SET:=3;
CP.HEMS_MAX_CURRENT_A:=ITEM.CURRENT_SET;
ENDIF;
""";
;INTERVAL;Intervall;20;;;IN;;5;"""CP.METER_VALUE_INTERVAL:=ITEM.INTERVAL;""";
(Gekürzt. Ausgelassene Stellen sind mit /* … */ markiert. Einige einzeilige Formeln enthalten im Original weitere Anweisungen.)
Was der Auszug zeigt:
- Jede Anweisung endet mit
;. EinCASEdarf die Anweisung in derselben Zeile tragen (CASE 0: ITEM.CPS:=65;). *FUNCTION-Formeln (AUTHORIZE,START,STOP) werden bei den gleichnamigen OCPP-Ereignissen ausgeführt.*LISTENERCPläuft bei jeder eingehenden Meldung.- Steuerung erfolgt über die
CP.*-Variablen in denWFORMULAs (CP.HEMS_MAX_CURRENT_A,CP.HEMS_PHASES_SET,CP.STARTSTOPTRANSACTION). CP.STATUSID(lautCOMMENTim Original): 0 Available, 1 Preparing (TagId Ready), 2 Preparing (EV Ready), 3 Charging, 4 SuspendedEV, 5 SuspendedEVSE, 6 Finishing, 7 Reserved, 8 Unavailable, 9 UnavailableFwUpdate, 10 Faulted, 11 UnavailableConnObj.- Im Original kommt in einer Bedingung
ANDvor (… <=0 AND CP.METER_WH > 0 THEN). Ob der ParserANDals Synonym für&&unterstützt, ist zu klären. Empfohlen wird&&.
Schritt für Schritt
LISTENER_PORTfestlegen, auf dem die Stationen den Controller erreichen.SLAVESMAX= Anzahl Ladepunkte.SLAVEje Instanz als<cpId>:<connector>(oder leer für „alle“).*LISTENER;CP;LISTENmit einer Formel, die Status und Messwerte in Items kopiert.*FUNCTION AUTHORIZEmussCP.AUTHORIZE_STATEsetzen, sonst läuft die Autorisierung in Wiederholungen.- Optional
START/STOP/METERfür Abrechnung und Protokoll. - Steuer-Items mit
WFORMULAauf dieCP.*-Schreibvariablen. - In der Station die Central-System-URL
ws://<CONTROLLER_IP>:<PORT>/<cpId>eintragen.
Fallstricke
- Phasenumschaltung wirkt erst beim nächsten Schreiben von
HEMS_MAX_CURRENT_A. HEMS_PHASES_SET_SUPPORTED = -1bedeutet, dass GetConfiguration noch nicht ausgewertet wurde.- Ohne
AUTHORIZE-Formel werden RFID-Anfragen nicht beantwortet. - Nur Instanz 1 öffnet den Port. Mehrere OCPP-DDF auf demselben Port kollidieren.
Checkliste
- [ ]
LISTENER_PORT,HEARTBEAT_INTERVAL,SLAVESMAX - [ ]
*LISTENER;CP;LISTENvorhanden - [ ]
AUTHORIZEsetztCP.AUTHORIZE_STATE - [ ] Schreib-Items verwenden belegte
CP.*-Variablen - [ ]
SLAVE-Format<cpId>:<connector>