Skip to content

Commit

Permalink
Allow to provide JSON schemas manually
Browse files Browse the repository at this point in the history
The OCA doesn't allow to share the JSON schemas for OCPP 2.1 outside
it's members. Thus this library can't include these schemas.

This commit introduces the `SchemaValidator`: a structure that loads
schemas located at inprovide folder and validates payload against those schemas.
The file names of the schema must follow the format '<action>Request' or
'<action>Response'. E.g.: "HeartbeatRequest" or
"BootNotificationResponse".

The file names for the schemas of OCPP 1.6 and OCPP 2.0 have been
adjusted to follow this pattern.

Users relying on `ocpp.v16`, `ocpp.v20` or `ocpp.v201` shouldn't be affected
by introduction of `SchemaValidator`. These modules create a default instance of `Validator`
to include the right set of schemas.

Users of `ocpp.v21` can create a custom validator and pass it to the
construct of `ocpp.v21.ChargePoint`. See also the two examples in
`examples/v21/`.

Fixes: #453
  • Loading branch information
OrangeTux committed Jul 21, 2023
1 parent 498c054 commit cbe1d70
Show file tree
Hide file tree
Showing 194 changed files with 3,720 additions and 281 deletions.
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ help:
@echo " If no version is provided, poetry outputs the current project version"
@echo " test run all the tests and linting"
@echo " update updates the dependencies in poetry.lock"
@echo " v21-central-system-example Run the example implementing an OCPP 2.1 central system.
@echo " v21-charge-point-example Run the example implementing an OCPP 2.1 charger.
@echo " update updates the dependencies in poetry.lock"
@echo ""
@echo "Check the Makefile to know exactly what each target is doing."

Expand All @@ -31,7 +34,7 @@ update: .install-poetry
poetry update

install: .install-poetry
poetry install
poetry install --dev

docs: .install-poetry
poetry run sphinx-build -b html docs/source docs/build
Expand All @@ -55,3 +58,9 @@ release: .install-poetry

deploy: update tests
poetry publish --build

v21-central-system-example:
PYTHONPATH=ocpp:$$PYTHONPATH poetry run python examples/v21/central_system.py

v21-charge-point-example:
PYTHONPATH=ocpp:$$PYTHONPATH poetry run python examples/v21/charge_point.py
58 changes: 58 additions & 0 deletions examples/v21/central_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import asyncio
import logging
import pathlib
from datetime import datetime, timezone

try:
import websockets
except ModuleNotFoundError:
print("This example relies on the 'websockets' package.")
print("Please install it by running: ")
print()
print(" $ pip install websockets")
import sys

sys.exit(1)

from ocpp.messages import SchemaValidator
from ocpp.routing import on
from ocpp.v21 import ChargePoint as cp
from ocpp.v21 import call_result
from ocpp.v21.enums import RegistrationStatus

logging.basicConfig(level=logging.INFO)

# The ocpp package doesn't come with the JSON schemas for OCPP 2.1.
# See https://github.com/mobilityhouse/ocpp/issues/458 for more details.
schemas_dir = str(pathlib.Path(__file__).parent.joinpath("schemas").resolve())
validator = SchemaValidator(schemas_dir)


class ChargePoint(cp):
@on("BootNotification")
def on_boot_notification(self, reason: str, charging_station: str, **kwargs):
return call_result.BootNotification(
current_time=datetime.now(timezone.utc).isoformat(),
interval=10,
status=RegistrationStatus.accepted,
)


async def on_connect(websocket, path):
charge_point_id = path.strip("/")
cp = ChargePoint(charge_point_id, websocket, validator)

await cp.start()


async def main():
server = await websockets.serve(
on_connect, "0.0.0.0", 9000, subprotocols=["ocpp2.1"]
)

logging.info("Server Started listening to new connections...")
await server.wait_closed()


if __name__ == "__main__":
asyncio.run(main())
54 changes: 54 additions & 0 deletions examples/v21/charge_point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import asyncio
import logging
import pathlib

try:
import websockets
except ModuleNotFoundError:
print("This example relies on the 'websockets' package.")
print("Please install it by running: ")
print()
print(" $ pip install websockets")
import sys

sys.exit(1)

from ocpp.messages import SchemaValidator
from ocpp.v21 import ChargePoint as cp
from ocpp.v21 import call, call_result
from ocpp.v21.datatypes import ChargingStation
from ocpp.v21.enums import BootReason, RegistrationStatus

logging.basicConfig(level=logging.INFO)

schemas_dir = pathlib.Path(__file__).parent.joinpath("schemas").resolve()
validator = SchemaValidator(str(schemas_dir))


class ChargePoint(cp):
async def send_boot_notification(self):
request = call.BootNotification(
reason=BootReason.power_up,
charging_station=ChargingStation(
model="Virtual Charge Point",
vendor_name="y",
),
)

response: call_result.BootNotification = await self.call(request)

if response.status == RegistrationStatus.accepted:
print("Connected to central system.")


async def main():
async with websockets.connect(
"ws://localhost:9000/CP_1", subprotocols=["ocpp2.1"]
) as ws:
cp = ChargePoint("CP_1", ws, validator)

await asyncio.gather(cp.start(), cp.send_boot_notification())


if __name__ == "__main__":
asyncio.run(main())
105 changes: 105 additions & 0 deletions examples/v21/schemas/BootNotificationRequest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "urn:OCPP:Cp:2:2023:5:BootNotificationRequest",
"comment": "OCPP 2.1 Draft 1, Copyright Open Charge Alliance",
"definitions": {
"CustomDataType": {
"description": "This class does not get 'AdditionalProperties = false' in the schema generation, so it can be extended with arbitrary JSON properties to allow adding custom data.",
"javaType": "CustomData",
"type": "object",
"properties": {
"vendorId": {
"type": "string",
"maxLength": 255
}
},
"required": [
"vendorId"
]
},
"BootReasonEnumType": {
"javaType": "BootReasonEnum",
"type": "string",
"additionalProperties": false,
"enum": [
"ApplicationReset",
"FirmwareUpdate",
"LocalReset",
"PowerUp",
"RemoteReset",
"ScheduledReset",
"Triggered",
"Unknown",
"Watchdog"
]
},
"ChargingStationType": {
"javaType": "ChargingStation",
"type": "object",
"additionalProperties": false,
"properties": {
"customData": {
"$ref": "#/definitions/CustomDataType"
},
"serialNumber": {
"type": "string",
"maxLength": 25
},
"model": {
"type": "string",
"maxLength": 20
},
"modem": {
"$ref": "#/definitions/ModemType"
},
"vendorName": {
"type": "string",
"maxLength": 50
},
"firmwareVersion": {
"type": "string",
"maxLength": 50
}
},
"required": [
"model",
"vendorName"
]
},
"ModemType": {
"javaType": "Modem",
"type": "object",
"additionalProperties": false,
"properties": {
"customData": {
"$ref": "#/definitions/CustomDataType"
},
"iccid": {
"type": "string",
"maxLength": 20
},
"imsi": {
"type": "string",
"maxLength": 20
}
}
}
},
"type": "object",
"additionalProperties": false,
"properties": {
"customData": {
"$ref": "#/definitions/CustomDataType"
},
"chargingStation": {
"$ref": "#/definitions/ChargingStationType"
},
"reason": {
"$ref": "#/definitions/BootReasonEnumType"
}
},
"required": [
"reason",
"chargingStation"
]
}
79 changes: 79 additions & 0 deletions examples/v21/schemas/BootNotificationResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "urn:OCPP:Cp:2:2023:5:BootNotificationResponse",
"comment": "OCPP 2.1 Draft 1, Copyright Open Charge Alliance",
"definitions": {
"CustomDataType": {
"description": "This class does not get 'AdditionalProperties = false' in the schema generation, so it can be extended with arbitrary JSON properties to allow adding custom data.",
"javaType": "CustomData",
"type": "object",
"properties": {
"vendorId": {
"type": "string",
"maxLength": 255
}
},
"required": [
"vendorId"
]
},
"RegistrationStatusEnumType": {
"javaType": "RegistrationStatusEnum",
"type": "string",
"additionalProperties": false,
"enum": [
"Accepted",
"Pending",
"Rejected"
]
},
"StatusInfoType": {
"javaType": "StatusInfo",
"type": "object",
"additionalProperties": false,
"properties": {
"customData": {
"$ref": "#/definitions/CustomDataType"
},
"reasonCode": {
"type": "string",
"maxLength": 20
},
"additionalInfo": {
"type": "string",
"maxLength": 512
}
},
"required": [
"reasonCode"
]
}
},
"type": "object",
"additionalProperties": false,
"properties": {
"customData": {
"$ref": "#/definitions/CustomDataType"
},
"currentTime": {
"type": "string",
"format": "date-time"
},
"interval": {
"type": "integer",
"minimum": -2147483648.0,
"maximum": 2147483647.0
},
"status": {
"$ref": "#/definitions/RegistrationStatusEnumType"
},
"statusInfo": {
"$ref": "#/definitions/StatusInfoType"
}
},
"required": [
"currentTime",
"interval",
"status"
]
}
Loading

0 comments on commit cbe1d70

Please sign in to comment.