Skip to content

Commit

Permalink
Merge branch 'master' into Typo-in-CostUpdated-Action
Browse files Browse the repository at this point in the history
  • Loading branch information
Jared-Newell-Mobility authored Feb 7, 2024
2 parents 879be88 + b3d08ed commit eb0cfcb
Show file tree
Hide file tree
Showing 27 changed files with 5,179 additions and 860 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @OrangeTux @tropxy
* @OrangeTux @tropxy @Jared-Newell-Mobility
3 changes: 2 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ jobs:
strategy:
matrix:
version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@master
- name: Set up Python ${{ matrix.version }}
Expand Down
52 changes: 51 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
# Change log

- [#557](https://github.com/mobilityhouse/ocpp/issues/557) OCPP 2.0.1 Wrong data type in CostUpdated total_cost
- [#564](https://github.com/mobilityhouse/ocpp/issues/564) Add support For Python 3.11 and 3.12
- [#583](https://github.com/mobilityhouse/ocpp/issues/583) OCPP v1.6/v2.0.1 deprecate dataclasses from calls and call results with the suffix 'Payload'
- [#591](https://github.com/mobilityhouse/ocpp/issues/591) Camel_to_snake_case doesn't handle v2x correctly
- [#593](https://github.com/mobilityhouse/ocpp/issues/593) Update tests to use Call and CallResult without the suffix Payload
- [#435](https://github.com/mobilityhouse/ocpp/issues/435) Typo in CostUpdated Action
- [#577](https://github.com/mobilityhouse/ocpp/issues/577) v2.0.1 AttributeType Enum Corrections

## BREAKING ##
- [#574](https://github.com/mobilityhouse/ocpp/issues/574) Remove v1.6 deprecated enum members - IMPORTANT see upgrade path [#574](https://github.com/mobilityhouse/ocpp/issues/574)

## 0.26.0 (2024-01-17)

- [#544](https://github.com/mobilityhouse/ocpp/issues/544) ocpp/charge_point.py - Pass `Call.unique_id` to the `on` and `after` routing handlers.
- [#559](https://github.com/mobilityhouse/ocpp/issues/559) Update project dependencies as of 22-12-2023
- [#447](https://github.com/mobilityhouse/ocpp/issues/447) v16, v201 - Make formatting of enums in py3.11 consistent with earlier Python versions
- [#421](https://github.com/mobilityhouse/ocpp/issues/421) Type of v16.datatypes.SampledValue.context is incorrect

## 0.25.0 (2024-01-08)

- [#366](https://github.com/mobilityhouse/ocpp/issues/366) Fix type hint of OCPP 1.6 ChangeConfiguration.value
- [#431](https://github.com/mobilityhouse/ocpp/issues/431) Attributes with 'v2x' are serialized as 'V2x', but should be serialized as 'V2X'
- [#554](https://github.com/mobilityhouse/ocpp/issues/554) OCPP 2.0.1 Edition 2 Errata 2023-12 document added
- [#548](https://github.com/mobilityhouse/ocpp/issues/548) OCPP 2.0.1 MessageInfoType attribute name correction
- [#300](https://github.com/mobilityhouse/ocpp/issues/300) OCPP 2.0.1 add reference components and variables
- [#518](https://github.com/mobilityhouse/ocpp/issues/518) OCPP 2.0.1 add additional reason codes from v1.3

## 0.24.0 (2023-12-07)


- [#539](https://github.com/mobilityhouse/ocpp/issues/539) feat: Add ChargePoint._handle_call return value. Thanks [@wafa-yah](https://github.com/wafa-yah)
- [#266](https://github.com/mobilityhouse/ocpp/issues/266) fix: Central System documentation link.
- [#516](https://github.com/mobilityhouse/ocpp/issues/516) OCPP 2.0.1 add additional security events from v1.3.
- [#537](https://github.com/mobilityhouse/ocpp/pull/537) Fix DataTransfer data types. Thanks [@mdwcrft](https://github.com/mdwcrft)

## 0.23.0 (2023-11-30)

- [#531] Feat: Add 1.6 security extension datatypes. Thanks [@proelke](https://github.com/proelke)
- [#528](https://github.com/mobilityhouse/ocpp/issues/528) v2.0.1 CertificateHashDataChainType childCertificateHashData requires the default of None.
- [#510](https://github.com/mobilityhouse/ocpp/issues/510) v2.0.1 UnitOfMeasureType - Enums missing and update docstring to allow use for variableCharacteristics.
- [#508](https://github.com/mobilityhouse/ocpp/issues/508) Exception - OccurrenceConstraintViolationError doc string correction.

## 0.22.0 (2023-11-03)

- [#493](https://github.com/mobilityhouse/ocpp/issues/493) Reduce use of NotSupportedError in favor of NotImplementedError. Thanks [drc38](@https://github.com/drc38).
- [#278](https://github.com/mobilityhouse/ocpp/pull/278) Fix types for attributes of OCPP 1.6's type `IdTagInfo`. Thanks [@chan-vince](https://github.com/chan-vince)
- [#504](https://github.com/mobilityhouse/ocpp/pull/504) Add missing tech_info attribute to v2.0.1 EventDataType. Thanks [@LokiHokie](https://github.com/LokiHokie)
- [#381](https://github.com/mobilityhouse/ocpp/issues/381) Add FormationError and OccurrenceConstraintViolationError.

## 0.21.0 (2023-10-19)

- [#492] Minor fixes _handle_call doc string - Thanks @drc38
- [#485](https://github.com/mobilityhouse/ocpp/issues/485) Update documents for 2.0.1 to lastest; removed 2.0 docs
- [#412](https://github.com/mobilityhouse/ocpp/issues/412) Add default value to 1.6 AuthorizationData datatype, id_tag_info
- [#141](https://github.com/mobilityhouse/ocpp/issues/141) Add to docs OCPP 1.6 Security White Paper Ed 2
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Central system

The code snippet below creates a simple OCPP 2.0 central system which is able
to handle BootNotification calls. You can find a detailed explanation of the
code in the `Central System documentation_`.
code in the `Central System documentation`_.


.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "Auke Willem Oosterhoff"

# The full version, including alpha/beta/rc tags
release = "0.20.0"
release = "0.26.0"


# -- General configuration ---------------------------------------------------
Expand Down
Binary file added docs/v201/OCPP-2.0.1_edition2_errata_2023-12.pdf
Binary file not shown.
2 changes: 0 additions & 2 deletions examples/v201/central_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ async def on_connect(websocket, path):
try:
requested_protocols = websocket.request_headers["Sec-WebSocket-Protocol"]
except KeyError:
logging.info("Client hasn't requested any Subprotocol. " "Closing Connection")
return await websocket.close()
logging.error("Client hasn't requested any Subprotocol. Closing Connection")
return await websocket.close()
if websocket.subprotocol:
Expand Down
78 changes: 65 additions & 13 deletions ocpp/charge_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +25,7 @@ def camel_to_snake_case(data):
if isinstance(data, dict):
snake_case_dict = {}
for key, value in data.items():
key = key.replace("V2X", "_v2x")
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", key)
key = re.sub("([a-z0-9])([A-Z])(?=\\S)", r"\1_\2", s1).lower()

Expand All @@ -44,7 +45,7 @@ def camel_to_snake_case(data):

def snake_to_camel_case(data):
"""
Convert all keys of a all dictionaries inside given argument from
Convert all keys of all dictionaries inside given argument from
snake_case to camelCase.
Inspired by: https://stackoverflow.com/a/19053800/1073222
Expand All @@ -53,6 +54,7 @@ def snake_to_camel_case(data):
camel_case_dict = {}
for key, value in data.items():
key = key.replace("soc", "SoC")
key = key.replace("_v2x", "V2X")
components = key.split("_")
key = components[0] + "".join(x[:1].upper() + x[1:] for x in components[1:])
camel_case_dict[key] = snake_to_camel_case(value)
Expand All @@ -79,6 +81,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
Expand Down Expand Up @@ -165,17 +200,17 @@ 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 NotImplemtendError 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.
"""
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)
Expand All @@ -190,12 +225,16 @@ 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)
handler_signature = inspect.signature(handler)
call_unique_id_required = "call_unique_id" in handler_signature.parameters
try:
response = handler(**snake_case_payload)
# call_unique_id should be passed as kwarg only if is defined explicitly
# in the handler signature
if call_unique_id_required:
response = handler(**snake_case_payload, call_unique_id=msg.unique_id)
else:
response = handler(**snake_case_payload)
if inspect.isawaitable(response):
response = await response
except Exception as e:
Expand Down Expand Up @@ -227,15 +266,23 @@ async def _handle_call(self, msg):

try:
handler = handlers["_after_action"]
handler_signature = inspect.signature(handler)
call_unique_id_required = "call_unique_id" in handler_signature.parameters
# call_unique_id should be passed as kwarg only if is defined explicitly
# in the handler signature
if call_unique_id_required:
response = handler(**snake_case_payload, call_unique_id=msg.unique_id)
else:
response = handler(**snake_case_payload)
# Create task to avoid blocking when making a call inside the
# after handler
response = handler(**snake_case_payload)
if inspect.isawaitable(response):
asyncio.ensure_future(response)
except KeyError:
# '_on_after' hooks are not required. Therefore ignore exception
# when no '_on_after' hook is installed.
pass
return response

async def call(self, payload, suppress=True, unique_id=None):
"""
Expand Down Expand Up @@ -265,9 +312,14 @@ async def call(self, payload, suppress=True, unique_id=None):
unique_id if unique_id is not None else str(self._unique_id_generator())
)

action_name = payload.__class__.__name__
# Due to deprecated call and callresults, remove in the future.
if "Payload" in payload.__class__.__name__:
action_name = payload.__class__.__name__[:-7]

call = Call(
unique_id=unique_id,
action=payload.__class__.__name__[:-7],
action=action_name,
payload=remove_nones(camel_case_payload),
)

Expand Down
55 changes: 49 additions & 6 deletions ocpp/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,24 @@ def __eq__(self, other):

def __repr__(self):
return (
f"<{self.__class__.__name__} - description={self.description},"
f"<{self.__class__.__name__} - description={self.description}, "
f" details={self.details}>"
)

def __str__(self):
return f"{self.__class__.__name__}: {self.description}," f" {self.details}"
return f"{self.__class__.__name__}: {self.description}, " f" {self.details}"


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):
Expand All @@ -68,12 +68,31 @@ class SecurityError(OCPPError):


class FormatViolationError(OCPPError):
"""
Not strict OCPP 1.6 - see FormationViolationError
Valid OCPP 2.0.1
"""

code = "FormatViolation"
default_description = (
"Payload for Action is syntactically incorrect or " "structure for Action"
)


class FormationViolationError(OCPPError):
"""
To allow for strict OCPP 1.6 compliance
5. Known issues that will not be fixed
5.2. Page 14, par 4.2.3. CallError: incorrect name in enum: FormationViolation
Incorrect name in enum: FormationViolation
"""

code = "FormationViolation"
default_description = (
"Payload for Action is syntactically incorrect or structure for Action"
)


class PropertyConstraintViolationError(OCPPError):
code = "PropertyConstraintViolation"
default_description = (
Expand All @@ -83,6 +102,15 @@ class PropertyConstraintViolationError(OCPPError):


class OccurenceConstraintViolationError(OCPPError):
"""
To allow for strict OCPP 1.6 compliance
ocpp-j-1.6-errata-sheet.pdf
5. Known issues that will not be fixed
5.1. Page 14, par 4.2.3: CallError: Typo in enum
Typo in enum: OccurenceConstraintViolation
Valid in 2.0.1
"""

code = "OccurenceConstraintViolation"
default_description = (
"Payload for Action is syntactically correct but "
Expand All @@ -91,6 +119,21 @@ class OccurenceConstraintViolationError(OCPPError):
)


class OccurrenceConstraintViolationError(OCPPError):
"""
Not strict OCPP 1.6 - see OccurenceConstraintViolationError
Not valid OCPP 2.0.1
Valid in OCPP 2.1
"""

code = "OccurrenceConstraintViolation"
default_description = (
"Payload for Action is syntactically correct but "
"at least one of the fields violates occurence "
"constraints"
)


class TypeConstraintViolationError(OCPPError):
code = "TypeConstraintViolation"
default_description = (
Expand Down
4 changes: 2 additions & 2 deletions ocpp/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,8 @@ def to_exception(self):
)

raise UnknownCallErrorCodeError(
"Error code '%s' is not defined by the" " OCPP specification",
self.error_code,
f"Error code '{self.error_code}' is not defined by the"
" OCPP specification"
)

def __repr__(self):
Expand Down
Loading

0 comments on commit eb0cfcb

Please sign in to comment.