ocpp icon indicating copy to clipboard operation
ocpp copied to clipboard

Add enums for OCPP 2.0

Open OrangeTux opened this issue 4 years ago • 10 comments

I've started by generating the code for the enums based on the JSON schema's. After that I had to correct the generated code slightly.

The script I've used is:

#!/bin/env/python
import json
import re
import os
from pathlib import Path
from typing import List, Dict
from functools import reduce
import operator

SCHEMAS_DIR = 'ocpp/v20/schemas/'


class EnumDefinition:
    def __init__(self, name: str, variants: List[str], comment: str = ""):
        self.name = name
        self.variants = variants
        self.comment = comment

    @staticmethod
    def from_schema_definition(name: str, definition: Dict):
        name = name[:-len('EnumType')]
        variants = [v for v in definition['enum']]

        return EnumDefinition(name, variants)

    def as_code(self):
        variants = ""
        for variant in self.variants:
            # Convert CamelCase variant to snake_case attribute.
            s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', variant)
            attribute = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
            attribute =attribute.replace('-', '_')
            variants += f"    {attribute}: \"{variant}\"\n"

        comment = ""
        if self.comment != "":
            comment = f'    """{self.comment}"""\n'

        return f"""class {self.name}:
{comment}{variants}\n"""

    def __repr__(self):
        return f'<{self.name} - {[v for v in self.variants]}>'

    def __eq__(self, other):
        return (self.name, self.variants, self.comment) == (other.name, other.variants, other.comment)

    def __hash__(self):
        return hash(self.__repr__)


def list_schemas(folder: str) -> List[str]:
    files = os.listdir(folder)
    return [f'{folder}/{f}' for f in files if f.endswith('.json')]


def load_schema(path: str) -> Dict:
    """ Load the given schema and return it as a dict. """
    with open(path, 'r', encoding='utf-8-sig') as f:
        return json.load(f)


def filter_enum_definitions(schema: Dict) -> List[Dict]:
    enum_definitions = []

    if 'definitions' not in schema:
        return enum_definitions

    for name in schema['definitions']:
        if not name.endswith('EnumType'):
            continue

        enum_definitions.append(
            EnumDefinition.from_schema_definition(name, schema['definitions'][name])
        )

    return enum_definitions


def filter_enum_properties(schema: Dict, name: str) -> List[EnumDefinition]:
    enum_definitions = []

    if 'properties' not in schema:
        return enum_definitions

    for _, property in schema['properties'].items():

        try:
            variants = property['enum']
        except KeyError:
            continue

        enum_definitions.append(
            EnumDefinition(name="Unknown", variants=variants, comment=name)
        )

    return enum_definitions


if __name__ == '__main__':
    schema_files = list_schemas(SCHEMAS_DIR)
    definitions = []

    for schema_file in schema_files:
        schema = load_schema(schema_file)

        definition = filter_enum_definitions(schema);
        definitions.append(definition)

        name = Path(schema_file).stem
        definition = filter_enum_properties(schema, name)
        definitions.append(definition)

    # schemas = [load_schema(s) for s in schema_files]
    # definitions = [filter_enum_properties(s) for s in schemas]

    # Flatten the list of lists to a single list with definitions.
    definitions = reduce(operator.add, definitions)


    # Remove duplicate definitions.
    unique_definitions = []
    for definition in definitions:
        if definition not in unique_definitions:
            unique_definitions.append(definition)

    # Sort definitions by name
    definitions = sorted(unique_definitions, key=lambda definition: definition.name)

    for definition in definitions:
        # print(definition)
        print(definition.as_code())
    print(len(unique_definitions))

OrangeTux avatar May 19 '20 19:05 OrangeTux

I will leave it to you if you want to modify this PR or close it and open a new one for 2.0.1

tropxy avatar May 21 '20 09:05 tropxy

OCPP 2.0 is deprecated and we should use spec and schemas from 2.0.1

I think we should have support for both versions. I agree with you that people ideally should use OCPP 2.0.1 over OCPP 2.0 if they have a choice. But people might not have that choice. Maybe the charge point they interact with supports OCPP 2.0 and not OCPP 2.0.1.

As library creators we shouldn't decide on what version someone should use. That is up the user itself.

Therefore I think we should support both versions.

OrangeTux avatar May 21 '20 10:05 OrangeTux

I understand, but the version is no longer supported by OCA and the files are no longer available to download, so I dont think it makes sense to support it...

tropxy avatar May 21 '20 11:05 tropxy

Besides there is the OCPP 2.0 and then the OCPP 2.0 errata. The errata was created to solve issues found in 2.0 and 2.0.1 was created to officially solve those issues, therefore to avoid confusion and to be in accordance with OCA I think we should just support the official released versions.

tropxy avatar May 21 '20 11:05 tropxy

just support the official released versions.

I think I agree with that. But OCPP 2.0 is an officially released version. It was released in April 2018. Here a tweet of the OCA that announces it.

Also section 1.1 OCPP Version 2.0.1 of the OCPP 2.0.1 - Part 0 Introduction mentions the that OCPP 2.0 is a different version of the protocol.

After the release of OCPP 2.0, some issues were found in OCPP 2.0. Some of these issues could not be fixed issuing errata to the specification text only, as has been done with OCPP 1.6, but required changes to the protocol’s machine-readable schema definition files that cannot be backward compatible. To prevent confusion in the market and possible interoperability issues in the field, OCA has decided to name this version: 2.0.1. OCPP 2.0.1 contains fixes for all the known issues, to date, not only the fixes to the messages.

OrangeTux avatar May 22 '20 08:05 OrangeTux

just support the official released versions.

I think I agree with that. But OCPP 2.0 is an officially released version. It was released in April 2018. Here a tweet of the OCA that announces it.

Also section 1.1 OCPP Version 2.0.1 of the OCPP 2.0.1 - Part 0 Introduction mentions the that OCPP 2.0 is a different version of the protocol.

After the release of OCPP 2.0, some issues were found in OCPP 2.0. Some of these issues could not be fixed issuing errata to the specification text only, as has been done with OCPP 1.6, but required changes to the protocol’s machine-readable schema definition files that cannot be backward compatible. To prevent confusion in the market and possible interoperability issues in the field, OCA has decided to name this version: 2.0.1. OCPP 2.0.1 contains fixes for all the known issues, to date, not only the fixes to the messages.

You are right and it was, in fact, an official release. What I meant is that we should support the releases that are currently supported by the OCA. If you try to download the OCPP 2.0 version, it is not possible to do it anymore from the OCA download page https://www.openchargealliance.org/downloads/

So, I dont see the point of supporting a version that dont even OCA supports anymore...

tropxy avatar May 22 '20 18:05 tropxy

So, I dont see the point of supporting a version that dont even OCA supports anymore...

I think this is exactly what this discussion boils down to. I see that different. This discussion learned me me that the OCA is pushing OCPP 2.0.1 over OCPP 2.0. But that doesn't invalidate OCPP 2.0 in my opinion. The version was created and officially released: it does exists. Even though a newer version of the protocol has been released in the mean time.

OrangeTux avatar May 23 '20 13:05 OrangeTux

I would back you up 100% on this one if OCA didnt have deleted OCPP 2.0 from their public download server and if we had already chargers/csms in the market with OCPP 2.0. However, AFAIK that is not the case. Also, if we support OCPP 2.0, then we should also support the OCPP 2.0 errata version (which btw was never publicly released).

This said, even if I am not 100% in accordance with you on this, but since we have users that have used our 2.0 lib already, rethinking all of this, I think it is better that we support at least OCPP 2.0

tropxy avatar May 24 '20 00:05 tropxy

Separate issue - why don't the enums use enum.Enum from the Python standard library? https://docs.python.org/3.8/library/enum.html#enum.Enum They provide some useful features like nice REPR's, conversion to/from values, immutability.

adamchainz avatar Jun 17 '20 15:06 adamchainz

@adamchainz Here is a discussion why this lib doesn't subclass from enum.Enum. Feel free to add your comments there.

OrangeTux avatar Jun 22 '20 09:06 OrangeTux

Closed due inactivity.

OrangeTux avatar Dec 23 '22 11:12 OrangeTux