diff --git a/ocpp/charge_point.py b/ocpp/charge_point.py index 78f56549e..5e31f3fe7 100644 --- a/ocpp/charge_point.py +++ b/ocpp/charge_point.py @@ -7,7 +7,7 @@ from dataclasses import asdict from typing import Dict, List, Union -from ocpp.exceptions import NotSupportedError, OCPPError +from ocpp.exceptions import NotImplementedError, NotSupportedError, OCPPError from ocpp.messages import Call, MessageType, unpack, validate_payload from ocpp.routing import create_route_map @@ -79,6 +79,39 @@ def remove_nones(data: Union[List, Dict]) -> Union[List, Dict]: return data +def _raise_key_error(action, version): + """ + Checks whether a keyerror returned by _handle_call + is supported by the OCPP version or is simply + not implemented by the server/client and raises + the appropriate error. + """ + + from ocpp.v16.enums import Action as v16_Action + from ocpp.v201.enums import Action as v201_Action + + if version == "1.6": + if hasattr(v16_Action, action): + raise NotImplementedError( + details={"cause": f"No handler for {action} registered."} + ) + else: + raise NotSupportedError( + details={"cause": f"{action} not supported by OCPP{version}."} + ) + elif version in ["2.0", "2.0.1"]: + if hasattr(v201_Action, action): + raise NotImplementedError( + details={"cause": f"No handler for {action} registered."} + ) + else: + raise NotSupportedError( + details={"cause": f"{action} not supported by OCPP{version}."} + ) + + return + + class ChargePoint: """ Base Element containing all the necessary OCPP1.6J messages for messages @@ -165,7 +198,8 @@ async def _handle_call(self, msg): First the '_on_action' hook is executed and its response is returned to the client. If there is no '_on_action' hook for Action in the message - a CallError with a NotSupportedError is returned. + a CallError with a NotImplementedError is returned. If the Action is + not supported by the OCPP version a NotSupportedError is returned. Next the '_after_action' hook is executed. @@ -173,9 +207,8 @@ async def _handle_call(self, msg): try: handlers = self.route_map[msg.action] except KeyError: - raise NotSupportedError( - details={"cause": f"No handler for {msg.action} registered."} - ) + _raise_key_error(msg.action, self._ocpp_version) + return if not handlers.get("_skip_schema_validation", False): validate_payload(msg, self._ocpp_version) @@ -190,9 +223,7 @@ async def _handle_call(self, msg): try: handler = handlers["_on_action"] except KeyError: - raise NotSupportedError( - details={"cause": f"No handler for {msg.action} registered."} - ) + _raise_key_error(msg.action, self._ocpp_version) try: response = handler(**snake_case_payload) diff --git a/ocpp/exceptions.py b/ocpp/exceptions.py index 22c693b40..5afb23e76 100644 --- a/ocpp/exceptions.py +++ b/ocpp/exceptions.py @@ -35,14 +35,14 @@ def __str__(self): class NotImplementedError(OCPPError): code = "NotImplemented" - default_description = "Requested Action is not known by receiver" + default_description = ( + "Request Action is recognized but not supported by the receiver" + ) class NotSupportedError(OCPPError): code = "NotSupported" - default_description = ( - "Request Action is recognized but not supported by " "the receiver" - ) + default_description = "Requested Action is not known by receiver" class InternalError(OCPPError): diff --git a/tests/v16/conftest.py b/tests/v16/conftest.py index d747bf420..a8dfc365f 100644 --- a/tests/v16/conftest.py +++ b/tests/v16/conftest.py @@ -17,6 +17,11 @@ def heartbeat_call(): return Call(unique_id=1, action=Action.Heartbeat, payload={}).to_json() +@pytest.fixture +def not_supported_call(): + return Call(unique_id=1, action="InvalidAction", payload={}).to_json() + + @pytest.fixture def boot_notification_call(): return Call( diff --git a/tests/v16/test_v16_charge_point.py b/tests/v16/test_v16_charge_point.py index 5471e1618..9d07acf23 100644 --- a/tests/v16/test_v16_charge_point.py +++ b/tests/v16/test_v16_charge_point.py @@ -108,6 +108,43 @@ def on_boot_notification(**kwargs): # noqa ) +@pytest.mark.asyncio +async def test_route_message_not_supported(base_central_system, not_supported_call): + """ + Test that a CALLERROR is sent back, reporting that it is + not supported by OCPP version. + + """ + + @on(Action.BootNotification) + def on_boot_notification(**kwargs): # noqa + assert kwargs["firmware_version"] == "#1:3.4.0-2990#N:217H;1.0-223" + + return call_result.BootNotificationPayload( + current_time="2018-05-29T17:37:05.495259", + interval=350, + # 'Yolo' is not a valid value for for field status. + status="Yolo", + ) + + setattr(base_central_system, "on_boot_notification", on_boot_notification) + base_central_system.route_map = create_route_map(base_central_system) + + await base_central_system.route_message(not_supported_call) + base_central_system._connection.send.assert_called_once_with( + json.dumps( + [ + 4, + 1, + "NotSupported", + "Requested Action is not known by receiver", + {"cause": "InvalidAction not supported by OCPP1.6."}, + ], + separators=(",", ":"), + ) + ) + + @pytest.mark.asyncio async def test_route_message_with_no_route(base_central_system, heartbeat_call): """ @@ -124,7 +161,7 @@ async def test_route_message_with_no_route(base_central_system, heartbeat_call): [ 4, 1, - "NotSupported", + "NotImplemented", "Request Action is recognized but not supported by the receiver", {"cause": "No handler for Heartbeat registered."}, ], diff --git a/tests/v20/test_v20_charge_point.py b/tests/v20/test_v20_charge_point.py index 9226b8ba4..bb0a04aa2 100644 --- a/tests/v20/test_v20_charge_point.py +++ b/tests/v20/test_v20_charge_point.py @@ -76,7 +76,7 @@ async def test_route_message_with_no_route(base_central_system, heartbeat_call): [ 4, 1, - "NotSupported", + "NotImplemented", "Request Action is recognized but not supported by the receiver", {"cause": "No handler for Heartbeat registered."}, ], diff --git a/tests/v201/test_v201_charge_point.py b/tests/v201/test_v201_charge_point.py index 8624c90ca..ddbd33808 100644 --- a/tests/v201/test_v201_charge_point.py +++ b/tests/v201/test_v201_charge_point.py @@ -76,7 +76,7 @@ async def test_route_message_with_no_route(base_central_system, heartbeat_call): [ 4, 1, - "NotSupported", + "NotImplemented", "Request Action is recognized but not supported by the receiver", {"cause": "No handler for Heartbeat registered."}, ],