Skip to content

Commit cbe1d70

Browse files
committed
Allow to provide JSON schemas manually
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
1 parent 498c054 commit cbe1d70

File tree

194 files changed

+3720
-281
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+3720
-281
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ help:
2020
@echo " If no version is provided, poetry outputs the current project version"
2121
@echo " test run all the tests and linting"
2222
@echo " update updates the dependencies in poetry.lock"
23+
@echo " v21-central-system-example Run the example implementing an OCPP 2.1 central system.
24+
@echo " v21-charge-point-example Run the example implementing an OCPP 2.1 charger.
25+
@echo " update updates the dependencies in poetry.lock"
2326
@echo ""
2427
@echo "Check the Makefile to know exactly what each target is doing."
2528

@@ -31,7 +34,7 @@ update: .install-poetry
3134
poetry update
3235

3336
install: .install-poetry
34-
poetry install
37+
poetry install --dev
3538

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

5659
deploy: update tests
5760
poetry publish --build
61+
62+
v21-central-system-example:
63+
PYTHONPATH=ocpp:$$PYTHONPATH poetry run python examples/v21/central_system.py
64+
65+
v21-charge-point-example:
66+
PYTHONPATH=ocpp:$$PYTHONPATH poetry run python examples/v21/charge_point.py

examples/v21/central_system.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import asyncio
2+
import logging
3+
import pathlib
4+
from datetime import datetime, timezone
5+
6+
try:
7+
import websockets
8+
except ModuleNotFoundError:
9+
print("This example relies on the 'websockets' package.")
10+
print("Please install it by running: ")
11+
print()
12+
print(" $ pip install websockets")
13+
import sys
14+
15+
sys.exit(1)
16+
17+
from ocpp.messages import SchemaValidator
18+
from ocpp.routing import on
19+
from ocpp.v21 import ChargePoint as cp
20+
from ocpp.v21 import call_result
21+
from ocpp.v21.enums import RegistrationStatus
22+
23+
logging.basicConfig(level=logging.INFO)
24+
25+
# The ocpp package doesn't come with the JSON schemas for OCPP 2.1.
26+
# See https://github.com/mobilityhouse/ocpp/issues/458 for more details.
27+
schemas_dir = str(pathlib.Path(__file__).parent.joinpath("schemas").resolve())
28+
validator = SchemaValidator(schemas_dir)
29+
30+
31+
class ChargePoint(cp):
32+
@on("BootNotification")
33+
def on_boot_notification(self, reason: str, charging_station: str, **kwargs):
34+
return call_result.BootNotification(
35+
current_time=datetime.now(timezone.utc).isoformat(),
36+
interval=10,
37+
status=RegistrationStatus.accepted,
38+
)
39+
40+
41+
async def on_connect(websocket, path):
42+
charge_point_id = path.strip("/")
43+
cp = ChargePoint(charge_point_id, websocket, validator)
44+
45+
await cp.start()
46+
47+
48+
async def main():
49+
server = await websockets.serve(
50+
on_connect, "0.0.0.0", 9000, subprotocols=["ocpp2.1"]
51+
)
52+
53+
logging.info("Server Started listening to new connections...")
54+
await server.wait_closed()
55+
56+
57+
if __name__ == "__main__":
58+
asyncio.run(main())

examples/v21/charge_point.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import asyncio
2+
import logging
3+
import pathlib
4+
5+
try:
6+
import websockets
7+
except ModuleNotFoundError:
8+
print("This example relies on the 'websockets' package.")
9+
print("Please install it by running: ")
10+
print()
11+
print(" $ pip install websockets")
12+
import sys
13+
14+
sys.exit(1)
15+
16+
from ocpp.messages import SchemaValidator
17+
from ocpp.v21 import ChargePoint as cp
18+
from ocpp.v21 import call, call_result
19+
from ocpp.v21.datatypes import ChargingStation
20+
from ocpp.v21.enums import BootReason, RegistrationStatus
21+
22+
logging.basicConfig(level=logging.INFO)
23+
24+
schemas_dir = pathlib.Path(__file__).parent.joinpath("schemas").resolve()
25+
validator = SchemaValidator(str(schemas_dir))
26+
27+
28+
class ChargePoint(cp):
29+
async def send_boot_notification(self):
30+
request = call.BootNotification(
31+
reason=BootReason.power_up,
32+
charging_station=ChargingStation(
33+
model="Virtual Charge Point",
34+
vendor_name="y",
35+
),
36+
)
37+
38+
response: call_result.BootNotification = await self.call(request)
39+
40+
if response.status == RegistrationStatus.accepted:
41+
print("Connected to central system.")
42+
43+
44+
async def main():
45+
async with websockets.connect(
46+
"ws://localhost:9000/CP_1", subprotocols=["ocpp2.1"]
47+
) as ws:
48+
cp = ChargePoint("CP_1", ws, validator)
49+
50+
await asyncio.gather(cp.start(), cp.send_boot_notification())
51+
52+
53+
if __name__ == "__main__":
54+
asyncio.run(main())
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"$id": "urn:OCPP:Cp:2:2023:5:BootNotificationRequest",
4+
"comment": "OCPP 2.1 Draft 1, Copyright Open Charge Alliance",
5+
"definitions": {
6+
"CustomDataType": {
7+
"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.",
8+
"javaType": "CustomData",
9+
"type": "object",
10+
"properties": {
11+
"vendorId": {
12+
"type": "string",
13+
"maxLength": 255
14+
}
15+
},
16+
"required": [
17+
"vendorId"
18+
]
19+
},
20+
"BootReasonEnumType": {
21+
"javaType": "BootReasonEnum",
22+
"type": "string",
23+
"additionalProperties": false,
24+
"enum": [
25+
"ApplicationReset",
26+
"FirmwareUpdate",
27+
"LocalReset",
28+
"PowerUp",
29+
"RemoteReset",
30+
"ScheduledReset",
31+
"Triggered",
32+
"Unknown",
33+
"Watchdog"
34+
]
35+
},
36+
"ChargingStationType": {
37+
"javaType": "ChargingStation",
38+
"type": "object",
39+
"additionalProperties": false,
40+
"properties": {
41+
"customData": {
42+
"$ref": "#/definitions/CustomDataType"
43+
},
44+
"serialNumber": {
45+
"type": "string",
46+
"maxLength": 25
47+
},
48+
"model": {
49+
"type": "string",
50+
"maxLength": 20
51+
},
52+
"modem": {
53+
"$ref": "#/definitions/ModemType"
54+
},
55+
"vendorName": {
56+
"type": "string",
57+
"maxLength": 50
58+
},
59+
"firmwareVersion": {
60+
"type": "string",
61+
"maxLength": 50
62+
}
63+
},
64+
"required": [
65+
"model",
66+
"vendorName"
67+
]
68+
},
69+
"ModemType": {
70+
"javaType": "Modem",
71+
"type": "object",
72+
"additionalProperties": false,
73+
"properties": {
74+
"customData": {
75+
"$ref": "#/definitions/CustomDataType"
76+
},
77+
"iccid": {
78+
"type": "string",
79+
"maxLength": 20
80+
},
81+
"imsi": {
82+
"type": "string",
83+
"maxLength": 20
84+
}
85+
}
86+
}
87+
},
88+
"type": "object",
89+
"additionalProperties": false,
90+
"properties": {
91+
"customData": {
92+
"$ref": "#/definitions/CustomDataType"
93+
},
94+
"chargingStation": {
95+
"$ref": "#/definitions/ChargingStationType"
96+
},
97+
"reason": {
98+
"$ref": "#/definitions/BootReasonEnumType"
99+
}
100+
},
101+
"required": [
102+
"reason",
103+
"chargingStation"
104+
]
105+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-06/schema#",
3+
"$id": "urn:OCPP:Cp:2:2023:5:BootNotificationResponse",
4+
"comment": "OCPP 2.1 Draft 1, Copyright Open Charge Alliance",
5+
"definitions": {
6+
"CustomDataType": {
7+
"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.",
8+
"javaType": "CustomData",
9+
"type": "object",
10+
"properties": {
11+
"vendorId": {
12+
"type": "string",
13+
"maxLength": 255
14+
}
15+
},
16+
"required": [
17+
"vendorId"
18+
]
19+
},
20+
"RegistrationStatusEnumType": {
21+
"javaType": "RegistrationStatusEnum",
22+
"type": "string",
23+
"additionalProperties": false,
24+
"enum": [
25+
"Accepted",
26+
"Pending",
27+
"Rejected"
28+
]
29+
},
30+
"StatusInfoType": {
31+
"javaType": "StatusInfo",
32+
"type": "object",
33+
"additionalProperties": false,
34+
"properties": {
35+
"customData": {
36+
"$ref": "#/definitions/CustomDataType"
37+
},
38+
"reasonCode": {
39+
"type": "string",
40+
"maxLength": 20
41+
},
42+
"additionalInfo": {
43+
"type": "string",
44+
"maxLength": 512
45+
}
46+
},
47+
"required": [
48+
"reasonCode"
49+
]
50+
}
51+
},
52+
"type": "object",
53+
"additionalProperties": false,
54+
"properties": {
55+
"customData": {
56+
"$ref": "#/definitions/CustomDataType"
57+
},
58+
"currentTime": {
59+
"type": "string",
60+
"format": "date-time"
61+
},
62+
"interval": {
63+
"type": "integer",
64+
"minimum": -2147483648.0,
65+
"maximum": 2147483647.0
66+
},
67+
"status": {
68+
"$ref": "#/definitions/RegistrationStatusEnumType"
69+
},
70+
"statusInfo": {
71+
"$ref": "#/definitions/StatusInfoType"
72+
}
73+
},
74+
"required": [
75+
"currentTime",
76+
"interval",
77+
"status"
78+
]
79+
}

0 commit comments

Comments
 (0)