-
Notifications
You must be signed in to change notification settings - Fork 107
OCPP #1893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
OCPP #1893
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ec3f403
OCPP
LKuemmel c8e76e0
fixes
LKuemmel b00235a
fix mac
LKuemmel 6d7ff79
logging
LKuemmel d5cb16d
flake8
LKuemmel 8486739
fix test
LKuemmel ddcdbe0
clean up imports
LKuemmel 5a7e63f
typo, json config
LKuemmel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| from datetime import datetime | ||
| import json | ||
| import logging | ||
| from ocpp.v16 import call, ChargePoint as OcppChargepoint | ||
| import websockets | ||
| import asyncio | ||
| from typing import Callable, Optional | ||
|
|
||
| from control import data | ||
| from control.optional_data import OptionalProtocol | ||
| from modules.common.fault_state import FaultState | ||
|
|
||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class OcppMixin: | ||
| def _get_formatted_time(self: OptionalProtocol) -> str: | ||
| return datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") | ||
|
|
||
| def _process_call(self: OptionalProtocol, | ||
| chargebox_id: str, | ||
| fault_state: FaultState, | ||
| func: Callable) -> Optional[websockets.WebSocketClientProtocol]: | ||
| async def make_call() -> websockets.WebSocketClientProtocol: | ||
| async with websockets.connect(self.data.ocpp.url+chargebox_id, | ||
| subprotocols=[self.data.ocpp.version]) as ws: | ||
| try: | ||
| cp = OcppChargepoint(chargebox_id, ws, 2) | ||
| await cp.call(func) | ||
| except asyncio.exceptions.TimeoutError: | ||
| # log.exception("Erwarteter TimeOut StartTransaction") | ||
| pass | ||
| return ws | ||
| try: | ||
| if self.data.ocpp.active and chargebox_id: | ||
| return asyncio.run(make_call()) | ||
| except websockets.exceptions.InvalidStatusCode: | ||
| fault_state.warning(f"Chargebox ID {chargebox_id} konnte nicht im OCPP-Backend gefunden werden oder " | ||
| "URL des Backends ist falsch.") | ||
| return None | ||
|
|
||
| def boot_notification(self: OptionalProtocol, | ||
| chargebox_id: str, | ||
| fault_state: FaultState, | ||
| model: str, | ||
| serial_number: str) -> Optional[int]: | ||
| try: | ||
| self._process_call(chargebox_id, fault_state, call.BootNotification( | ||
| charge_point_model=model, | ||
| charge_point_vendor="openWB", | ||
| firmware_version=data.data.system_data["system"].data["version"], | ||
| meter_serial_number=serial_number | ||
| )) | ||
| except Exception as e: | ||
| fault_state.from_exception(e) | ||
|
|
||
| def start_transaction(self: OptionalProtocol, | ||
| chargebox_id: str, | ||
| fault_state: FaultState, | ||
| connector_id: int, | ||
| id_tag: str, | ||
| imported: int) -> Optional[int]: | ||
| try: | ||
| ws = self._process_call(chargebox_id, fault_state, call.StartTransaction( | ||
| connector_id=connector_id, | ||
| id_tag=id_tag if id_tag else "", | ||
| meter_start=int(imported), | ||
| timestamp=self._get_formatted_time() | ||
| )) | ||
| if ws: | ||
| tansaction_id = json.loads(ws.messages[0])[2]["transactionId"] | ||
| log.debug(f"Transaction ID: {tansaction_id} für Chargebox ID: {chargebox_id} mit Tag: {id_tag} und " | ||
| f"Zählerstand: {imported} erhalten.") | ||
| return tansaction_id | ||
| except Exception as e: | ||
| fault_state.from_exception(e) | ||
| return None | ||
|
|
||
| def transfer_values(self: OptionalProtocol, | ||
| chargebox_id: str, | ||
| fault_state: FaultState, | ||
| connector_id: int, | ||
| imported: int) -> None: | ||
| try: | ||
| self._process_call(chargebox_id, fault_state, call.MeterValues( | ||
| connector_id=connector_id, | ||
| meter_value=[{"timestamp": self._get_formatted_time(), | ||
| "sampledValue": [ | ||
| { | ||
| "value": f'{int(imported)}', | ||
| "context": "Sample.Periodic", | ||
| "format": "Raw", | ||
| "measurand": "Energy.Active.Import.Register", | ||
| "unit": "Wh" | ||
| }, | ||
| ]}], | ||
| )) | ||
| log.debug(f"Zählerstand {imported} an Chargebox ID: {chargebox_id} übermittelt.") | ||
| except Exception as e: | ||
| fault_state.from_exception(e) | ||
|
|
||
| def send_heart_beat(self: OptionalProtocol, chargebox_id: str, fault_state: FaultState) -> None: | ||
| try: | ||
| self._process_call(chargebox_id, fault_state, call.Heartbeat()) | ||
| log.debug(f"Heartbeat an Chargebox ID: {chargebox_id} gesendet.") | ||
| except Exception as e: | ||
| fault_state.from_exception(e) | ||
|
|
||
| def stop_transaction(self: OptionalProtocol, | ||
| chargebox_id: str, | ||
| fault_state: FaultState, | ||
| imported: int, | ||
| transaction_id: int, | ||
| id_tag: str) -> None: | ||
| try: | ||
| self._process_call(chargebox_id, fault_state, call.StopTransaction(meter_stop=int(imported), | ||
| timestamp=self._get_formatted_time(), | ||
| transaction_id=transaction_id, | ||
| reason="EVDisconnected", | ||
| id_tag=id_tag if id_tag else "" | ||
| )) | ||
| log.debug(f"Transaction mit ID: {transaction_id} für Chargebox ID: {chargebox_id} mit Tag: {id_tag} und " | ||
| f"Zählerstand: {imported} beendet.") | ||
| except Exception as e: | ||
| fault_state.from_exception(e) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| from unittest.mock import Mock | ||
| import pytest | ||
|
|
||
| from control import data | ||
| from control.chargepoint.chargepoint import Chargepoint | ||
| from control.chargepoint.chargepoint_template import CpTemplate | ||
| from control.counter import Counter | ||
| from control.ev import Ev | ||
| from modules.chargepoints.mqtt.chargepoint_module import ChargepointModule | ||
| from modules.chargepoints.mqtt.config import Mqtt | ||
|
|
||
|
|
||
| @pytest.fixture() | ||
| def mock_data() -> None: | ||
| data.data_init(Mock()) | ||
| data.data.optional_data.data.ocpp.active = True | ||
| data.data.optional_data.data.ocpp.url = "ws://localhost:9000/" | ||
|
|
||
|
|
||
| def test_start_transaction(mock_data, monkeypatch): | ||
| cp = Chargepoint(1, None) | ||
| cp.data.config.ocpp_chargebox_id = "cp1" | ||
| cp.data.get.plug_state = True | ||
| cp.template = CpTemplate() | ||
| cp.chargepoint_module = ChargepointModule(Mqtt()) | ||
|
|
||
| start_transaction_mock = Mock() | ||
| monkeypatch.setattr(data.data.optional_data, "start_transaction", start_transaction_mock) | ||
| _pub_configured_ev_mock = Mock() | ||
| monkeypatch.setattr(cp, "_pub_configured_ev", _pub_configured_ev_mock) | ||
|
|
||
| cp.update([]) | ||
|
|
||
| assert start_transaction_mock.call_args == (("cp1", cp.chargepoint_module.fault_state, 1, None, 0),) | ||
|
|
||
|
|
||
| def test_stop_transaction(mock_data, monkeypatch): | ||
| cp = Chargepoint(1, None) | ||
| cp.data.config.ocpp_chargebox_id = "cp1" | ||
| cp.data.get.plug_state = False | ||
| cp.data.set.ocpp_transaction_id = 124 | ||
| cp.data.set.charging_ev_prev = 1 | ||
| cp.chargepoint_module = ChargepointModule(Mqtt()) | ||
| cp.template = CpTemplate() | ||
|
|
||
| stop_transaction_mock = Mock() | ||
| monkeypatch.setattr(data.data.optional_data, "stop_transaction", stop_transaction_mock) | ||
| get_evu_counter_mock = Mock(return_value=Mock(spec=Counter)) | ||
| monkeypatch.setattr(data.data.counter_all_data, "get_evu_counter", get_evu_counter_mock) | ||
| data.data.ev_data["ev1"] = Ev(1) | ||
|
|
||
| cp._process_charge_stop() | ||
|
|
||
| assert stop_transaction_mock.call_args == (("cp1", cp.chargepoint_module.fault_state, 0, 124, None),) | ||
|
|
||
|
|
||
| def test_send_ocpp_data(mock_data, monkeypatch): | ||
| data.data.cp_data["cp1"] = Chargepoint(1, None) | ||
| data.data.cp_data["cp1"].data.config.ocpp_chargebox_id = "cp1" | ||
| data.data.cp_data["cp1"].data.get.plug_state = True | ||
| data.data.cp_data["cp1"].chargepoint_module = ChargepointModule(Mqtt()) | ||
| data.data.cp_data["cp1"].data.get.serial_number = "123456" | ||
| transfer_values_mock = Mock() | ||
| monkeypatch.setattr(data.data.optional_data, "transfer_values", transfer_values_mock) | ||
| boot_notification_mock = Mock() | ||
| monkeypatch.setattr(data.data.optional_data, "boot_notification", boot_notification_mock) | ||
| send_heart_beat_mock = Mock() | ||
| monkeypatch.setattr(data.data.optional_data, "send_heart_beat", send_heart_beat_mock) | ||
|
|
||
| data.data.optional_data.ocpp_boot_notification_send = False | ||
|
|
||
| data.data.optional_data._transfer_meter_values() | ||
|
|
||
| boot_notification_mock.call_args == (("cp1", "mqtt", "123456"),) | ||
| send_heart_beat_mock.call_args == (("cp1",),) | ||
| transfer_values_mock.call_args == (("cp1", 1, 0),) | ||
| assert data.data.optional_data.ocpp_boot_notification_send is True | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.