diff --git a/MANIFEST.in b/MANIFEST.in index e73fc9dcb..40992dedb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ recursive-exclude tests * exclude requirements_dev.txt include requirements.txt recursive-include ocpp/v16/schemas *.json +recursive-include ocpp/v20/schemas *.json diff --git a/README.rst b/README.rst index 52021faae..828dbe64e 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,8 @@ OCPP ---- -Python package implementing the JSON version of the Open Charge Point Protocol (OCPP). Currently -only OCPP 1.6 is supported. +Python package implementing the JSON version of the Open Charge Point Protocol +(OCPP). Currently OCPP 1.6 and OCPP 2.0 are supported. You can find the documentation on `rtd`_. @@ -33,7 +33,8 @@ Or clone the project and install it manually using: Quick start ----------- -Below you can find examples on how to create a simple charge point as well as a charge point. +Below you can find examples on how to create a simple OCPP 2.0 centrl system as +well as an OCPP 2.0 charge point. .. note:: @@ -46,8 +47,9 @@ Below you can find examples on how to create a simple charge point as well as a Central system ~~~~~~~~~~~~~~ -The code snippet below creates a simple central system which is able to handle BootNotification -calls. You can find a detailed explaination of the code in the `Central System documentation_`. +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-block:: python @@ -57,26 +59,18 @@ calls. You can find a detailed explaination of the code in the `Central System d from datetime import datetime from ocpp.routing import on - from ocpp.v16 import ChargePoint as cp - from ocpp.v16.enums import Action, RegistrationStatus - from ocpp.v16 import call_result - + from ocpp.v20 import ChargePoint as cp + from ocpp.v20 import call_result class ChargePoint(cp): - @on(Action.BootNotification) - def on_boot_notification(self, charge_point_vendor, charge_point_model, **kwargs): + @on('BootNotification') + def on_boot_notitication(self, charging_station, reason, **kwargs): return call_result.BootNotificationPayload( current_time=datetime.utcnow().isoformat(), interval=10, - status=RegistrationStatus.accepted + status='Accepted' ) - @after(Action.BootNotification) - def after_boot_notification(self, charge_point_vendor, charge_point_model, **kwargs): - print("ChargePoint Vendor is: %s", charge_point_vendor) - print("ChargePoint Model is: %s",charge_point_model) - - async def on_connect(websocket, path): """ For every new charge point that connects, create a ChargePoint instance and start listening for messages. @@ -93,7 +87,7 @@ calls. You can find a detailed explaination of the code in the `Central System d on_connect, '0.0.0.0', 9000, - subprotocols=['ocpp1.6'] + subprotocols=['ocpp2.0'] ) await server.wait_closed() @@ -110,27 +104,30 @@ Charge point import asyncio import websockets - from ocpp.v16 import call, ChargePoint as cp - from ocpp.v16.enums import RegistrationStatus + from ocpp.v20 import call + from ocpp.v20 import ChargePoint as cp class ChargePoint(cp): + async def send_boot_notification(self): request = call.BootNotificationPayload( - charge_point_model="Optimus", - charge_point_vendor="The Mobility House" + charging_station={ + 'model': 'Wallbox XYZ', + 'vendor_name': 'anewone' + }, + reason="PowerUp" ) - response = await self.call(request) - if response.status == RegistrationStatus.accepted: + if response.status == 'Accepted': print("Connected to central system.") async def main(): async with websockets.connect( 'ws://localhost:9000/CP_1', - subprotocols=['ocpp1.6'] + subprotocols=['ocpp2.0'] ) as ws: cp = ChargePoint('CP_1', ws) diff --git a/docs/source/central_system.rst b/docs/source/central_system.rst index 72f92c913..f86e510d1 100644 --- a/docs/source/central_system.rst +++ b/docs/source/central_system.rst @@ -75,6 +75,13 @@ After you've started the server you can connect a client to it by using the `web OCPP compliant handler ---------------------- +.. note:: + + This document describes how to create an central system that supports OCPP + 1.6. The ocpp Python package has support for OCPP 2.0 as well. This + documentation will be updated soon to reflect that. In the mean time please + consult the `examples/`_ to learn how to create an OCPP 2.0 central system. + The websocket server created above is not very useful and only sends a non-OCPP compliant message. Remove the `on_connect()` handler from the code above and replace it by the following snippet. @@ -133,8 +140,8 @@ is used to create a response that is send back to the client. .. note:: - OCPP uses a camelCase naming scheme for the keys in the payload. Python, on the other hand, uses - snake_case. + OCPP uses a camelCase naming scheme for the keys in the payload. Python, on + the other hand, uses snake_case. Therefore this ocpp package converts all keys in messages from camelCase to snake_case and vice versa to make sure you can write Pythonic code. @@ -162,7 +169,7 @@ You can find the source code of the central system created in this document in t directory. .. _client: https://websockets.readthedocs.io/en/stable/intro.html#one-more-thing -.. _examples/: https://github.com/mobilityhouse/ocpp/blob/master/examples/central_system.py +.. _examples/: https://github.com/mobilityhouse/ocpp/blob/master/examples .. _ocpp.v16.call_result.BootNotificationPayload: https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/v16/call_result.py#L28 .. _ocpp.v16.ChargePoint: https://github.com/mobilityhouse/ocpp/blob/master/ocpp/v16/charge_point.py#L80 .. _start(): https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/v16/charge_point.py#L125 diff --git a/docs/source/conf.py b/docs/source/conf.py index 4148d1b13..b1adf5ae4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,7 +13,6 @@ import os import sys sys.path.insert(0, os.path.abspath('../../')) -print(os.path.abspath('../../ocpp')) # -- Project information ----------------------------------------------------- diff --git a/examples/central_system.py b/examples/v16/central_system.py similarity index 100% rename from examples/central_system.py rename to examples/v16/central_system.py diff --git a/examples/charge_point.py b/examples/v16/charge_point.py similarity index 100% rename from examples/charge_point.py rename to examples/v16/charge_point.py diff --git a/examples/v20/central_system.py b/examples/v20/central_system.py new file mode 100644 index 000000000..eeedb2043 --- /dev/null +++ b/examples/v20/central_system.py @@ -0,0 +1,68 @@ +import asyncio +from datetime import datetime + +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.routing import on +from ocpp.v20 import ChargePoint as cp +from ocpp.v20 import call_result + + +class ChargePoint(cp): + @on('BootNotification') + def on_boot_notitication(self, charging_station, reason, **kwargs): + return call_result.BootNotificationPayload( + current_time=datetime.utcnow().isoformat(), + interval=10, + status='Accepted' + ) + + @on('Heartbeat') + def on_heartbeat(self): + print('Got a Heartbeat!') + return call_result.HeartbeatPayload( + current_time=datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + "Z" + ) + + +async def on_connect(websocket, path): + """ For every new charge point that connects, create a ChargePoint instance + and start listening for messages. + + """ + charge_point_id = path.strip('/') + cp = ChargePoint(charge_point_id, websocket) + + await cp.start() + + +async def main(): + server = await websockets.serve( + on_connect, + '0.0.0.0', + 9000, + subprotocols=['ocpp1.6'] + ) + + await server.wait_closed() + + +if __name__ == '__main__': + try: + # asyncio.run() is used when running this example with Python 3.7 and + # higher. + asyncio.run(main()) + except AttributeError: + # For Python 3.6 a bit more code is required to run the main() task on + # an event loop. + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/examples/v20/charge_point.py b/examples/v20/charge_point.py new file mode 100644 index 000000000..bd2b5470d --- /dev/null +++ b/examples/v20/charge_point.py @@ -0,0 +1,62 @@ +import asyncio + +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.v20 import call +from ocpp.v20 import ChargePoint as cp + + +class ChargePoint(cp): + + async def send_heartbeat(self, interval): + request = call.HeartbeatPayload() + while True: + await self.call(request) + await asyncio.sleep(interval) + + async def send_boot_notification(self): + request = call.BootNotificationPayload( + charging_station={ + 'model': 'Wallbox XYZ', + 'vendor_name': 'anewone' + }, + reason="PowerUp" + ) + response = await self.call(request) + + if response.status == 'Accepted': + print("Connected to central system.") + await self.send_heartbeat(response.interval) + + +async def main(): + async with websockets.connect( + 'ws://localhost:9000/CP_1', + subprotocols=['ocpp2.0'] + ) as ws: + + cp = ChargePoint('CP_1', ws) + + await asyncio.gather(cp.start(), cp.send_boot_notification()) + + +if __name__ == '__main__': + try: + # asyncio.run() is used when running this example with Python 3.7 and + # higher. + asyncio.run(main()) + except AttributeError: + # For Python 3.6 a bit more code is required to run the main() task on + # an event loop. + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close()