|
| 1 | +from datetime import datetime |
| 2 | +import json |
| 3 | +import logging |
| 4 | +from ocpp.v16 import call, ChargePoint as OcppChargepoint |
| 5 | +import websockets |
| 6 | +import asyncio |
| 7 | +from typing import Callable, Optional |
| 8 | + |
| 9 | +from control import data |
| 10 | +from control.optional_data import OptionalProtocol |
| 11 | +from modules.common.fault_state import FaultState |
| 12 | + |
| 13 | + |
| 14 | +log = logging.getLogger(__name__) |
| 15 | + |
| 16 | + |
| 17 | +class OcppMixin: |
| 18 | + def _get_formatted_time(self: OptionalProtocol) -> str: |
| 19 | + return datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") |
| 20 | + |
| 21 | + def _process_call(self: OptionalProtocol, |
| 22 | + chargebox_id: str, |
| 23 | + fault_state: FaultState, |
| 24 | + func: Callable) -> Optional[websockets.WebSocketClientProtocol]: |
| 25 | + async def make_call() -> websockets.WebSocketClientProtocol: |
| 26 | + async with websockets.connect(self.data.ocpp.url+chargebox_id, |
| 27 | + subprotocols=[self.data.ocpp.version]) as ws: |
| 28 | + try: |
| 29 | + cp = OcppChargepoint(chargebox_id, ws, 2) |
| 30 | + await cp.call(func) |
| 31 | + except asyncio.exceptions.TimeoutError: |
| 32 | + # log.exception("Erwarteter TimeOut StartTransaction") |
| 33 | + pass |
| 34 | + return ws |
| 35 | + try: |
| 36 | + if self.data.ocpp.active and chargebox_id: |
| 37 | + return asyncio.run(make_call()) |
| 38 | + except websockets.exceptions.InvalidStatusCode: |
| 39 | + fault_state.warning(f"Chargebox ID {chargebox_id} konnte nicht im OCPP-Backend gefunden werden oder " |
| 40 | + "URL des Backends ist falsch.") |
| 41 | + return None |
| 42 | + |
| 43 | + def boot_notification(self: OptionalProtocol, |
| 44 | + chargebox_id: str, |
| 45 | + fault_state: FaultState, |
| 46 | + model: str, |
| 47 | + serial_number: str) -> Optional[int]: |
| 48 | + try: |
| 49 | + self._process_call(chargebox_id, fault_state, call.BootNotification( |
| 50 | + charge_point_model=model, |
| 51 | + charge_point_vendor="openWB", |
| 52 | + firmware_version=data.data.system_data["system"].data["version"], |
| 53 | + meter_serial_number=serial_number |
| 54 | + )) |
| 55 | + except Exception as e: |
| 56 | + fault_state.from_exception(e) |
| 57 | + |
| 58 | + def start_transaction(self: OptionalProtocol, |
| 59 | + chargebox_id: str, |
| 60 | + fault_state: FaultState, |
| 61 | + connector_id: int, |
| 62 | + id_tag: str, |
| 63 | + imported: int) -> Optional[int]: |
| 64 | + try: |
| 65 | + ws = self._process_call(chargebox_id, fault_state, call.StartTransaction( |
| 66 | + connector_id=connector_id, |
| 67 | + id_tag=id_tag if id_tag else "", |
| 68 | + meter_start=int(imported), |
| 69 | + timestamp=self._get_formatted_time() |
| 70 | + )) |
| 71 | + if ws: |
| 72 | + tansaction_id = json.loads(ws.messages[0])[2]["transactionId"] |
| 73 | + log.debug(f"Transaction ID: {tansaction_id} für Chargebox ID: {chargebox_id} mit Tag: {id_tag} und " |
| 74 | + f"Zählerstand: {imported} erhalten.") |
| 75 | + return tansaction_id |
| 76 | + except Exception as e: |
| 77 | + fault_state.from_exception(e) |
| 78 | + return None |
| 79 | + |
| 80 | + def transfer_values(self: OptionalProtocol, |
| 81 | + chargebox_id: str, |
| 82 | + fault_state: FaultState, |
| 83 | + connector_id: int, |
| 84 | + imported: int) -> None: |
| 85 | + try: |
| 86 | + self._process_call(chargebox_id, fault_state, call.MeterValues( |
| 87 | + connector_id=connector_id, |
| 88 | + meter_value=[{"timestamp": self._get_formatted_time(), |
| 89 | + "sampledValue": [ |
| 90 | + { |
| 91 | + "value": f'{int(imported)}', |
| 92 | + "context": "Sample.Periodic", |
| 93 | + "format": "Raw", |
| 94 | + "measurand": "Energy.Active.Import.Register", |
| 95 | + "unit": "Wh" |
| 96 | + }, |
| 97 | + ]}], |
| 98 | + )) |
| 99 | + log.debug(f"Zählerstand {imported} an Chargebox ID: {chargebox_id} übermittelt.") |
| 100 | + except Exception as e: |
| 101 | + fault_state.from_exception(e) |
| 102 | + |
| 103 | + def send_heart_beat(self: OptionalProtocol, chargebox_id: str, fault_state: FaultState) -> None: |
| 104 | + try: |
| 105 | + self._process_call(chargebox_id, fault_state, call.Heartbeat()) |
| 106 | + log.debug(f"Heartbeat an Chargebox ID: {chargebox_id} gesendet.") |
| 107 | + except Exception as e: |
| 108 | + fault_state.from_exception(e) |
| 109 | + |
| 110 | + def stop_transaction(self: OptionalProtocol, |
| 111 | + chargebox_id: str, |
| 112 | + fault_state: FaultState, |
| 113 | + imported: int, |
| 114 | + transaction_id: int, |
| 115 | + id_tag: str) -> None: |
| 116 | + try: |
| 117 | + self._process_call(chargebox_id, fault_state, call.StopTransaction(meter_stop=int(imported), |
| 118 | + timestamp=self._get_formatted_time(), |
| 119 | + transaction_id=transaction_id, |
| 120 | + reason="EVDisconnected", |
| 121 | + id_tag=id_tag if id_tag else "" |
| 122 | + )) |
| 123 | + log.debug(f"Transaction mit ID: {transaction_id} für Chargebox ID: {chargebox_id} mit Tag: {id_tag} und " |
| 124 | + f"Zählerstand: {imported} beendet.") |
| 125 | + except Exception as e: |
| 126 | + fault_state.from_exception(e) |
0 commit comments