Brak opisu

central_system.rst 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. Central System
  2. ==============
  3. The Open Charge Point Protocol defines two roles: the charge point (or the client) and the central
  4. server (or the server). The ocpp Python package can be used to model both sides of the connection.
  5. This document explains how to create a central system and how to model a charge
  6. point at server side. Most of the examples on this page use the `websockets`_
  7. library as implementation of the websockets layer. Note that package isn't
  8. required, other websocket implementations might work as well.
  9. Create a websocket server
  10. -------------------------
  11. The snippet below creates a very simple websocket server that listens at port 9000 for connections
  12. and that prints 'Charge point connected' for every new websocket connection made.
  13. .. code-block:: python
  14. import asyncio
  15. import websockets
  16. async def on_connect(websocket, path):
  17. await websocket.send('Connection made successfully.')
  18. print(f'Charge point {path} connected')
  19. async def main():
  20. server = await websockets.serve(
  21. on_connect,
  22. '0.0.0.0',
  23. 9000,
  24. subprotocols=['ocpp1.6']
  25. )
  26. await server.wait_closed()
  27. if __name__ == '__main__':
  28. asyncio.run(main())
  29. There are two things that requires a few words of explanation.
  30. * The `on_connect()` handler that is passed as a first argument to `websockets.serve()`_. This is
  31. the handler that is executed on every new connection.
  32. The handler is passed two arguments: an instance of `websockets.server.WebSocketServerProtocol`_
  33. and the request URI. The request URI is used as identifier for the charge point that made the
  34. connection. To quote a snippet from section 3.1.1 of the OCPP-J specification:
  35. *"The charge point's connection URL contains the charge point identity
  36. so that the Central System knows which charge point a Websocket connection
  37. belongs to."*
  38. The handler in this example sends a message to the client and prints a message to the console.
  39. * The `subprotocols` argument in `websockets.serve()` is used to configure the server that it
  40. supports OCPP 1.6.
  41. After you've started the server you can connect a client to it by using the `websockets` interactive
  42. `client`_:
  43. .. code-block:: shell
  44. $ python -m websockets ws://localhost:9000/test_charge_point
  45. Connected to ws://localhost:9000/test_charge_point.
  46. < Connection made successfully.
  47. Connection closed: code = 1000 (OK), no reason.
  48. OCPP compliant handler
  49. ----------------------
  50. .. note::
  51. This document describes how to create an central system that supports OCPP
  52. 1.6. The ocpp Python package has support for OCPP 2.0 as well. This
  53. documentation will be updated soon to reflect that. In the mean time please
  54. consult the `examples/`_ to learn how to create an OCPP 2.0 central system.
  55. The websocket server created above is not very useful and only sends a non-OCPP compliant message.
  56. Remove the `on_connect()` handler from the code above and replace it by the following snippet.
  57. .. code-block:: python
  58. from datetime import datetime
  59. from ocpp.routing import on
  60. from ocpp.v16 import ChargePoint as cp
  61. from ocpp.v16.enums import Action, RegistrationStatus
  62. from ocpp.v16 import call_result
  63. class MyChargePoint(cp):
  64. @on(Action.BootNotification)
  65. async def on_boot_notification(self, charge_point_vendor, charge_point_model, **kwargs):
  66. return call_result.BootNotificationPayload(
  67. current_time=datetime.utcnow().isoformat(),
  68. interval=10,
  69. status=RegistrationStatus.accepted
  70. )
  71. async def on_connect(websocket, path):
  72. """ For every new charge point that connects, create a ChargePoint instance
  73. and start listening for messages.
  74. """
  75. charge_point_id = path.strip('/')
  76. cp = MyChargePoint(charge_point_id, websocket)
  77. await cp.start()
  78. The `on_connect()` handler has been updated and now creates a `MyChargePoint` instance and calls the
  79. `start()`_ coroutine.
  80. `MyChargePoint` subclasses from `ocpp.v16.ChargePoint`_. `ocpp.v16.ChargePoint` is the core of the
  81. ocpp package. This class implements the routing of messages coming from the client to the correct handler.
  82. It also will validate all messages that are being received or being send to the client and it
  83. implements flow control.
  84. Our `MyChargePoint` class uses the `@on()`_ decorator to implement a handler for 'BootNotification'
  85. requests. The `@on()` takes a string with the name of an action as only argument. Although not used
  86. in this example, the package also provides an `@after()`_ decorator that can be used the register a
  87. post request handler.
  88. According to the OCPP specification a payload of a BootNotification request must contain two
  89. required arguments, 'chargePointModel' and 'chargePointVendor', as well as an seven optional
  90. arguments. The handler reflects this by having two required arguments, `charge_point_vendor` and
  91. `charge_point_model`. The handler uses `**kwargs` for the optional arguments.
  92. The handler returns an instance of `ocpp.v16.call_result.BootNotificationPayload`_. This object
  93. is used to create a response that is send back to the client.
  94. .. note::
  95. OCPP uses a camelCase naming scheme for the keys in the payload. Python, on
  96. the other hand, uses snake_case.
  97. Therefore this ocpp package converts all keys in messages from camelCase to
  98. snake_case and vice versa to make sure you can write Pythonic code.
  99. Now start the websocket server again and connect a client to it as you did before. If the client is
  100. connected send this BootNotification to the central system:
  101. .. code-block:: shell
  102. `[2, "12345", "BootNotification", {"chargePointVendor": "The Mobility House", "chargePointModel": "Optimus"}]`
  103. The server should respond and the you should see something like this:
  104. .. code-block:: shell
  105. $ python -m websockets ws://localhost:9000/test_charge_point
  106. Connected to ws://localhost:9000/test_charge_point.
  107. > [2, "12345", "BootNotification", {"chargePointVendor": "The Mobility House", "chargePointModel": "Optimus"}]
  108. < [3, "12345", {"currentTime": "2019-06-16T11:18:09.591716", "interval": 10, "status": "Accepted"}]`
  109. Congratulations! You've created a central system.
  110. You can find the source code of the central system created in this document in the `examples/`_
  111. directory.
  112. .. _client: https://websockets.readthedocs.io/en/stable/intro.html#one-more-thing
  113. .. _examples/: https://github.com/mobilityhouse/ocpp/blob/master/examples
  114. .. _ocpp.v16.call_result.BootNotificationPayload: https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/v16/call_result.py#L28
  115. .. _ocpp.v16.ChargePoint: https://github.com/mobilityhouse/ocpp/blob/master/ocpp/v16/charge_point.py#L80
  116. .. _start(): https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/v16/charge_point.py#L125
  117. .. _websockets: https://websockets.readthedocs.io/en/stable/
  118. .. _websockets.serve(): https://websockets.readthedocs.io/en/stable/api.html#module-websockets.server
  119. .. _websockets.server.WebsocketServerProtocol: https://websockets.readthedocs.io/en/stable/api.html#websockets.server.WebSocketServerProtocol
  120. .. _@on(): https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/routing.py#L4
  121. .. _@after(): https://github.com/mobilityhouse/ocpp/blob/3b92c2c53453dd6511a202e1dc1b9aa1a236389e/ocpp/routing.py#L34