t">+ 28
+- [#493](https://github.com/mobilityhouse/ocpp/issues/493) Reduce use of NotSupportedError in favor of NotImplementedError. Thanks [drc38](@https://github.com/drc38).
29
+- [#278](https://github.com/mobilityhouse/ocpp/pull/278) Fix types for attributes of OCPP 1.6's type `IdTagInfo`. Thanks [@chan-vince](https://github.com/chan-vince)
30
+- [#504](https://github.com/mobilityhouse/ocpp/pull/504) Add missing tech_info attribute to v2.0.1 EventDataType. Thanks [@LokiHokie](https://github.com/LokiHokie)
31
+- [#381](https://github.com/mobilityhouse/ocpp/issues/381) Add FormationError and OccurrenceConstraintViolationError.
32
+
33
+## 0.21.0 (2023-10-19) 
34
+
35
+- [#492] Minor fixes _handle_call doc string  - Thanks @drc38
36
+- [#485](https://github.com/mobilityhouse/ocpp/issues/485) Update documents for 2.0.1 to lastest; removed 2.0 docs
37
+- [#412](https://github.com/mobilityhouse/ocpp/issues/412) Add default value to 1.6 AuthorizationData datatype, id_tag_info
38
+- [#141](https://github.com/mobilityhouse/ocpp/issues/141) Add to docs OCPP 1.6 Security White Paper Ed 2
39
+
40
+## 0.20.0 (2023-10-10) 
41
+
42
+- [#471](https://github.com/mobilityhouse/ocpp/issues/471) Fix `ImportError` when using jsonschema 4.19.0 or higher.
43
+- Fix import error in v201 example. Thanks [@Shiwei-Shen](https://github.com/Shiwei-Shen)!
44
+- Update Poetry to 1.5.1 in CI.
45
+
46
+## 0.19.0 (2023-04-26)
47
+
48
+- [#438] feat: add optional param for passing an unique_id to call method. Thanks [@santiagosalamandri](https://github.com/santiagosalamandri)
49
+- [#420] fix: type hint child_certificate_hash_data type. Thanks [@santiagosalamandri](https://github.com/santiagosalamandri)
50
+
51
+## 0.18.0 (2023-03-22)
52
+
53
+- [#434](https://github.com/mobilityhouse/ocpp/issues/407) Add custom_data attribute to payload-related dataclasses. [@will-fs](https://github.com/will-fs)
54
+
55
+## 0.17.0 (2023-01-04)
56
+
57
+- [#413](https://github.com/mobilityhouse/ocpp/issues/407) Add enum for OCPP 1.6 ConfigurationKey. Thansks [@isabelle-tmh](https://github.com/isabelle-tmh) and [@lbbrhzn](https://github.com/lbbrhzn)
58
+- [#407](https://github.com/mobilityhouse/ocpp/issues/407) Fix build that publishes release to pypi.
59
+
60
+## 0.16.0 (2023-01-04)
61
+
62
+- [#401](https://github.com/mobilityhouse/ocpp/issues/401) Fix serialization error when receiving invalid inbound Call.
63
+- [#402](https://github.com/mobilityhouse/ocpp/issues/402) Update development dependencies.
64
+- [#382](https://github.com/mobilityhouse/ocpp/issues/382) Added missing ' (single quote) to error message. Thanks [@BIGduzy](https://github.com/BIGduzy)
65
+- [#374](https://github.com/mobilityhouse/ocpp/issues/374) Run tests in CI build that builds package and releases it to Pypi. Thanks [@aysauchoa](https://github.com/aysauchoa)
66
+- [#384](https://github.com/mobilityhouse/ocpp/issues/384) Include OCPP message that causes OCPPError. Thanks [@klimaschkas](https://github.com/klimaschkas)
67
+- [#385](https://github.com/mobilityhouse/ocpp/issues/385) Integrate black and isort. Thanks [@proelke](https://github.com/proelke)
68
+- [#331](https://github.com/mobilityhouse/ocpp/issues/331) Add missing variant to OCPP 2.0.1 enum DataType.password_string. Thanks [@mdwcrft](https://github.com/mdwcrft)
69
+- [#332](https://github.com/mobilityhouse/ocpp/issues/332) Update OCPP 2.0.1 ConnectorType enum variants introduced in errata. Thanks [@mdwcrft](https://github.com/mdwcrft)
70
+- [#379](https://github.com/mobilityhouse/ocpp/issues/379) Fix typo in OCPP 2.0.1 attribute FirmwareType.retrieve_data. Thanks [@mdwcrft](https://github.com/mdwcrft)
71
+- [#369](https://github.com/mobilityhouse/ocpp/issues/369) Make sure that CI use latest Python 3.10 release. Thanks [@adamchainz](https://github.com/adamchainz)
72
+- [#353](https://github.com/mobilityhouse/ocpp/issues/343) Fix typo in OCPP 2.0.1 enum variant MessageTrigger.sign_v2g_certificate. Thanks [@proelke](https://github.com/proelke)
73
+- [#359](https://github.com/mobilityhouse/ocpp/issues/359) Add enum for OCPP 2.0.1's SecurityEvent. Thanks [@proelke](https://github.com/proelke)
74
+
75
+## 0.15.0 (2022-05-11)
76
+
77
+- [#306](https://github.com/mobilityhouse/ocpp/issues/306) Fix type hint `ocpp.v201.datatypes.MeterValueType.sampled_value`. Thanks [@Shadowsith](https://github.com/Shadowsith)
78
+- [#328](https://github.com/mobilityhouse/ocpp/issues/324) Add missing attribute `ocpp.v201.dataypes.SampledValueType.measurand`.Thanks [@maurerle](https://github.com/maurerle)
79
+- [#335](https://github.com/mobilityhouse/ocpp/issues/335) Improve Exception handling and CallError responses. Thanks [@proelke](https://github.com/proelke)
80
+- [#316](https://github.com/mobilityhouse/ocpp/issues/333) Drop Python 3.6 support and update jsonschema to 4.4. Thanks [@laysauchoa](https://github.com/laysauchoa)
81
+
82
+## 0.14.1 (2022-03-08)
83
+
84
+- [#316](https://github.com/mobilityhouse/ocpp/issues/316) Fix definition of `GetVariableResultType.variable`. Thanks [@HugoJP1](https://github.com/HugoJP1)
85
+
86
+## 0.14.0 (2022-03-03)
87
+
88
+- [#312](https://github.com/mobilityhouse/ocpp/issues/312) Raise `TypeConstraintViolationError` instead of `ValidationError` when value exceeds length limit. Thanks [@tmh-grunwald-markus](https://github.com/tmh-grunwald-markus)
89
+
90
+## 0.13.1 (2022-02-02)
91
+
92
+The tag 0.13.0 was created, but the build to publish the release failed to pypi failed.
93
+Therefore, 0.13.0 is not listed in this CHANGELOG.md
94
+
95
+- [#293](https://github.com/mobilityhouse/ocpp/issues/293) Add missing attributes to `GetVariableResultType`. Thanks [@proelke](https://github.com/proelke)
96
+- [#294](https://github.com/mobilityhouse/ocpp/issues/294) Improved error handling when schema validation fails. Thanks [@joaomariord](https://github.com/joaomariord)
97
+
98
+## 0.12.1 (2022-01-17)
99
+
100
+- [#289](https://github.com/mobilityhouse/ocpp/issues/289) Fix bug in `remove_nones()` when processing `str`.
101
+
102
+## 0.12.0 (2022-01-12)
103
+
104
+- [#272](https://github.com/mobilityhouse/ocpp/issues/272) Improve `remove_nones` so it handles nested data structures better. Thanks [@proelke](https://github.com/proelke)
105
+- [#287](https://github.com/mobilityhouse/ocpp/issues/287) Add enum StatusCodeInfoType. Thanks [@proelke](https://github.com/proelke)
106
+- [#288](https://github.com/mobilityhouse/ocpp/issues/288) Fixed typos in attributes. Thanks [@mdwcrft](https://github.com/mdwcrft)
107
+
108
+## 0.11.0 (2021-11-26)
109
+
110
+- [#250](https://github.com/mobilityhouse/ocpp/issues/250) Add v1.6 data types
111
+- [#268](https://github.com/mobilityhouse/ocpp/issues/268) Move from CircleCI to Github Actions.
112
+- [#270](https://github.com/mobilityhouse/ocpp/issues/270) Changes badges to reflect move to Github Action
113
+
114
+## 0.10.1 (2021-11-18)
115
+
116
+- [#252](https://github.com/mobilityhouse/ocpp/issues/252) Fix CI build.
117
+- [#259](https://github.com/mobilityhouse/ocpp/issues/259) Fix typo on `Action.SetMonitoringBase`. Thanks [@shaikhasif15752](https://github.com/shaikhasif15752)
118
+
119
+## 0.10.0 (2021-09-16)
120
+
121
+- [#249](https://github.com/mobilityhouse/ocpp/issues/249) Remove depreciated function `get_schema_code()`. Thanks [@proelke](https://github.com/proelke)
122
+- [#240](https://github.com/mobilityhouse/ocpp/issues/240) Add OCPP v2.0.1 data types. Thanks [@proelke](https://github.com/proelke)
123
+
124
+## 0.9.0 (2021-09-02)
125
+
126
+- Fix limit array in meterValue and sampledValue. Thanks [@laysauchoa](https://github.com/laysauchoa)
127
+- [#141](https://github.com/mobilityhouse/ocpp/issues/141) Add security enhancement for OCPP 1.6. Thanks [@villekr](https://github.com/villekr)
128
+- [#217](https://github.com/mobilityhouse/ocpp/issues/217) Fix parsing of floats in GetCompositeSchedule response. Thanks [@laysauchoa](https://github.com/laysauchoa)
129
+- [#223](https://github.com/mobilityhouse/ocpp/issues/223) Fix type DataTransferPayload.status. Thanks [@laysauchoa](https://github.com/laysauchoa)
130
+
131
+## 0.8.3 (2021-04-21)
132
+
133
+- [#200](https://github.com/mobilityhouse/ocpp/issues/200) Add context to `asyncio.TimeoutError`s raised by `ocpp.ChargePoint.call()`.
134
+
135
+## 0.8.2 (2021-04-21)
136
+
137
+- [#167](https://github.com/mobilityhouse/ocpp/issues/167) Fix OCPP 2.0.1 call payloads for `RequestStartTransactionPayload` and `RequestStopTransactionPayload`.
138
+
139
+## 0.8.1 (2020-11-14)
140
+
141
+- [#114](https://github.com/mobilityhouse/ocpp/issues/114) Make casing of `ocpp.v16.enums`'s attributes consistent. Thanks [@tropxy](https://github.com/tropxy)
142
+- [#147](https://github.com/mobilityhouse/ocpp/issues/147) Fix type hint for `ocpp.v16.call.ChangeAvailabilityPayload`. Thanks [@laysauchoa](https://github.com/laysauchoa)
143
+- [#150](https://github.com/mobilityhouse/ocpp/issues/150) Log in to Docker hub to prevent being rate limited.
144
+- [#154](https://github.com/mobilityhouse/ocpp/issues/154) Speed up handling of `Call`s by caching `Draft4Validator` instances.
145
+
146
+## 0.8.0 (2020-10-27)
147
+
148
+- [#104](https://github.com/mobilityhouse/ocpp/issues/104) Allow `CallError`s to be catched. Thanks [@tmh-azinhal](https://github.com/tmh-azinhal)
149
+- [#142](https://github.com/mobilityhouse/ocpp/issues/142)[#143](https://github.com/mobilityhouse/ocpp/issues/143) Add support for OCPP 2.0.1. Thanks [@tropxy](https://github.com/tropxy)
150
+- [#137](https://github.com/mobilityhouse/ocpp/issues/137) Fix generation route map when using `@property`. Thanks [@fa1k3n](https://github.com/fa1k3n)
151
+
152
+## 0.7.2 (2020-10-17)
153
+
154
+- [#127](https://github.com/mobilityhouse/ocpp/issues/127) Fix type hints of enums.
155
+- [#130](https://github.com/mobilityhouse/ocpp/issues/130) Fix possible deadlock when using `@after()` handlers.
156
+- [#131](https://github.com/mobilityhouse/ocpp/issues/131) Add CI support for Python 3.9. Thanks [@laysauchoa](https://github.com/laysauchoa)!
157
+
158
+## 0.7.1 (2020-09-18)
159
+
160
+- [#117](https://github.com/mobilityhouse/ocpp/issues/117) Fix handling of async `@after()` handlers.
161
+
162
+## 0.7.0 (2020-09-13)
163
+
164
+- [#95](https://github.com/mobilityhouse/ocpp/issues/95) Remove use of deprecated `asyncio.coroutine()`. Thanks [@laysauchoa](https://github.com/laysauchoa)!
165
+- [#105](https://github.com/mobilityhouse/ocpp/issues/105) Implement `__str__()` for all exceptions. Thanks [@laysauchoa](https://github.com/laysauchoa)!
166
+- [#110](https://github.com/mobilityhouse/ocpp/issues/110) Subclass OCPP 1.6 enums from `str` and `enum.Enum`.
167
+- [#113](https://github.com/mobilityhouse/ocpp/issues/113) Use OCPP 1.6 enums as type hints in calls and call results.
168
+
169
+## 0.6.4 (2020-03-22)
170
+
171
+- [#76](https://github.com/mobilityhouse/ocpp/issues/76) Fix names of 2 OCPP OCPP 2.0 call payloads.
172
+
173
+## 0.6.3 (2020-02-26)
174
+
175
+- Add links to source and documentation in Pypi. [@adamchainz](https://github.com/adamchainz)
176
+
177
+## 0.6.2 (2020-02-21)
178
+
179
+- [#71](https://github.com/mobilityhouse/ocpp/issues/71) Add unit Hertz. [@bengarrett1971](https://github.com/bengarrett1971)
180
+
181
+## 0.6.1 (2020-02-19)
182
+
183
+- [#68](https://github.com/mobilityhouse/ocpp/issues/68) Fix validation of SetChargingProfile
184
+
185
+## 0.6.0 (2020-02-10)
186
+
187
+- [#63](https://github.com/mobilityhouse/ocpp/issues/63) Remove spaces after separators before sending message
188
+- [#65](https://github.com/mobilityhouse/ocpp/issues/65) `ocpp.ChargePoint.call()` doesn't validate the messages
189
+
190
+## 0.5.1 (2019-12-06)
191
+
192
+- [#57](https://github.com/mobilityhouse/ocpp/issues/57) Implement errata v4 for OCPP 1.6. Thanks [@darander](https://github.com/darander)
193
+
194
+## 0.5.0 (2019-12-03)
195
+
196
+- [#54](https://github.com/mobilityhouse/ocpp/issues/54) Add option to `@on()` to skip schema validation
197
+
198
+## 0.4.4 (2019-11-21)
199
+
200
+- [#43](https://github.com/mobilityhouse/ocpp/issues/43) Fix validation of 3 OCPP v1.6 payloads containing floats
201
+
202
+## 0.4.3 (2019-11-18)
203
+
204
+- [#50](https://github.com/mobilityhouse/ocpp/issues/50) Fix RuntimeError when using ocpp.charge_point.ChargePoint.call
205
+
206
+## 0.4.2 (2019-11-18)
207
+
208
+- [#46](https://github.com/mobilityhouse/ocpp/issues/46) Fix potential deadlock
209
+- [#48](https://github.com/mobilityhouse/ocpp/issues/48) Make ocpp.v16.call.ReserveNowPayload.parent_id_tag optional
210
+
211
+## 0.4.1 (2019-11-11)
212
+
213
+- [#37](https://github.com/mobilityhouse/ocpp/issues/37) Add Python 3.8 support
214
+- Several fixes of typos and type hints in v16 dataclasses
215
+
216
+## 0.4.0 (2019-10-29)
217
+
218
+- [#29](https://github.com/mobilityhouse/ocpp/issues/29) Add OCPP 2.0 support
219
+
220
+## 0.3.2 (2019-09-30)
221
+
222
+- [#27](https://github.com/mobilityhouse/ocpp/issues/27) Fix possible dead lock when running `@after()` handler.
223
+
224
+## 0.3.1 (2019-09-23)
225
+
226
+- An invalid 0.3.0 release has been uploaded to pypi.org. pypi.org doesn't
227
+  allow reuploading a new release using the same file names. Therefore a new
228
+  release had to be made.
229
+
230
+## 0.3.0 (2019-09-23)
231
+
232
+** Backwards incompatible change with ocpp <= 0.2.2. **
233
+
234
+- [#26](https://github.com/mobilityhouse/ocpp/issues/26) Pass request payload to `@after()` handler.
235
+
236
+## 0.2.2 (2019-08-29)
237
+
238
+- [#21](https://github.com/mobilityhouse/ocpp/issues/21) Fix several type hints
239
+
240
+## 0.2.1 (2019-07-31)
241
+
242
+### Bug fixes
243
+
244
+- [#14](https://github.com/mobilityhouse/ocpp/issues/14) Fix typo in attribute of call.StartTransactionPayload
245
+- [#16](https://github.com/mobilityhouse/ocpp/issues/16) Fix attributes of call.StopTransaction
246
+- [#18](https://github.com/mobilityhouse/ocpp/issues/18) Fix typo in attribute of call.RemoteStopTransaction
247
+
248
+## 0.2.0 (2019-06-07)
249
+
250
+### Improvements
251
+
252
+- [#5](https://github.com/mobilityhouse/ocpp/issues/5) Add support for Python 3.6 and move to Poetry
253
+
254
+## 0.1.1 (2019-05-31)
255
+
256
+### Improvements
257
+
258
+- [#3](https://github.com/mobilityhouse/ocpp/issues/3) Add CircleCI integration.
259
+
260
+### Bug fixes
261
+
262
+- [#1](https://github.com/mobilityhouse/ocpp/issues/1) Add JSON schema's to distribution.
263
+
264
+## 0.1.0 (2019-05-26)
265
+
266
+- Initial release.

+ 21 - 0
ocpp/LICENSE

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2019 The Mobility House
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 3 - 0
ocpp/MANIFEST.in

@@ -0,0 +1,3 @@
1
+recursive-exclude tests *
2
+recursive-include ocpp/v16/schemas *.json
3
+recursive-include ocpp/v20/schemas *.json

+ 57 - 0
ocpp/Makefile

@@ -0,0 +1,57 @@
1
+.PHONY: help .check-pypi-envs .install-poetry update install docs tests build deploy
2
+
3
+.DEFAULT_GOAL := help
4
+
5
+export PATH := ${HOME}/.local/bin:$(PATH)
6
+
7
+IS_POETRY := $(shell pip freeze | grep "poetry==")
8
+
9
+CURRENT_VERSION := $(shell poetry version -s)
10
+
11
+help:
12
+	@echo "Please use 'make <target>', where <target> is one of"
13
+	@echo ""
14
+	@echo "  build                            builds the project .whl with poetry"
15
+	@echo "  deploy                           deploys the project using poetry (not recommended, only use if really needed)"
16
+	@echo "  help                             outputs this helper"
17
+	@echo "  install                          installs the dependencies in the env"
18
+	@echo "  release version=<sem. version>   bumps the project version to <sem. version>, using poetry;"
19
+	@echo "                                   Updates also docs/source/conf.py version;"
20
+	@echo "                                   If no version is provided, poetry outputs the current project version"
21
+	@echo "  test                             run all the tests and linting"
22
+	@echo "  update                           updates the dependencies in poetry.lock"
23
+	@echo ""
24
+	@echo "Check the Makefile to know exactly what each target is doing."
25
+
26
+
27
+.install-poetry:
28
+	@if [ -z ${IS_POETRY} ]; then pip install poetry; fi
29
+
30
+update: .install-poetry
31
+	poetry update
32
+
33
+install: .install-poetry
34
+	poetry install
35
+
36
+docs: .install-poetry
37
+	poetry run sphinx-build -b html docs/source docs/build
38
+
39
+format: .install-poetry
40
+	poetry run isort ocpp tests  && poetry run black ocpp tests
41
+
42
+tests: .install-poetry
43
+	poetry run black --check --diff ocpp tests
44
+	poetry run isort --check-only ocpp tests
45
+	poetry run flake8 ocpp tests
46
+	poetry run py.test -vvv --cov=ocpp --cov-report=term-missing tests/
47
+
48
+build: .install-poetry
49
+	poetry build
50
+
51
+release: .install-poetry
52
+	@echo "Please remember to update the CHANGELOG.md, before tagging the release"
53
+	@sed -i ".bkp" "s/release = '${CURRENT_VERSION}'/release = '${version}'/g" docs/source/conf.py
54
+	@poetry version ${version}
55
+
56
+deploy: update tests
57
+	poetry publish --build

+ 206 - 0
ocpp/README.rst

@@ -0,0 +1,206 @@
1
+.. image:: https://github.com/mobilityhouse/ocpp/actions/workflows/pull-request.yml/badge.svg?style=svg
2
+   :target: https://github.com/mobilityhouse/ocpp/actions/workflows/pull-request.yml
3
+
4
+.. image:: https://img.shields.io/pypi/pyversions/ocpp.svg
5
+   :target: https://pypi.org/project/ocpp/
6
+
7
+.. image:: https://img.shields.io/readthedocs/ocpp.svg
8
+   :target: https://ocpp.readthedocs.io/en/latest/
9
+
10
+OCPP
11
+----
12
+
13
+Python package implementing the JSON version of the Open Charge Point Protocol
14
+(OCPP). Currently OCPP 1.6 (errata v4), OCPP 2.0 and OCPP 2.0.1 (Final Version)
15
+are supported.
16
+
17
+You can find the documentation on `rtd`_.
18
+
19
+Installation
20
+------------
21
+
22
+You can either the project install from Pypi:
23
+
24
+.. code-block:: bash
25
+
26
+   $ pip install ocpp
27
+
28
+Or clone the project and install it manually using:
29
+
30
+.. code-block:: bash
31
+
32
+   $ pip install .
33
+
34
+Quick start
35
+-----------
36
+
37
+Below you can find examples on how to create a simple OCPP 2.0 central system as
38
+well as an OCPP 2.0 charge point.
39
+
40
+.. note::
41
+
42
+   To run these examples the dependency websockets_ is required! Install it by running:
43
+
44
+   .. code-block:: bash
45
+
46
+      $ pip install websockets
47
+
48
+Central system
49
+~~~~~~~~~~~~~~
50
+
51
+The code snippet below creates a simple OCPP 2.0 central system which is able
52
+to handle BootNotification calls. You can find a detailed explanation of the
53
+code in the `Central System documentation`_.
54
+
55
+
56
+.. code-block:: python
57
+
58
+    import asyncio
59
+    import logging
60
+    import websockets
61
+    from datetime import datetime
62
+
63
+    from ocpp.routing import on
64
+    from ocpp.v201 import ChargePoint as cp
65
+    from ocpp.v201 import call_result
66
+    from ocpp.v201.enums import RegistrationStatusType
67
+
68
+    logging.basicConfig(level=logging.INFO)
69
+
70
+
71
+    class ChargePoint(cp):
72
+        @on('BootNotification')
73
+        async def on_boot_notification(self, charging_station, reason, **kwargs):
74
+            return call_result.BootNotificationPayload(
75
+                current_time=datetime.utcnow().isoformat(),
76
+                interval=10,
77
+                status=RegistrationStatusType.accepted
78
+            )
79
+
80
+
81
+    async def on_connect(websocket, path):
82
+        """ For every new charge point that connects, create a ChargePoint
83
+        instance and start listening for messages.
84
+        """
85
+        try:
86
+            requested_protocols = websocket.request_headers[
87
+                'Sec-WebSocket-Protocol']
88
+        except KeyError:
89
+            logging.info("Client hasn't requested any Subprotocol. "
90
+                     "Closing Connection")
91
+            return await websocket.close()
92
+
93
+        if websocket.subprotocol:
94
+            logging.info("Protocols Matched: %s", websocket.subprotocol)
95
+        else:
96
+            # In the websockets lib if no subprotocols are supported by the
97
+            # client and the server, it proceeds without a subprotocol,
98
+            # so we have to manually close the connection.
99
+            logging.warning('Protocols Mismatched | Expected Subprotocols: %s,'
100
+                            ' but client supports  %s | Closing connection',
101
+                            websocket.available_subprotocols,
102
+                            requested_protocols)
103
+            return await websocket.close()
104
+
105
+        charge_point_id = path.strip('/')
106
+        cp = ChargePoint(charge_point_id, websocket)
107
+
108
+        await cp.start()
109
+
110
+
111
+    async def main():
112
+        server = await websockets.serve(
113
+            on_connect,
114
+            '0.0.0.0',
115
+            9000,
116
+            subprotocols=['ocpp2.0.1']
117
+        )
118
+        logging.info("WebSocket Server Started")
119
+        await server.wait_closed()
120
+
121
+    if __name__ == '__main__':
122
+        asyncio.run(main())
123
+
124
+Charge point
125
+~~~~~~~~~~~~
126
+
127
+.. code-block:: python
128
+
129
+    import asyncio
130
+
131
+    from ocpp.v201.enums import RegistrationStatusType
132
+    import logging
133
+    import websockets
134
+
135
+    from ocpp.v201 import call
136
+    from ocpp.v201 import ChargePoint as cp
137
+
138
+    logging.basicConfig(level=logging.INFO)
139
+
140
+
141
+    class ChargePoint(cp):
142
+
143
+        async def send_boot_notification(self):
144
+            request = call.BootNotificationPayload(
145
+                charging_station={
146
+                    'model': 'Wallbox XYZ',
147
+                    'vendor_name': 'anewone'
148
+                },
149
+                reason="PowerUp"
150
+            )
151
+            response = await self.call(request)
152
+
153
+            if response.status == RegistrationStatusType.accepted:
154
+                print("Connected to central system.")
155
+
156
+
157
+    async def main():
158
+        async with websockets.connect(
159
+                'ws://localhost:9000/CP_1',
160
+                subprotocols=['ocpp2.0.1']
161
+        ) as ws:
162
+            cp = ChargePoint('CP_1', ws)
163
+
164
+            await asyncio.gather(cp.start(), cp.send_boot_notification())
165
+
166
+
167
+    if __name__ == '__main__':
168
+        asyncio.run(main())
169
+
170
+Debugging
171
+---------
172
+
173
+Python's default log level is `logging.WARNING`. As result most of the logs
174
+generated by this package are discarded. To see the log output of this package
175
+lower the log level to `logging.DEBUG`.
176
+
177
+.. code-block:: python
178
+
179
+  import logging
180
+  logging.basicConfig(level=logging.DEBUG)
181
+
182
+However, this approach defines the log level for the complete logging system.
183
+In other words: the log level of all dependencies is set to `logging.DEBUG`.
184
+
185
+To lower the logs for this package only use the following code:
186
+
187
+.. code-block:: python
188
+
189
+  import logging
190
+  logging.getLogger('ocpp').setLevel(level=logging.DEBUG)
191
+  logging.getLogger('ocpp').addHandler(logging.StreamHandler())
192
+
193
+License
194
+-------
195
+
196
+Except from the documents in `docs/v16` and `docs/v201` everything is licensed under MIT_.
197
+© `The Mobility House`_
198
+
199
+The documents in `docs/v16` and `docs/v201` are licensed under Creative Commons
200
+Attribution-NoDerivatives 4.0 International Public License.
201
+
202
+.. _Central System documentation: https://ocpp.readthedocs.io/en/latest/central_system.html
203
+.. _MIT: https://github.com/mobilityhouse/ocpp/blob/master/LICENSE
204
+.. _rtd: https://ocpp.readthedocs.io/en/latest/index.html
205
+.. _The Mobility House: https://www.mobilityhouse.com/int_en/
206
+.. _websockets: https://pypi.org/project/websockets/

+ 4 - 0
ocpp/docs/source/README.rst

@@ -0,0 +1,4 @@
1
+Getting started
2
+===============
3
+
4
+.. include:: ../../README.rst

+ 180 - 0
ocpp/docs/source/central_system.rst

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

+ 0 - 0
ocpp/docs/source/client.rst


+ 56 - 0
ocpp/docs/source/conf.py

@@ -0,0 +1,56 @@
1
+# Configuration file for the Sphinx documentation builder.
2
+#
3
+# This file only contains a selection of the most common options. For a full
4
+# list see the documentation:
5
+# http://www.sphinx-doc.org/en/master/config
6
+
7
+# -- Path setup --------------------------------------------------------------
8
+
9
+# If extensions (or modules to document with autodoc) are in another directory,
10
+# add these directories to sys.path here. If the directory is relative to the
11
+# documentation root, use os.path.abspath to make it absolute, like shown here.
12
+#
13
+import os
14
+import sys
15
+
16
+sys.path.insert(0, os.path.abspath("../../"))
17
+
18
+
19
+# -- Project information -----------------------------------------------------
20
+
21
+project = "OCPP"
22
+copyright = "2023, Auke Willem Oosterhoff"
23
+author = "Auke Willem Oosterhoff"
24
+
25
+# The full version, including alpha/beta/rc tags
26
+release = "0.25.0"
27
+
28
+
29
+# -- General configuration ---------------------------------------------------
30
+
31
+# Add any Sphinx extension module names here, as strings. They can be
32
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
33
+# ones.
34
+extensions = ["sphinx.ext.autodoc"]
35
+
36
+# Add any paths that contain templates here, relative to this directory.
37
+templates_path = ["_templates"]
38
+
39
+# List of patterns, relative to source directory, that match files and
40
+# directories to ignore when looking for source files.
41
+# This pattern also affects html_static_path and html_extra_path.
42
+exclude_patterns = []
43
+
44
+master_doc = "index"
45
+
46
+# -- Options for HTML output -------------------------------------------------
47
+
48
+# The theme to use for HTML and HTML Help pages.  See the documentation for
49
+# a list of builtin themes.
50
+#
51
+html_theme = "alabaster"
52
+
53
+# Add any paths that contain custom static files (such as style sheets) here,
54
+# relative to this directory. They are copied after the builtin static files,
55
+# so a file named "default.css" will overwrite the builtin "default.css".
56
+html_static_path = ["_static"]

+ 15 - 0
ocpp/docs/source/index.rst

@@ -0,0 +1,15 @@
1
+.. OCPP documentation master file, created by
2
+   sphinx-quickstart on Tue May 28 20:40:32 2019.
3
+   You can adapt this file completely to your liking, but it should at least
4
+   contain the root `toctree` directive.
5
+
6
+.. include:: ../../README.rst
7
+
8
+Index
9
+-----
10
+
11
+.. toctree::
12
+   :maxdepth: 2
13
+
14
+   README
15
+   central_system

binární
ocpp/docs/v16/ocpp-1.6 edition 2.pdf


binární
ocpp/docs/v16/ocpp-1.6-errata-sheet.pdf


binární
ocpp/docs/v16/ocpp-1.6-security-whitepaper-edition-2.pdf


File diff suppressed because it is too large
+ 188194 - 0
ocpp/docs/v16/ocpp-1.6.pdf


binární
ocpp/docs/v16/ocpp-j-1.6-errata-sheet.pdf


File diff suppressed because it is too large
+ 16654 - 0
ocpp/docs/v16/ocpp-j-1.6-specification.pdf


binární
ocpp/docs/v201/Changelog OCPP 2.0 - 2.0.1.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_edition2_errata_2023-12.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part0_introduction.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part1_architecture_topology.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part2_appendices_v13.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part2_errata_v20.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part2_specification_edition2.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part4_ocpp-j-specification.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part5_certification_profiles.pdf


binární
ocpp/docs/v201/OCPP-2.0.1_part6_test_cases.pdf


+ 76 - 0
ocpp/examples/v16/central_system.py

@@ -0,0 +1,76 @@
1
+import asyncio
2
+import logging
3
+from datetime import datetime
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.routing import on
17
+from ocpp.v16 import ChargePoint as cp
18
+from ocpp.v16 import call_result
19
+from ocpp.v16.enums import Action, RegistrationStatus
20
+
21
+logging.basicConfig(level=logging.INFO)
22
+
23
+
24
+class ChargePoint(cp):
25
+    @on(Action.BootNotification)
26
+    def on_boot_notification(
27
+        self, charge_point_vendor: str, charge_point_model: str, **kwargs
28
+    ):
29
+        return call_result.BootNotificationPayload(
30
+            current_time=datetime.utcnow().isoformat(),
31
+            interval=10,
32
+            status=RegistrationStatus.accepted,
33
+        )
34
+
35
+
36
+async def on_connect(websocket, path):
37
+    """For every new charge point that connects, create a ChargePoint
38
+    instance and start listening for messages.
39
+    """
40
+    try:
41
+        requested_protocols = websocket.request_headers["Sec-WebSocket-Protocol"]
42
+    except KeyError:
43
+        logging.error("Client hasn't requested any Subprotocol. Closing Connection")
44
+        return await websocket.close()
45
+    if websocket.subprotocol:
46
+        logging.info("Protocols Matched: %s", websocket.subprotocol)
47
+    else:
48
+        # In the websockets lib if no subprotocols are supported by the
49
+        # client and the server, it proceeds without a subprotocol,
50
+        # so we have to manually close the connection.
51
+        logging.warning(
52
+            "Protocols Mismatched | Expected Subprotocols: %s,"
53
+            " but client supports  %s | Closing connection",
54
+            websocket.available_subprotocols,
55
+            requested_protocols,
56
+        )
57
+        return await websocket.close()
58
+
59
+    charge_point_id = path.strip("/")
60
+    cp = ChargePoint(charge_point_id, websocket)
61
+
62
+    await cp.start()
63
+
64
+
65
+async def main():
66
+    server = await websockets.serve(
67
+        on_connect, "0.0.0.0", 9000, subprotocols=["ocpp1.6"]
68
+    )
69
+
70
+    logging.info("Server Started listening to new connections...")
71
+    await server.wait_closed()
72
+
73
+
74
+if __name__ == "__main__":
75
+    # asyncio.run() is used when running this example with Python >= 3.7v
76
+    asyncio.run(main())

+ 47 - 0
ocpp/examples/v16/charge_point.py

@@ -0,0 +1,47 @@
1
+import asyncio
2
+import logging
3
+
4
+try:
5
+    import websockets
6
+except ModuleNotFoundError:
7
+    print("This example relies on the 'websockets' package.")
8
+    print("Please install it by running: ")
9
+    print()
10
+    print(" $ pip install websockets")
11
+    import sys
12
+
13
+    sys.exit(1)
14
+
15
+
16
+from ocpp.v16 import ChargePoint as cp
17
+from ocpp.v16 import call
18
+from ocpp.v16.enums import RegistrationStatus
19
+
20
+logging.basicConfig(level=logging.INFO)
21
+
22
+
23
+class ChargePoint(cp):
24
+    async def send_boot_notification(self):
25
+        request = call.BootNotificationPayload(
26
+            charge_point_model="Optimus", charge_point_vendor="The Mobility House"
27
+        )
28
+
29
+        response = await self.call(request)
30
+
31
+        if response.status == RegistrationStatus.accepted:
32
+            print("Connected to central system.")
33
+
34
+
35
+async def main():
36
+    async with websockets.connect(
37
+        "ws://localhost:9000/CP_1", subprotocols=["ocpp1.6"]
38
+    ) as ws:
39
+
40
+        cp = ChargePoint("CP_1", ws)
41
+
42
+        await asyncio.gather(cp.start(), cp.send_boot_notification())
43
+
44
+
45
+if __name__ == "__main__":
46
+    # asyncio.run() is used when running this example with Python >= 3.7v
47
+    asyncio.run(main())

+ 78 - 0
ocpp/examples/v20/central_system.py

@@ -0,0 +1,78 @@
1
+import asyncio
2
+import logging
3
+from datetime import datetime
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.routing import on
17
+from ocpp.v20 import ChargePoint as cp
18
+from ocpp.v20 import call_result
19
+
20
+logging.basicConfig(level=logging.INFO)
21
+
22
+
23
+class ChargePoint(cp):
24
+    @on("BootNotification")
25
+    def on_boot_notification(self, charging_station, reason, **kwargs):
26
+        return call_result.BootNotificationPayload(
27
+            current_time=datetime.utcnow().isoformat(), interval=10, status="Accepted"
28
+        )
29
+
30
+    @on("Heartbeat")
31
+    def on_heartbeat(self):
32
+        print("Got a Heartbeat!")
33
+        return call_result.HeartbeatPayload(
34
+            current_time=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") + "Z"
35
+        )
36
+
37
+
38
+async def on_connect(websocket, path):
39
+    """For every new charge point that connects, create a ChargePoint
40
+    instance and start listening for messages.
41
+    """
42
+    try:
43
+        requested_protocols = websocket.request_headers["Sec-WebSocket-Protocol"]
44
+    except KeyError:
45
+        logging.error("Client hasn't requested any Subprotocol. Closing Connection")
46
+        return await websocket.close()
47
+    if websocket.subprotocol:
48
+        logging.info("Protocols Matched: %s", websocket.subprotocol)
49
+    else:
50
+        # In the websockets lib if no subprotocols are supported by the
51
+        # client and the server, it proceeds without a subprotocol,
52
+        # so we have to manually close the connection.
53
+        logging.warning(
54
+            "Protocols Mismatched | Expected Subprotocols: %s,"
55
+            " but client supports  %s | Closing connection",
56
+            websocket.available_subprotocols,
57
+            requested_protocols,
58
+        )
59
+        return await websocket.close()
60
+
61
+    charge_point_id = path.strip("/")
62
+    cp = ChargePoint(charge_point_id, websocket)
63
+
64
+    await cp.start()
65
+
66
+
67
+async def main():
68
+    server = await websockets.serve(
69
+        on_connect, "0.0.0.0", 9000, subprotocols=["ocpp2.0"]
70
+    )
71
+
72
+    logging.info("Server Started listening to new connections...")
73
+    await server.wait_closed()
74
+
75
+
76
+if __name__ == "__main__":
77
+    # asyncio.run() is used when running this example with Python >= 3.7v
78
+    asyncio.run(main())

+ 53 - 0
ocpp/examples/v20/charge_point.py

@@ -0,0 +1,53 @@
1
+import asyncio
2
+import logging
3
+
4
+try:
5
+    import websockets
6
+except ModuleNotFoundError:
7
+    print("This example relies on the 'websockets' package.")
8
+    print("Please install it by running: ")
9
+    print()
10
+    print(" $ pip install websockets")
11
+    import sys
12
+
13
+    sys.exit(1)
14
+
15
+
16
+from ocpp.v20 import ChargePoint as cp
17
+from ocpp.v20 import call
18
+
19
+logging.basicConfig(level=logging.INFO)
20
+
21
+
22
+class ChargePoint(cp):
23
+    async def send_heartbeat(self, interval):
24
+        request = call.HeartbeatPayload()
25
+        while True:
26
+            await self.call(request)
27
+            await asyncio.sleep(interval)
28
+
29
+    async def send_boot_notification(self):
30
+        request = call.BootNotificationPayload(
31
+            charging_station={"model": "Wallbox XYZ", "vendor_name": "anewone"},
32
+            reason="PowerUp",
33
+        )
34
+        response = await self.call(request)
35
+
36
+        if response.status == "Accepted":
37
+            print("Connected to central system.")
38
+            await self.send_heartbeat(response.interval)
39
+
40
+
41
+async def main():
42
+    async with websockets.connect(
43
+        "ws://localhost:9000/CP_1", subprotocols=["ocpp2.0"]
44
+    ) as ws:
45
+
46
+        cp = ChargePoint("CP_1", ws)
47
+
48
+        await asyncio.gather(cp.start(), cp.send_boot_notification())
49
+
50
+
51
+if __name__ == "__main__":
52
+    # asyncio.run() is used when running this example with Python >= 3.7v
53
+    asyncio.run(main())

+ 79 - 0
ocpp/examples/v201/central_system.py

@@ -0,0 +1,79 @@
1
+import asyncio
2
+import logging
3
+from datetime import datetime
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.routing import on
17
+from ocpp.v201 import ChargePoint as cp
18
+from ocpp.v201 import call_result
19
+
20
+logging.basicConfig(level=logging.INFO)
21
+
22
+
23
+class ChargePoint(cp):
24
+    @on("BootNotification")
25
+    def on_boot_notification(self, charging_station, reason, **kwargs):
26
+        return call_result.BootNotificationPayload(
27
+            current_time=datetime.utcnow().isoformat(), interval=10, status="Accepted"
28
+        )
29
+
30
+    @on("Heartbeat")
31
+    def on_heartbeat(self):
32
+        print("Got a Heartbeat!")
33
+        return call_result.HeartbeatPayload(
34
+            current_time=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") + "Z"
35
+        )
36
+
37
+
38
+async def on_connect(websocket, path):
39
+    """For every new charge point that connects, create a ChargePoint
40
+    instance and start listening for messages.
41
+    """
42
+    try:
43
+        requested_protocols = websocket.request_headers["Sec-WebSocket-Protocol"]
44
+    except KeyError:
45
+        logging.error("Client hasn't requested any Subprotocol. Closing Connection")
46
+        return await websocket.close()
47
+    if websocket.subprotocol:
48
+        logging.info("Protocols Matched: %s", websocket.subprotocol)
49
+    else:
50
+        # In the websockets lib if no subprotocols are supported by the
51
+        # client and the server, it proceeds without a subprotocol,
52
+        # so we have to manually close the connection.
53
+        logging.warning(
54
+            "Protocols Mismatched | Expected Subprotocols: %s,"
55
+            " but client supports %s | Closing connection",
56
+            websocket.available_subprotocols,
57
+            requested_protocols,
58
+        )
59
+        return await websocket.close()
60
+
61
+    charge_point_id = path.strip("/")
62
+    charge_point = ChargePoint(charge_point_id, websocket)
63
+
64
+    await charge_point.start()
65
+
66
+
67
+async def main():
68
+    #  deepcode ignore BindToAllNetworkInterfaces: <Example Purposes>
69
+    server = await websockets.serve(
70
+        on_connect, "0.0.0.0", 9000, subprotocols=["ocpp2.0.1"]
71
+    )
72
+
73
+    logging.info("Server Started listening to new connections...")
74
+    await server.wait_closed()
75
+
76
+
77
+if __name__ == "__main__":
78
+    # asyncio.run() is used when running this example with Python >= 3.7v
79
+    asyncio.run(main())

+ 54 - 0
ocpp/examples/v201/charge_point.py

@@ -0,0 +1,54 @@
1
+import asyncio
2
+import logging
3
+
4
+try:
5
+    import websockets
6
+except ModuleNotFoundError:
7
+    print("This example relies on the 'websockets' package.")
8
+    print("Please install it by running: ")
9
+    print()
10
+    print(" $ pip install websockets")
11
+    import sys
12
+
13
+    sys.exit(1)
14
+
15
+
16
+from ocpp.v201 import ChargePoint as cp
17
+from ocpp.v201 import call
18
+
19
+logging.basicConfig(level=logging.INFO)
20
+
21
+
22
+class ChargePoint(cp):
23
+    async def send_heartbeat(self, interval):
24
+        request = call.HeartbeatPayload()
25
+        while True:
26
+            await self.call(request)
27
+            await asyncio.sleep(interval)
28
+
29
+    async def send_boot_notification(self):
30
+        request = call.BootNotificationPayload(
31
+            charging_station={"model": "Wallbox XYZ", "vendor_name": "anewone"},
32
+            reason="PowerUp",
33
+        )
34
+        response = await self.call(request)
35
+
36
+        if response.status == "Accepted":
37
+            print("Connected to central system.")
38
+            await self.send_heartbeat(response.interval)
39
+
40
+
41
+async def main():
42
+    async with websockets.connect(
43
+        "ws://localhost:9000/CP_1", subprotocols=["ocpp2.0.1"]
44
+    ) as ws:
45
+
46
+        charge_point = ChargePoint("CP_1", ws)
47
+        await asyncio.gather(
48
+            charge_point.start(), charge_point.send_boot_notification()
49
+        )
50
+
51
+
52
+if __name__ == "__main__":
53
+    # asyncio.run() is used when running this example with Python >= 3.7v
54
+    asyncio.run(main())

+ 0 - 0
ocpp/ocpp/__init__.py


+ 368 - 0
ocpp/ocpp/charge_point.py

@@ -0,0 +1,368 @@
1
+import asyncio
2
+import inspect
3
+import logging
4
+import re
5
+import time
6
+import uuid
7
+from dataclasses import asdict
8
+from typing import Dict, List, Union
9
+
10
+from ocpp.exceptions import NotImplementedError, NotSupportedError, OCPPError
11
+from ocpp.messages import Call, MessageType, unpack, validate_payload
12
+from ocpp.routing import create_route_map
13
+
14
+LOGGER = logging.getLogger("ocpp")
15
+
16
+
17
+def camel_to_snake_case(data):
18
+    """
19
+    Convert all keys of all dictionaries inside the given argument from
20
+    camelCase to snake_case.
21
+
22
+    Inspired by: https://stackoverflow.com/a/1176023/1073222
23
+
24
+    """
25
+    if isinstance(data, dict):
26
+        snake_case_dict = {}
27
+        for key, value in data.items():
28
+            s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", key)
29
+            key = re.sub("([a-z0-9])([A-Z])(?=\\S)", r"\1_\2", s1).lower()
30
+
31
+            snake_case_dict[key] = camel_to_snake_case(value)
32
+
33
+        return snake_case_dict
34
+
35
+    if isinstance(data, list):
36
+        snake_case_list = []
37
+        for value in data:
38
+            snake_case_list.append(camel_to_snake_case(value))
39
+
40
+        return snake_case_list
41
+
42
+    return data
43
+
44
+
45
+def snake_to_camel_case(data):
46
+    """
47
+    Convert all keys of all dictionaries inside given argument from
48
+    snake_case to camelCase.
49
+
50
+    Inspired by: https://stackoverflow.com/a/19053800/1073222
51
+    """
52
+    if isinstance(data, dict):
53
+        camel_case_dict = {}
54
+        for key, value in data.items():
55
+            key = key.replace("soc", "SoC")
56
+            key = key.replace("_v2x", "V2X")
57
+            components = key.split("_")
58
+            key = components[0] + "".join(x[:1].upper() + x[1:] for x in components[1:])
59
+            camel_case_dict[key] = snake_to_camel_case(value)
60
+
61
+        return camel_case_dict
62
+
63
+    if isinstance(data, list):
64
+        camel_case_list = []
65
+        for value in data:
66
+            camel_case_list.append(snake_to_camel_case(value))
67
+
68
+        return camel_case_list
69
+
70
+    return data
71
+
72
+
73
+def remove_nones(data: Union[List, Dict]) -> Union[List, Dict]:
74
+    if isinstance(data, dict):
75
+        return {k: remove_nones(v) for k, v in data.items() if v is not None}
76
+
77
+    elif isinstance(data, list):
78
+        return [remove_nones(v) for v in data if v is not None]
79
+
80
+    return data
81
+
82
+
83
+def _raise_key_error(action, version):
84
+    """
85
+    Checks whether a keyerror returned by _handle_call
86
+    is supported by the OCPP version or is simply
87
+    not implemented by the server/client and raises
88
+    the appropriate error.
89
+    """
90
+
91
+    from ocpp.v16.enums import Action as v16_Action
92
+    from ocpp.v201.enums import Action as v201_Action
93
+
94
+    if version == "1.6":
95
+        if hasattr(v16_Action, action):
96
+            raise NotImplementedError(
97
+                details={"cause": f"No handler for {action} registered."}
98
+            )
99
+        else:
100
+            raise NotSupportedError(
101
+                details={"cause": f"{action} not supported by OCPP{version}."}
102
+            )
103
+    elif version in ["2.0", "2.0.1"]:
104
+        if hasattr(v201_Action, action):
105
+            raise NotImplementedError(
106
+                details={"cause": f"No handler for {action} registered."}
107
+            )
108
+        else:
109
+            raise NotSupportedError(
110
+                details={"cause": f"{action} not supported by OCPP{version}."}
111
+            )
112
+
113
+    return
114
+
115
+
116
+class ChargePoint:
117
+    """
118
+    Base Element containing all the necessary OCPP1.6J messages for messages
119
+    initiated and received by the Central System
120
+    """
121
+
122
+    def __init__(self, id, connection, response_timeout=30):
123
+        """
124
+
125
+        Args:
126
+
127
+            charger_id (str): ID of the charger.
128
+            connection: Connection to CP.
129
+            response_timeout (int): When no response on a request is received
130
+                within this interval, a asyncio.TimeoutError is raised.
131
+
132
+        """
133
+        self.id = id
134
+
135
+        # The maximum time in seconds it may take for a CP to respond to a
136
+        # CALL. An asyncio.TimeoutError will be raised if this limit has been
137
+        # exceeded.
138
+        self._response_timeout = response_timeout
139
+
140
+        # A connection to the client. Currently this is an instance of gh
141
+        self._connection = connection
142
+
143
+        # A dictionary that hooks for Actions. So if the CS receives a it will
144
+        # look up the Action into this map and execute the corresponding hooks
145
+        # if exists.
146
+        self.route_map = create_route_map(self)
147
+
148
+        self._call_lock = asyncio.Lock()
149
+
150
+        # A queue used to pass CallResults and CallErrors from
151
+        # the self.serve() task to the self.call() task.
152
+        self._response_queue = asyncio.Queue()
153
+
154
+        # Function used to generate unique ids for CALLs. By default
155
+        # uuid.uuid4() is used, but it can be changed. This is meant primarily
156
+        # for testing purposes to have predictable unique ids.
157
+        self._unique_id_generator = uuid.uuid4
158
+
159
+    async def start(self):
160
+        while True:
161
+            message = await self._connection.recv()
162
+            LOGGER.info("%s: receive message %s", self.id, message)
163
+
164
+            await self.route_message(message)
165
+
166
+    async def route_message(self, raw_msg):
167
+        """
168
+        Route a message received from a CP.
169
+
170
+        If the message is a of type Call the corresponding hooks are executed.
171
+        If the message is of type CallResult or CallError the message is passed
172
+        to the call() function via the response_queue.
173
+        """
174
+        try:
175
+            msg = unpack(raw_msg)
176
+        except OCPPError as e:
177
+            LOGGER.exception(
178
+                "Unable to parse message: '%s', it doesn't seem "
179
+                "to be valid OCPP: %s",
180
+                raw_msg,
181
+                e,
182
+            )
183
+            return
184
+
185
+        if msg.message_type_id == MessageType.Call:
186
+            try:
187
+                await self._handle_call(msg)
188
+            except OCPPError as error:
189
+                LOGGER.exception("Error while handling request '%s'", msg)
190
+                response = msg.create_call_error(error).to_json()
191
+                await self._send(response)
192
+
193
+        elif msg.message_type_id in [MessageType.CallResult, MessageType.CallError]:
194
+            self._response_queue.put_nowait(msg)
195
+
196
+    async def _handle_call(self, msg):
197
+        """
198
+        Execute all hooks installed for based on the Action of the message.
199
+
200
+        First the '_on_action' hook is executed and its response is returned to
201
+        the client. If there is no '_on_action' hook for Action in the message
202
+        a CallError with a NotImplementedError is returned. If the Action is
203
+        not supported by the OCPP version a NotSupportedError is returned.
204
+
205
+        Next the '_after_action' hook is executed.
206
+
207
+        """
208
+        try:
209
+            handlers = self.route_map[msg.action]
210
+        except KeyError:
211
+            _raise_key_error(msg.action, self._ocpp_version)
212
+            return
213
+
214
+        if not handlers.get("_skip_schema_validation", False):
215
+            validate_payload(msg, self._ocpp_version)
216
+        # OCPP uses camelCase for the keys in the payload. It's more pythonic
217
+        # to use snake_case for keyword arguments. Therefore the keys must be
218
+        # 'translated'. Some examples:
219
+        #
220
+        # * chargePointVendor becomes charge_point_vendor
221
+        # * firmwareVersion becomes firmwareVersion
222
+        if msg.payload is None:
223
+            msg.payload = {}
224
+        snake_case_payload = camel_to_snake_case(msg.payload)
225
+
226
+        try:
227
+            handler = handlers["_on_action"]
228
+        except KeyError:
229
+            _raise_key_error(msg.action, self._ocpp_version)
230
+
231
+        try:
232
+        
233
+            response = handler(**snake_case_payload)
234
+            if inspect.isawaitable(response):
235
+                response = await response
236
+        except Exception as e:
237
+            LOGGER.exception("Error while handling request '%s'", msg)
238
+            response = msg.create_call_error(e).to_json()
239
+            await self._send(response)
240
+
241
+            return
242
+
243
+        temp_response_payload = asdict(response)
244
+
245
+        # Remove nones ensures that we strip out optional arguments
246
+        # which were not set and have a default value of None
247
+        response_payload = remove_nones(temp_response_payload)
248
+
249
+        # The response payload must be 'translated' from snake_case to
250
+        # camelCase. So:
251
+        #
252
+        # * charge_point_vendor becomes chargePointVendor
253
+        # * firmware_version becomes firmwareVersion
254
+        camel_case_payload = snake_to_camel_case(response_payload)
255
+
256
+        response = msg.create_call_result(camel_case_payload)
257
+
258
+        if not handlers.get("_skip_schema_validation", False):
259
+            validate_payload(response, self._ocpp_version)
260
+
261
+        await self._send(response.to_json())
262
+
263
+        try:
264
+            handler = handlers["_after_action"]
265
+            # Create task to avoid blocking when making a call inside the
266
+            # after handler
267
+            response = handler(**snake_case_payload)
268
+            if inspect.isawaitable(response):
269
+                asyncio.ensure_future(response)
270
+        except KeyError:
271
+            # '_on_after' hooks are not required. Therefore ignore exception
272
+            # when no '_on_after' hook is installed.
273
+            pass
274
+        return response
275
+
276
+    async def call(self, payload, suppress=True, unique_id=None):
277
+        """
278
+        Send Call message to client and return payload of response.
279
+
280
+        The given payload is transformed into a Call object by looking at the
281
+        type of the payload. A payload of type BootNotificationPayload will
282
+        turn in a Call with Action BootNotification, a HeartbeatPayload will
283
+        result in a Call with Action Heartbeat etc.
284
+
285
+        A timeout is raised when no response has arrived before expiring of
286
+        the configured timeout.
287
+
288
+        When waiting for a response no other Call message can be send. So this
289
+        function will wait before response arrives or response timeout has
290
+        expired. This is in line the OCPP specification
291
+
292
+        Suppress is used to maintain backwards compatibility. When set to True,
293
+        if response is a CallError, then this call will be suppressed. When
294
+        set to False, an exception will be raised for users to handle this
295
+        CallError.
296
+
297
+        """
298
+        camel_case_payload = snake_to_camel_case(asdict(payload))
299
+
300
+        unique_id = (
301
+            unique_id if unique_id is not None else str(self._unique_id_generator())
302
+        )
303
+
304
+        call = Call(
305
+            unique_id=unique_id,
306
+            action=payload.__class__.__name__[:-7],
307
+            payload=remove_nones(camel_case_payload),
308
+        )
309
+
310
+        validate_payload(call, self._ocpp_version)
311
+
312
+        # Use a lock to prevent make sure that only 1 message can be send at a
313
+        # a time.
314
+        async with self._call_lock:
315
+            await self._send(call.to_json())
316
+            try:
317
+                response = await self._get_specific_response(
318
+                    call.unique_id, self._response_timeout
319
+                )
320
+            except asyncio.TimeoutError:
321
+                raise asyncio.TimeoutError(
322
+                    f"Waited {self._response_timeout}s for response on "
323
+                    f"{call.to_json()}."
324
+                )
325
+
326
+        if response.message_type_id == MessageType.CallError:
327
+            LOGGER.warning("Received a CALLError: %s'", response)
328
+            if suppress:
329
+                return
330
+            raise response.to_exception()
331
+        else:
332
+            response.action = call.action
333
+            validate_payload(response, self._ocpp_version)
334
+
335
+        snake_case_payload = camel_to_snake_case(response.payload)
336
+        # Create the correct Payload instance based on the received payload. If
337
+        # this method is called with a call.BootNotificationPayload, then it
338
+        # will create a call_result.BootNotificationPayload. If this method is
339
+        # called with a call.HeartbeatPayload, then it will create a
340
+        # call_result.HeartbeatPayload etc.
341
+        cls = getattr(self._call_result, payload.__class__.__name__)  # noqa
342
+        return cls(**snake_case_payload)
343
+
344
+    async def _get_specific_response(self, unique_id, timeout):
345
+        """
346
+        Return response with given unique ID or raise an asyncio.TimeoutError.
347
+        """
348
+        wait_until = time.time() + timeout
349
+        try:
350
+            # Wait for response of the Call message.
351
+            response = await asyncio.wait_for(self._response_queue.get(), timeout)
352
+        except asyncio.TimeoutError:
353
+            raise
354
+
355
+        if response.unique_id == unique_id:
356
+            return response
357
+
358
+        LOGGER.error("Ignoring response with unknown unique id: %s", response)
359
+        timeout_left = wait_until - time.time()
360
+
361
+        if timeout_left < 0:
362
+            raise asyncio.TimeoutError
363
+
364
+        return await self._get_specific_response(unique_id, timeout_left)
365
+
366
+    async def _send(self, message):
367
+        LOGGER.info("%s: send %s", self.id, message)
368
+        await self._connection.send(message)

+ 163 - 0
ocpp/ocpp/exceptions.py

@@ -0,0 +1,163 @@
1
+class OCPPError(Exception):
2
+    """Base class for all OCPP errors. It shouldn't be raised, only it
3
+    subclasses.
4
+    """
5
+
6
+    default_description = ""
7
+
8
+    def __init__(self, description=None, details=None):
9
+        self.description = description
10
+        if description is None:
11
+            self.description = self.default_description
12
+
13
+        self.details = details
14
+        if self.details is None:
15
+            self.details = {}
16
+
17
+    def __eq__(self, other):
18
+        if other.__class__ is self.__class__:
19
+            return (self.description, self.details) == (
20
+                other.description,
21
+                other.details,
22
+            )
23
+
24
+        return NotImplemented
25
+
26
+    def __repr__(self):
27
+        return (
28
+            f"<{self.__class__.__name__} - description={self.description},"
29
+            f" details={self.details}>"
30
+        )
31
+
32
+    def __str__(self):
33
+        return f"{self.__class__.__name__}: {self.description}," f" {self.details}"
34
+
35
+
36
+class NotImplementedError(OCPPError):
37
+    code = "NotImplemented"
38
+    default_description = (
39
+        "Request Action is recognized but not supported by the receiver"
40
+    )
41
+
42
+
43
+class NotSupportedError(OCPPError):
44
+    code = "NotSupported"
45
+    default_description = "Requested Action is not known by receiver"
46
+
47
+
48
+class InternalError(OCPPError):
49
+    code = "InternalError"
50
+    default_description = (
51
+        "An internal error occurred and the receiver was "
52
+        "able to process the requested Action successfully"
53
+    )
54
+
55
+
56
+class ProtocolError(OCPPError):
57
+    code = "ProtocolError"
58
+    default_description = "Payload for Action is incomplete"
59
+
60
+
61
+class SecurityError(OCPPError):
62
+    code = "SecurityError"
63
+    default_description = (
64
+        "During the processing of Action a security issue "
65
+        "occurred preventing receiver from completing the "
66
+        "Action successfully"
67
+    )
68
+
69
+
70
+class FormatViolationError(OCPPError):
71
+    """
72
+    Not strict OCPP 1.6 - see FormationViolationError
73
+    Valid OCPP 2.0.1
74
+    """
75
+
76
+    code = "FormatViolation"
77
+    default_description = (
78
+        "Payload for Action is syntactically incorrect or " "structure for Action"
79
+    )
80
+
81
+
82
+class FormationViolationError(OCPPError):
83
+    """
84
+    To allow for strict OCPP 1.6 compliance
85
+        5. Known issues that will not be fixed
86
+        5.2. Page 14, par 4.2.3. CallError: incorrect name in enum: FormationViolation
87
+        Incorrect name in enum: FormationViolation
88
+    """
89
+
90
+    code = "FormationViolation"
91
+    default_description = (
92
+        "Payload for Action is syntactically incorrect or structure for Action"
93
+    )
94
+
95
+
96
+class PropertyConstraintViolationError(OCPPError):
97
+    code = "PropertyConstraintViolation"
98
+    default_description = (
99
+        "Payload is syntactically correct but at least "
100
+        "one field contains an invalid value"
101
+    )
102
+
103
+
104
+class OccurenceConstraintViolationError(OCPPError):
105
+    """
106
+    To allow for strict OCPP 1.6 compliance
107
+    ocpp-j-1.6-errata-sheet.pdf
108
+        5. Known issues that will not be fixed
109
+        5.1. Page 14, par 4.2.3: CallError: Typo in enum
110
+        Typo in enum: OccurenceConstraintViolation
111
+    Valid in 2.0.1
112
+    """
113
+
114
+    code = "OccurenceConstraintViolation"
115
+    default_description = (
116
+        "Payload for Action is syntactically correct but "
117
+        "at least one of the fields violates occurence "
118
+        "constraints"
119
+    )
120
+
121
+
122
+class OccurrenceConstraintViolationError(OCPPError):
123
+    """
124
+    Not strict OCPP 1.6 - see OccurenceConstraintViolationError
125
+    Not valid OCPP 2.0.1
126
+    Valid in OCPP 2.1
127
+    """
128
+
129
+    code = "OccurrenceConstraintViolation"
130
+    default_description = (
131
+        "Payload for Action is syntactically correct but "
132
+        "at least one of the fields violates occurence "
133
+        "constraints"
134
+    )
135
+
136
+
137
+class TypeConstraintViolationError(OCPPError):
138
+    code = "TypeConstraintViolation"
139
+    default_description = (
140
+        "Payload for Action is syntactically correct but "
141
+        "at least one of the fields violates data type "
142
+        "constraints (e.g. “somestring”: 12)"
143
+    )
144
+
145
+
146
+class GenericError(OCPPError):
147
+    code = "GenericError"
148
+    default_description = "Any other error not all other OCPP defined errors"
149
+
150
+
151
+class ValidationError(Exception):
152
+    """ValidationError should be raised if validation a message payload fails.
153
+
154
+    Note this isn't an official OCPP error!
155
+    """
156
+
157
+    pass
158
+
159
+
160
+class UnknownCallErrorCodeError(Exception):
161
+    """Raised when a CALLERROR is received with unknown error code."""
162
+
163
+    pass

+ 450 - 0
ocpp/ocpp/messages.py

@@ -0,0 +1,450 @@
1
+""" Module containing classes that model the several OCPP messages types. It
2
+also contain some helper functions for packing and unpacking messages.  """
3
+from __future__ import annotations
4
+
5
+import decimal
6
+import json
7
+import os
8
+from dataclasses import asdict, is_dataclass
9
+from typing import Callable, Dict, Union
10
+
11
+from jsonschema import Draft4Validator
12
+from jsonschema.exceptions import ValidationError as SchemaValidationError
13
+
14
+from ocpp.exceptions import (
15
+    FormatViolationError,
16
+    NotImplementedError,
17
+    OCPPError,
18
+    PropertyConstraintViolationError,
19
+    ProtocolError,
20
+    TypeConstraintViolationError,
21
+    UnknownCallErrorCodeError,
22
+    ValidationError,
23
+)
24
+
25
+_validators: Dict[str, Draft4Validator] = {}
26
+
27
+
28
+class _DecimalEncoder(json.JSONEncoder):
29
+    """Encode values of type `decimal.Decimal` using 1 decimal point.
30
+
31
+    A custom encoder is required because `json.dumps()` cannot encode a value
32
+    of type decimal.Decimal. This raises a TypeError:
33
+
34
+        >>> import decimal
35
+        >>> import json
36
+        >>> >>> json.dumps(decimal.Decimal(3))
37
+        Traceback (most recent call last):
38
+          File "<stdin>", line 1, in <module>
39
+          File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/__init__.py", line 231, in dumps  # noqa
40
+            return _default_encoder.encode(obj)
41
+          File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 199, in encode
42
+            chunks = self.iterencode(o, _one_shot=True)
43
+          File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 257, in iterencode
44
+            return _iterencode(o, 0)
45
+          File "/home/developer/.pyenv/versions/3.7.0/lib/python3.7/json/encoder.py", line 179, in default
46
+            raise TypeError(f'Object of type {o.__class__.__name__} '
47
+        TypeError: Object of type Decimal is not JSON serializable
48
+
49
+    This can be prevented by using a custom encoder.
50
+
51
+    """
52
+
53
+    def default(self, obj):
54
+        if isinstance(obj, decimal.Decimal):
55
+            return float("%.1f" % obj)
56
+        try:
57
+            return json.JSONEncoder.default(self, obj)
58
+        except TypeError as e:
59
+            try:
60
+                return obj.to_json()
61
+            except AttributeError:
62
+                raise e
63
+
64
+
65
+class MessageType:
66
+    """Number identifying the different types of OCPP messages."""
67
+
68
+    #: Call identifies a request.
69
+    Call = 2
70
+
71
+    #: CallResult identifies a successful response.
72
+    CallResult = 3
73
+
74
+    #: CallError identifies an erroneous response.
75
+    CallError = 4
76
+
77
+
78
+def unpack(msg):
79
+    """
80
+    Unpacks a message into either a Call, CallError or CallResult.
81
+    """
82
+    try:
83
+        msg = json.loads(msg)
84
+    except json.JSONDecodeError:
85
+        raise FormatViolationError(
86
+            details={"cause": "Message is not valid JSON", "ocpp_message": msg}
87
+        )
88
+
89
+    if not isinstance(msg, list):
90
+        raise ProtocolError(
91
+            details={
92
+                "cause": (
93
+                    "OCPP message hasn't the correct format. It "
94
+                    f"should be a list, but got '{type(msg)}' "
95
+                    "instead"
96
+                )
97
+            }
98
+        )
99
+
100
+    for cls in [Call, CallResult, CallError]:
101
+        try:
102
+            if msg[0] == cls.message_type_id:
103
+                return cls(*msg[1:])
104
+        except IndexError:
105
+            raise ProtocolError(
106
+                details={"cause": "Message does not contain MessageTypeId"}
107
+            )
108
+        except TypeError:
109
+            raise ProtocolError(details={"cause": "Message is missing elements."})
110
+
111
+    raise PropertyConstraintViolationError(
112
+        details={"cause": f"MessageTypeId '{msg[0]}' isn't valid"}
113
+    )
114
+
115
+
116
+def pack(msg):
117
+    """
118
+    Returns the JSON representation of a Call, CallError or CallResult.
119
+
120
+    It just calls the 'to_json()' method of the message. But it is here mainly
121
+    to complement the 'unpack' function of this module.
122
+    """
123
+    return msg.to_json()
124
+
125
+
126
+def get_validator(
127
+    message_type_id: int, action: str, ocpp_version: str, parse_float: Callable = float
128
+) -> Draft4Validator:
129
+    """
130
+    Read schema from disk and return as `Draft4Validator`. Instances will be
131
+    cached for performance reasons.
132
+
133
+    The `parse_float` argument can be used to set the conversion method that
134
+    is used to parse floats. It must be a callable taking 1 argument. By
135
+    default it is `float()`, but certain schema's require `decimal.Decimal()`.
136
+    """
137
+    if ocpp_version not in ["1.6", "2.0", "2.0.1"]:
138
+        raise ValueError
139
+
140
+    schemas_dir = "v" + ocpp_version.replace(".", "")
141
+
142
+    schema_name = action
143
+    if message_type_id == MessageType.CallResult:
144
+        schema_name += "Response"
145
+    elif message_type_id == MessageType.Call:
146
+        if ocpp_version in ["2.0", "2.0.1"]:
147
+            schema_name += "Request"
148
+
149
+    if ocpp_version == "2.0":
150
+        schema_name += "_v1p0"
151
+
152
+    cache_key = schema_name + "_" + ocpp_version
153
+    if cache_key in _validators:
154
+        return _validators[cache_key]
155
+
156
+    dir, _ = os.path.split(os.path.realpath(__file__))
157
+    relative_path = f"{schemas_dir}/schemas/{schema_name}.json"
158
+    path = os.path.join(dir, relative_path)
159
+
160
+    # The JSON schemas for OCPP 2.0 start with a byte order mark (BOM)
161
+    # character. If no encoding is given, reading the schema would fail with:
162
+    #
163
+    #     Unexpected UTF-8 BOM (decode using utf-8-sig):
164
+    with open(path, "r", encoding="utf-8-sig") as f:
165
+        data = f.read()
166
+        validator = Draft4Validator(json.loads(data, parse_float=parse_float))
167
+        _validators[cache_key] = validator
168
+
169
+    return _validators[cache_key]
170
+
171
+
172
+def validate_payload(message: Union[Call, CallResult], ocpp_version: str) -> None:
173
+    """Validate the payload of the message using JSON schemas."""
174
+    if type(message) not in [Call, CallResult]:
175
+        raise ValidationError(
176
+            "Payload can't be validated because message "
177
+            f"type. It's '{type(message)}', but it should "
178
+            "be either 'Call'  or 'CallResult'."
179
+        )
180
+
181
+    try:
182
+        # 3 OCPP 1.6 schedules have fields of type floats. The JSON schema
183
+        # defines a certain precision for these fields of 1 decimal. A value of
184
+        # 21.4 is valid, whereas a value if 4.11 is not.
185
+        #
186
+        # The problem is that Python's internal representation of 21.4 might
187
+        # have more than 1 decimal. It might be 21.399999999999995. This would
188
+        # make the validation fail, although the payload is correct. This is a
189
+        # known issue with jsonschemas, see:
190
+        # https://github.com/Julian/jsonschema/issues/247
191
+        #
192
+        # This issue can be fixed by using a different parser for floats than
193
+        # the default one that is used.
194
+        #
195
+        # Both the schema and the payload must be parsed using the different
196
+        # parser for floats.
197
+        if ocpp_version == "1.6" and (
198
+            (
199
+                type(message) == Call
200
+                and message.action in ["SetChargingProfile", "RemoteStartTransaction"]
201
+            )  # noqa
202
+            or (
203
+                type(message) == CallResult and message.action == "GetCompositeSchedule"
204
+            )
205
+        ):
206
+            validator = get_validator(
207
+                message.message_type_id,
208
+                message.action,
209
+                ocpp_version,
210
+                parse_float=decimal.Decimal,
211
+            )
212
+
213
+            message.payload = json.loads(
214
+                json.dumps(message.payload), parse_float=decimal.Decimal
215
+            )
216
+        else:
217
+            validator = get_validator(
218
+                message.message_type_id, message.action, ocpp_version
219
+            )
220
+    except (OSError, json.JSONDecodeError):
221
+        raise NotImplementedError(
222
+            details={"cause": f"Failed to validate action: {message.action}"}
223
+        )
224
+
225
+    try:
226
+        validator.validate(message.payload)
227
+    except SchemaValidationError as e:
228
+        if e.validator == "type":
229
+            raise TypeConstraintViolationError(
230
+                details={"cause": e.message, "ocpp_message": message}
231
+            )
232
+        elif e.validator == "additionalProperties":
233
+            raise FormatViolationError(
234
+                details={"cause": e.message, "ocpp_message": message}
235
+            )
236
+        elif e.validator == "required":
237
+            raise ProtocolError(details={"cause": e.message})
238
+
239
+        elif e.validator == "maxLength":
240
+            raise TypeConstraintViolationError(
241
+                details={"cause": e.message, "ocpp_message": message}
242
+            ) from e
243
+        else:
244
+            raise FormatViolationError(
245
+                details={
246
+                    "cause": f"Payload '{message.payload}' for action "
247
+                    f"'{message.action}' is not valid: {e}",
248
+                    "ocpp_message": message,
249
+                }
250
+            )
251
+
252
+
253
+class Call:
254
+    """A Call is a type of message that initiate a request/response sequence.
255
+    Both central systems and charge points can send this message.
256
+
257
+    From the specification:
258
+
259
+        A Call always consists of 4 elements: The standard elements
260
+        MessageTypeId and UniqueId, a specific Action that is required on the
261
+        other side and a payload, the arguments to the Action. The syntax of a
262
+        call looks like this:
263
+
264
+            [<MessageTypeId>, "<UniqueId>", "<Action>", {<Payload>}]
265
+
266
+        ...
267
+
268
+        For example, a BootNotification request could look like this:
269
+
270
+            [2,
271
+             "19223201",
272
+             "BootNotification",
273
+             {
274
+              "chargePointVendor": "VendorX",
275
+              "chargePointModel": "SingleSocketCharger"
276
+             }
277
+            ]
278
+    """
279
+
280
+    message_type_id = 2
281
+
282
+    def __init__(self, unique_id, action, payload):
283
+        self.unique_id = unique_id
284
+        self.action = action
285
+        self.payload = payload
286
+
287
+        if is_dataclass(payload):
288
+            self.payload = asdict(payload)
289
+
290
+    def to_json(self):
291
+        """Return a valid JSON representation of the instance."""
292
+        return json.dumps(
293
+            [
294
+                self.message_type_id,
295
+                self.unique_id,
296
+                self.action,
297
+                self.payload,
298
+            ],
299
+            # By default json.dumps() adds a white space after every separator.
300
+            # By setting the separator manually that can be avoided.
301
+            separators=(",", ":"),
302
+            cls=_DecimalEncoder,
303
+        )
304
+
305
+    def create_call_result(self, payload):
306
+        call_result = CallResult(self.unique_id, payload)
307
+        call_result.action = self.action
308
+        return call_result
309
+
310
+    def create_call_error(self, exception):
311
+        error_code = "InternalError"
312
+        error_description = "An unexpected error occurred."
313
+        error_details = {}
314
+
315
+        if isinstance(exception, OCPPError):
316
+            error_code = exception.code
317
+            error_description = exception.description
318
+            error_details = exception.details
319
+
320
+        return CallError(
321
+            self.unique_id,
322
+            error_code,
323
+            error_description,
324
+            error_details,
325
+        )
326
+
327
+    def __repr__(self):
328
+        return (
329
+            f"<Call - unique_id={self.unique_id}, action={self.action}, "
330
+            f"payload={self.payload}>"
331
+        )
332
+
333
+
334
+class CallResult:
335
+    """
336
+    A CallResult is a message indicating that a Call has been handled
337
+    successfully.
338
+
339
+    From the specification:
340
+
341
+        A CallResult always consists of 3 elements: The standard elements
342
+        MessageTypeId, UniqueId and a payload, containing the response to the
343
+        Action in the original Call. The syntax of a call looks like this:
344
+
345
+            [<MessageTypeId>, "<UniqueId>", {<Payload>}]
346
+
347
+        ...
348
+
349
+        For example, a BootNotification response could look like this:
350
+
351
+            [3,
352
+             "19223201",
353
+             {
354
+              "status":"Accepted",
355
+              "currentTime":"2013-02-01T20:53:32.486Z",
356
+              "heartbeatInterval":300
357
+             }
358
+            ]
359
+
360
+    """
361
+
362
+    message_type_id = 3
363
+
364
+    def __init__(self, unique_id, payload, action=None):
365
+        self.unique_id = unique_id
366
+        self.payload = payload
367
+
368
+        # Strictly speaking no action is required in a CallResult. But in order
369
+        # to validate the message it is needed.
370
+        self.action = action
371
+
372
+    def to_json(self):
373
+        return json.dumps(
374
+            [
375
+                self.message_type_id,
376
+                self.unique_id,
377
+                self.payload,
378
+            ],
379
+            # By default json.dumps() adds a white space after every separator.
380
+            # By setting the separator manually that can be avoided.
381
+            separators=(",", ":"),
382
+            cls=_DecimalEncoder,
383
+        )
384
+
385
+    def __repr__(self):
386
+        return (
387
+            f"<CallResult - unique_id={self.unique_id}, "
388
+            f"action={self.action}, "
389
+            f"payload={self.payload}>"
390
+        )
391
+
392
+
393
+class CallError:
394
+    """
395
+    A CallError is a response to a Call that indicates an error.
396
+
397
+    From the specification:
398
+
399
+        CallError always consists of 5 elements: The standard elements
400
+        MessageTypeId and UniqueId, an errorCode string, an errorDescription
401
+        string and an errorDetails object.
402
+
403
+        The syntax of a call looks like this:
404
+
405
+            [<MessageTypeId>, "<UniqueId>", "<errorCode>", "<errorDescription>", {<errorDetails>}] # noqa
406
+    """
407
+
408
+    message_type_id = 4
409
+
410
+    def __init__(self, unique_id, error_code, error_description, error_details=None):
411
+        self.unique_id = unique_id
412
+        self.error_code = error_code
413
+        self.error_description = error_description
414
+        self.error_details = error_details
415
+
416
+    def to_json(self):
417
+        return json.dumps(
418
+            [
419
+                self.message_type_id,
420
+                self.unique_id,
421
+                self.error_code,
422
+                self.error_description,
423
+                self.error_details,
424
+            ],
425
+            # By default json.dumps() adds a white space after every separator.
426
+            # By setting the separator manually that can be avoided.
427
+            separators=(",", ":"),
428
+            cls=_DecimalEncoder,
429
+        )
430
+
431
+    def to_exception(self):
432
+        """Return the exception that corresponds to the CallError."""
433
+        for error in OCPPError.__subclasses__():
434
+            if error.code == self.error_code:
435
+                return error(
436
+                    description=self.error_description, details=self.error_details
437
+                )
438
+
439
+        raise UnknownCallErrorCodeError(
440
+            f"Error code '{self.error_code}' is not defined by the"
441
+            " OCPP specification"
442
+        )
443
+
444
+    def __repr__(self):
445
+        return (
446
+            f"<CallError - unique_id={self.unique_id}, "
447
+            f"error_code={self.error_code}, "
448
+            f"error_description={self.error_description}, "
449
+            f"error_details={self.error_details}>"
450
+        )

+ 139 - 0
ocpp/ocpp/routing.py

@@ -0,0 +1,139 @@
1
+import functools
2
+
3
+routables = []
4
+
5
+
6
+def on(action, *, skip_schema_validation=False):
7
+    """
8
+    Function decorator to mark function as handler for specific action. The
9
+    wrapped function may be async or sync.
10
+
11
+    The handler function will receive keyword arguments derived from the
12
+    payload of the specific action. It's recommended you use `**kwargs` in your
13
+    definition to ignore any extra arguments that may be added in the future.
14
+
15
+    The handler function should return a relevant payload to be returned to the
16
+    Charge Point.
17
+
18
+    It can be used like so:
19
+
20
+    ```
21
+    class MyChargePoint(cp):
22
+        @on(Action.BootNotification):
23
+        async def on_boot_notification(
24
+            self,
25
+            charge_point_model,
26
+            charge_point_vendor,
27
+            **kwargs,
28
+        ):
29
+            print(f'{charge_point_model} from {charge_point_vendor} booted.')
30
+
31
+            now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + "Z"
32
+            return call_result.BootNotificationPayload(
33
+                current_time=now,
34
+                interval=30,
35
+                status="Accepted",
36
+            )
37
+    ```
38
+
39
+    The decorator takes an optional argument `skip_schema_validation` which
40
+    defaults to False. Setting this argument to `True` will disable schema
41
+    validation of the request and the response of the specific route.
42
+
43
+    """
44
+
45
+    def decorator(func):
46
+        @functools.wraps(func)
47
+        def inner(*args, **kwargs):
48
+            return func(*args, **kwargs)
49
+
50
+        inner._on_action = action
51
+        inner._skip_schema_validation = skip_schema_validation
52
+        if func.__name__ not in routables:
53
+            routables.append(func.__name__)
54
+        return inner
55
+
56
+    return decorator
57
+
58
+
59
+def after(action):
60
+    """Function decorator to mark function as hook to post-request hook.
61
+
62
+    This hook's arguments are the data that is in the payload for the specific
63
+    action.
64
+
65
+    It can be used like so:
66
+
67
+        @after(Action.BootNotification):
68
+        def after_boot_notification():
69
+            pass
70
+
71
+    """
72
+
73
+    def decorator(func):
74
+        @functools.wraps(func)
75
+        def inner(*args, **kwargs):
76
+            return func(*args, **kwargs)
77
+
78
+        inner._after_action = action
79
+        if func.__name__ not in routables:
80
+            routables.append(func.__name__)
81
+        return inner
82
+
83
+    return decorator
84
+
85
+
86
+def create_route_map(obj):
87
+    """
88
+    Iterates of all attributes of the class looking for attributes which
89
+    have been decorated by the @on() decorator It returns a dictionary where
90
+    the action name are the keys and the decorated functions are the values.
91
+
92
+    To illustrate this with an example, consider the following function:
93
+
94
+        class ChargePoint:
95
+
96
+            @on(Action.BootNotification)
97
+            def on_boot_notification(self, *args, **kwargs):
98
+                pass
99
+
100
+            @after(Action.BootNotification)
101
+            def after_boot_notification(self, *args, **kwargs):
102
+                pass
103
+
104
+
105
+    In this case this returns:
106
+
107
+        {
108
+            Action.BootNotification: {
109
+                '_on_action': <reference to 'on_boot_notification'>,
110
+                '_after_action': <reference to 'after_boot_notification'>,
111
+                '_skip_schema_validation': False,
112
+            },
113
+        }
114
+
115
+    """
116
+    routes = {}
117
+    for attr_name in routables:
118
+        for option in ["_on_action", "_after_action"]:
119
+            try:
120
+                attr = getattr(obj, attr_name)
121
+                action = getattr(attr, option)
122
+
123
+                if action not in routes:
124
+                    routes[action] = {}
125
+
126
+                # Routes decorated with the `@on()` decorator can be configured
127
+                # to skip validation of the input and output. For more info see
128
+                # the docstring of `on()`.
129
+                if option == "_on_action":
130
+                    routes[action]["_skip_schema_validation"] = getattr(
131
+                        attr, "_skip_schema_validation", False
132
+                    )
133
+
134
+                routes[action][option] = attr
135
+
136
+            except AttributeError:
137
+                continue
138
+
139
+    return routes

+ 8 - 0
ocpp/ocpp/v16/__init__.py

@@ -0,0 +1,8 @@
1
+from ocpp.charge_point import ChargePoint as cp
2
+from ocpp.v16 import call, call_result
3
+
4
+
5
+class ChargePoint(cp):
6
+    _call = call
7
+    _call_result = call_result
8
+    _ocpp_version = "1.6"

+ 303 - 0
ocpp/ocpp/v16/call.py

@@ -0,0 +1,303 @@
1
+from dataclasses import dataclass, field
2
+from typing import Dict, List, Optional
3
+
4
+from ocpp.v16.enums import (
5
+    AvailabilityType,
6
+    CertificateUse,
7
+    ChargePointErrorCode,
8
+    ChargePointStatus,
9
+    ChargingProfilePurposeType,
10
+    ChargingRateUnitType,
11
+    DiagnosticsStatus,
12
+    FirmwareStatus,
13
+    Log,
14
+    MessageTrigger,
15
+    Reason,
16
+    ResetType,
17
+    UpdateType,
18
+    UploadLogStatus,
19
+)
20
+
21
+# Most types of CALL messages can originate from only 1 source, either
22
+# from a Charge Point or Central System, but not from both.
23
+#
24
+# Take for example the CALL for an ChangeConfiguration action. This type of
25
+# CALL can only be send from a Central System to Charging Station, not
26
+# the other way around.
27
+#
28
+# For some types of CALL messages the opposite is true; for example for the
29
+# CALL message for a BootNotification action. This can only come from a Charge
30
+# Point and send to a Central System.
31
+#
32
+# The only CALL that can originate from both a Central System and a
33
+# Charge Point is the CALL message for a DataTransfer.
34
+
35
+# The now following section of classes are for CALL messages that flow
36
+# from Central System to Charge Point.
37
+
38
+
39
+@dataclass
40
+class CancelReservationPayload:
41
+    reservation_id: int
42
+
43
+
44
+@dataclass
45
+class CertificateSignedPayload:
46
+    certificate_chain: str
47
+
48
+
49
+@dataclass
50
+class ChangeAvailabilityPayload:
51
+    connector_id: int
52
+    type: AvailabilityType
53
+
54
+
55
+@dataclass
56
+class ChangeConfigurationPayload:
57
+    key: str
58
+    value: str
59
+
60
+
61
+@dataclass
62
+class ClearCachePayload:
63
+    pass
64
+
65
+
66
+@dataclass
67
+class ClearChargingProfilePayload:
68
+    id: Optional[int] = None
69
+    connector_id: Optional[int] = None
70
+    charging_profile_purpose: Optional[ChargingProfilePurposeType] = None
71
+    stack_level: Optional[int] = None
72
+
73
+
74
+@dataclass
75
+class DeleteCertificatePayload:
76
+    certificate_hash_data: Dict
77
+
78
+
79
+@dataclass
80
+class ExtendedTriggerMessagePayload:
81
+    requested_message: MessageTrigger
82
+    connector_id: Optional[int] = None
83
+
84
+
85
+@dataclass
86
+class GetCompositeSchedulePayload:
87
+    connector_id: int
88
+    duration: int
89
+    charging_rate_unit: Optional[ChargingRateUnitType] = None
90
+
91
+
92
+@dataclass
93
+class GetConfigurationPayload:
94
+    key: Optional[List] = None
95
+
96
+
97
+@dataclass
98
+class GetDiagnosticsPayload:
99
+    location: str
100
+    retries: Optional[int] = None
101
+    retry_interval: Optional[int] = None
102
+    start_time: Optional[str] = None
103
+    stop_time: Optional[str] = None
104
+
105
+
106
+@dataclass
107
+class GetInstalledCertificateIdsPayload:
108
+    certificate_type: CertificateUse
109
+
110
+
111
+@dataclass
112
+class GetLocalListVersionPayload:
113
+    pass
114
+
115
+
116
+@dataclass
117
+class GetLogPayload:
118
+    log: Dict
119
+    log_type: Log
120
+    request_id: int
121
+    retries: Optional[int] = None
122
+    retry_interval: Optional[int] = None
123
+
124
+
125
+@dataclass
126
+class InstallCertificatePayload:
127
+    certificate_type: CertificateUse
128
+    certificate: str
129
+
130
+
131
+@dataclass
132
+class RemoteStartTransactionPayload:
133
+    id_tag: str
134
+    connector_id: Optional[int] = None
135
+    charging_profile: Optional[Dict] = None
136
+
137
+
138
+@dataclass
139
+class RemoteStopTransactionPayload:
140
+    transaction_id: int
141
+
142
+
143
+@dataclass
144
+class ReserveNowPayload:
145
+    connector_id: int
146
+    expiry_date: str
147
+    id_tag: str
148
+    reservation_id: int
149
+    parent_id_tag: Optional[str] = None
150
+
151
+
152
+@dataclass
153
+class ResetPayload:
154
+    type: ResetType
155
+
156
+
157
+@dataclass
158
+class SendLocalListPayload:
159
+    list_version: int
160
+    update_type: UpdateType
161
+    local_authorization_list: List = field(default_factory=list)
162
+
163
+
164
+@dataclass
165
+class SetChargingProfilePayload:
166
+    connector_id: int
167
+    cs_charging_profiles: Dict
168
+
169
+
170
+@dataclass
171
+class SignedUpdateFirmwarePayload:
172
+    request_id: int
173
+    firmware: Dict
174
+    retries: Optional[int] = None
175
+    retry_interval: Optional[int] = None
176
+
177
+
178
+@dataclass
179
+class TriggerMessagePayload:
180
+    requested_message: MessageTrigger
181
+    connector_id: Optional[int] = None
182
+
183
+
184
+@dataclass
185
+class UnlockConnectorPayload:
186
+    connector_id: int
187
+
188
+
189
+@dataclass
190
+class UpdateFirmwarePayload:
191
+    location: str
192
+    retrieve_date: str
193
+    retries: Optional[int] = None
194
+    retry_interval: Optional[int] = None
195
+
196
+
197
+# The CALL messages that flow from Charge Point to Central System are listed
198
+# in the bottom part of this module.
199
+
200
+
201
+@dataclass
202
+class AuthorizePayload:
203
+    id_tag: str
204
+
205
+
206
+@dataclass
207
+class BootNotificationPayload:
208
+    charge_point_model: str
209
+    charge_point_vendor: str
210
+    charge_box_serial_number: Optional[str] = None
211
+    charge_point_serial_number: Optional[str] = None
212
+    firmware_version: Optional[str] = None
213
+    iccid: Optional[str] = None
214
+    imsi: Optional[str] = None
215
+    meter_serial_number: Optional[str] = None
216
+    meter_type: Optional[str] = None
217
+
218
+
219
+@dataclass
220
+class DiagnosticsStatusNotificationPayload:
221
+    status: DiagnosticsStatus
222
+
223
+
224
+@dataclass
225
+class FirmwareStatusNotificationPayload:
226
+    status: FirmwareStatus
227
+
228
+
229
+@dataclass
230
+class HeartbeatPayload:
231
+    pass
232
+
233
+
234
+@dataclass
235
+class LogStatusNotificationPayload:
236
+    status: UploadLogStatus
237
+    request_id: int
238
+
239
+
240
+@dataclass
241
+class MeterValuesPayload:
242
+    connector_id: int
243
+    meter_value: List = field(default_factory=list)
244
+    transaction_id: Optional[int] = None
245
+
246
+
247
+@dataclass
248
+class SecurityEventNotificationPayload:
249
+    type: str
250
+    timestamp: str
251
+    tech_info: Optional[str]
252
+
253
+
254
+@dataclass
255
+class SignCertificatePayload:
256
+    csr: str
257
+
258
+
259
+@dataclass
260
+class SignedFirmwareStatusNotificationPayload:
261
+    status: FirmwareStatus
262
+    request_id: int
263
+
264
+
265
+@dataclass
266
+class StartTransactionPayload:
267
+    connector_id: int
268
+    id_tag: str
269
+    meter_start: int
270
+    timestamp: str
271
+    reservation_id: Optional[int] = None
272
+
273
+
274
+@dataclass
275
+class StopTransactionPayload:
276
+    meter_stop: int
277
+    timestamp: str
278
+    transaction_id: int
279
+    reason: Optional[Reason] = None
280
+    id_tag: Optional[str] = None
281
+    transaction_data: Optional[List] = None
282
+
283
+
284
+@dataclass
285
+class StatusNotificationPayload:
286
+    connector_id: int
287
+    error_code: ChargePointErrorCode
288
+    status: ChargePointStatus
289
+    timestamp: Optional[str] = None
290
+    info: Optional[str] = None
291
+    vendor_id: Optional[str] = None
292
+    vendor_error_code: Optional[str] = None
293
+
294
+
295
+# The DataTransfer CALL can be send both from Central System as well as from a
296
+# Charge Point.
297
+
298
+
299
+@dataclass
300
+class DataTransferPayload:
301
+    vendor_id: str
302
+    message_id: Optional[str] = None
303
+    data: Optional[str] = None

+ 258 - 0
ocpp/ocpp/v16/call_result.py

@@ -0,0 +1,258 @@
1
+from dataclasses import dataclass
2
+from typing import Dict, List, Optional
3
+
4
+from ocpp.v16.datatypes import IdTagInfo
5
+from ocpp.v16.enums import (
6
+    AvailabilityStatus,
7
+    CancelReservationStatus,
8
+    CertificateSignedStatus,
9
+    CertificateStatus,
10
+    ChargingProfileStatus,
11
+    ClearCacheStatus,
12
+    ClearChargingProfileStatus,
13
+    ConfigurationStatus,
14
+    DataTransferStatus,
15
+    DeleteCertificateStatus,
16
+    GenericStatus,
17
+    GetCompositeScheduleStatus,
18
+    GetInstalledCertificateStatus,
19
+    LogStatus,
20
+    RegistrationStatus,
21
+    RemoteStartStopStatus,
22
+    ReservationStatus,
23
+    ResetStatus,
24
+    TriggerMessageStatus,
25
+    UnlockStatus,
26
+    UpdateFirmwareStatus,
27
+    UpdateStatus,
28
+)
29
+
30
+# Most types of CALLRESULT messages can originate from only 1 source, either
31
+# from a Charge Point or Central System, but not from both.
32
+#
33
+# Take for example the CALLRESULT for an Authorize action. This type of
34
+# CALLRESULT can only be send from a Central System to Charging Station, not
35
+# the other way around.
36
+#
37
+# For some types of CALLRESULT messages the opposite is true; for example for
38
+# the CALLRESULT message for a Reset action. This can only come from a Charge
39
+# Point to a Central System.
40
+#
41
+# The only CALLRESULT that can originate from both a Central System and a
42
+# Charge Point is the CALLRESULT message for a DataTransfer.
43
+
44
+# The now following section of classes are for CALLRESULT messages that flow
45
+# from Central System to Charge Point.
46
+
47
+
48
+@dataclass
49
+class AuthorizePayload:
50
+    id_tag_info: IdTagInfo
51
+
52
+
53
+@dataclass
54
+class BootNotificationPayload:
55
+    current_time: str
56
+    interval: int
57
+    status: RegistrationStatus
58
+
59
+
60
+@dataclass
61
+class DiagnosticsStatusNotificationPayload:
62
+    pass
63
+
64
+
65
+@dataclass
66
+class FirmwareStatusNotificationPayload:
67
+    pass
68
+
69
+
70
+@dataclass
71
+class HeartbeatPayload:
72
+    current_time: str
73
+
74
+
75
+@dataclass
76
+class LogStatusNotificationPayload:
77
+    pass
78
+
79
+
80
+@dataclass
81
+class SecurityEventNotificationPayload:
82
+    pass
83
+
84
+
85
+@dataclass
86
+class SignCertificatePayload:
87
+    status: GenericStatus
88
+
89
+
90
+@dataclass
91
+class MeterValuesPayload:
92
+    pass
93
+
94
+
95
+@dataclass
96
+class StartTransactionPayload:
97
+    transaction_id: int
98
+    id_tag_info: IdTagInfo
99
+
100
+
101
+@dataclass
102
+class StatusNotificationPayload:
103
+    pass
104
+
105
+
106
+@dataclass
107
+class StopTransactionPayload:
108
+    id_tag_info: Optional[IdTagInfo] = None
109
+
110
+
111
+# The CALLRESULT messages that flow from Charge Point to Central System are
112
+# listed in the bottom part of this module.
113
+
114
+
115
+@dataclass
116
+class CancelReservationPayload:
117
+    status: CancelReservationStatus
118
+
119
+
120
+@dataclass
121
+class CertificateSignedPayload:
122
+    status: CertificateSignedStatus
123
+
124
+
125
+@dataclass
126
+class ChangeAvailabilityPayload:
127
+    status: AvailabilityStatus
128
+
129
+
130
+@dataclass
131
+class ChangeConfigurationPayload:
132
+    status: ConfigurationStatus
133
+
134
+
135
+@dataclass
136
+class ClearCachePayload:
137
+    status: ClearCacheStatus
138
+
139
+
140
+@dataclass
141
+class ClearChargingProfilePayload:
142
+    status: ClearChargingProfileStatus
143
+
144
+
145
+@dataclass
146
+class DeleteCertificatePayload:
147
+    status: DeleteCertificateStatus
148
+
149
+
150
+@dataclass
151
+class ExtendedTriggerMessagePayload:
152
+    status: TriggerMessageStatus
153
+
154
+
155
+@dataclass
156
+class GetInstalledCertificateIdsPayload:
157
+    status: GetInstalledCertificateStatus
158
+    certificate_hash_data: Optional[List] = None
159
+
160
+
161
+@dataclass
162
+class GetCompositeSchedulePayload:
163
+    status: GetCompositeScheduleStatus
164
+    connector_id: Optional[int] = None
165
+    schedule_start: Optional[str] = None
166
+    charging_schedule: Optional[Dict] = None
167
+
168
+
169
+@dataclass
170
+class GetConfigurationPayload:
171
+    configuration_key: Optional[List] = None
172
+    unknown_key: Optional[List] = None
173
+
174
+
175
+@dataclass
176
+class GetDiagnosticsPayload:
177
+    file_name: Optional[str] = None
178
+
179
+
180
+@dataclass
181
+class GetLocalListVersionPayload:
182
+    list_version: int
183
+
184
+
185
+@dataclass
186
+class GetLogPayload:
187
+    status: LogStatus
188
+    filename: Optional[str] = None
189
+
190
+
191
+@dataclass
192
+class InstallCertificatePayload:
193
+    status: CertificateStatus
194
+
195
+
196
+@dataclass
197
+class RemoteStartTransactionPayload:
198
+    status: RemoteStartStopStatus
199
+
200
+
201
+@dataclass
202
+class RemoteStopTransactionPayload:
203
+    status: RemoteStartStopStatus
204
+
205
+
206
+@dataclass
207
+class ReserveNowPayload:
208
+    status: ReservationStatus
209
+
210
+
211
+@dataclass
212
+class ResetPayload:
213
+    status: ResetStatus
214
+
215
+
216
+@dataclass
217
+class SendLocalListPayload:
218
+    status: UpdateStatus
219
+
220
+
221
+@dataclass
222
+class SetChargingProfilePayload:
223
+    status: ChargingProfileStatus
224
+
225
+
226
+@dataclass
227
+class SignedFirmwareStatusNotificationPayload:
228
+    pass
229
+
230
+
231
+@dataclass
232
+class SignedUpdateFirmwarePayload:
233
+    status: UpdateFirmwareStatus
234
+
235
+
236
+@dataclass
237
+class TriggerMessagePayload:
238
+    status: TriggerMessageStatus
239
+
240
+
241
+@dataclass
242
+class UnlockConnectorPayload:
243
+    status: UnlockStatus
244
+
245
+
246
+@dataclass
247
+class UpdateFirmwarePayload:
248
+    pass
249
+
250
+
251
+# The DataTransfer CALLRESULT can be send both from Central System as well as
252
+# from a Charge Point.
253
+
254
+
255
+@dataclass
256
+class DataTransferPayload:
257
+    status: DataTransferStatus
258
+    data: Optional[str] = None

+ 166 - 0
ocpp/ocpp/v16/datatypes.py

@@ -0,0 +1,166 @@
1
+from dataclasses import dataclass
2
+from typing import List, Optional
3
+
4
+from ocpp.v16.enums import (
5
+    AuthorizationStatus,
6
+    ChargingProfileKindType,
7
+    ChargingProfilePurposeType,
8
+    ChargingRateUnitType,
9
+    CiStringType,
10
+    HashAlgorithm,
11
+    Location,
12
+    Measurand,
13
+    Phase,
14
+    ReadingContext,
15
+    RecurrencyKind,
16
+    UnitOfMeasure,
17
+    ValueFormat,
18
+)
19
+
20
+
21
+@dataclass
22
+class IdTagInfo:
23
+    """
24
+    Contains status information about an identifier. It is returned in
25
+    Authorize, Start Transaction and Stop Transaction responses.
26
+
27
+    If expiryDate is not given, the status has no end date.
28
+    """
29
+
30
+    status: AuthorizationStatus
31
+    parent_id_tag: Optional[str] = None
32
+    expiry_date: Optional[str] = None
33
+
34
+
35
+@dataclass
36
+class AuthorizationData:
37
+    """
38
+    Elements that constitute an entry of a Local Authorization List update.
39
+    """
40
+
41
+    id_tag: str
42
+    id_tag_info: Optional[IdTagInfo] = None
43
+
44
+
45
+@dataclass
46
+class ChargingSchedulePeriod:
47
+    start_period: int
48
+    limit: float
49
+    number_phases: Optional[int] = None
50
+
51
+
52
+@dataclass
53
+class ChargingSchedule:
54
+    charging_rate_unit: ChargingRateUnitType
55
+    charging_schedule_period: List[ChargingSchedulePeriod]
56
+    duration: Optional[int] = None
57
+    start_schedule: Optional[str] = None
58
+    min_charging_rate: Optional[float] = None
59
+
60
+
61
+@dataclass
62
+class ChargingProfile:
63
+    """
64
+    A ChargingProfile consists of a ChargingSchedule, describing the
65
+    amount of power or current that can be delivered per time interval.
66
+    """
67
+
68
+    charging_profile_id: int
69
+    stack_level: int
70
+    charging_profile_purpose: ChargingProfilePurposeType
71
+    charging_profile_kind: ChargingProfileKindType
72
+    charging_schedule: ChargingSchedule
73
+    transaction_id: Optional[int] = None
74
+    recurrency_kind: Optional[RecurrencyKind] = None
75
+    valid_from: Optional[str] = None
76
+    valid_to: Optional[str] = None
77
+
78
+
79
+@dataclass
80
+class KeyValue:
81
+    """
82
+    Contains information about a specific configuration key.
83
+    It is returned in GetConfiguration.conf.
84
+    """
85
+
86
+    key: str
87
+    readonly: bool
88
+    value: Optional[str] = None
89
+
90
+    def __post_init__(self):
91
+        if len(self.key) > CiStringType.ci_string_50:
92
+            msg = "Field key is longer than 50 characters"
93
+            raise ValueError(msg)
94
+
95
+        if self.value and len(self.value) > CiStringType.ci_string_500:
96
+            msg = "Field key is longer than 500 characters"
97
+            raise ValueError(msg)
98
+
99
+
100
+@dataclass
101
+class SampledValue:
102
+    """
103
+    Single sampled value in MeterValues. Each value can be accompanied by
104
+    optional fields.
105
+    """
106
+
107
+    value: str
108
+    context: ReadingContext
109
+    format: Optional[ValueFormat] = None
110
+    measurand: Optional[Measurand] = None
111
+    phase: Optional[Phase] = None
112
+    location: Optional[Location] = None
113
+    unit: Optional[UnitOfMeasure] = None
114
+
115
+
116
+@dataclass
117
+class MeterValue:
118
+    """
119
+    Collection of one or more sampled values in MeterValues.req.
120
+    All sampled values in a MeterValue are sampled at the same point in time.
121
+    """
122
+
123
+    timestamp: str
124
+    sampled_value: List[SampledValue]
125
+
126
+
127
+# Security Extension
128
+
129
+
130
+@dataclass
131
+class CertificateHashData:
132
+    """
133
+    CertificateHashDataType is used by:
134
+    DeleteCertificate.req, GetInstalledCertificateIds.conf
135
+    """
136
+
137
+    hash_algorithm: HashAlgorithm
138
+    issuer_name_hash: str
139
+    issuer_key_hash: str
140
+    serial_number: str
141
+
142
+
143
+@dataclass
144
+class Firmware:
145
+    """
146
+    Represents a copy of the firmware that can be loaded/updated on the Charge Point.
147
+    FirmwareType is used by: SignedUpdateFirmware.req
148
+    """
149
+
150
+    location: str
151
+    retrieve_date_time: str
152
+    signing_certificate: str
153
+    install_date_time: Optional[str] = None
154
+    signature: Optional[str] = None
155
+
156
+
157
+@dataclass
158
+class LogParameters:
159
+    """
160
+    Class for detailed information the retrieval of logging entries.
161
+    LogParametersType is used by: GetLog.req
162
+    """
163
+
164
+    remote_location: str
165
+    oldest_timestamp: Optional[str] = None
166
+    latest_timestamp: Optional[str] = None

+ 852 - 0
ocpp/ocpp/v16/enums.py

@@ -0,0 +1,852 @@
1
+from enum import Enum
2
+
3
+
4
+class Action(str, Enum):
5
+    """An Action is a required part of a Call message."""
6
+
7
+    Authorize = "Authorize"
8
+    BootNotification = "BootNotification"
9
+    CancelReservation = "CancelReservation"
10
+    CertificateSigned = "CertificateSigned"
11
+    ChangeAvailability = "ChangeAvailability"
12
+    ChangeConfiguration = "ChangeConfiguration"
13
+    ClearCache = "ClearCache"
14
+    ClearChargingProfile = "ClearChargingProfile"
15
+    DataTransfer = "DataTransfer"
16
+    DeleteCertificate = "DeleteCertificate"
17
+    DiagnosticsStatusNotification = "DiagnosticsStatusNotification"
18
+    ExtendedTriggerMessage = "ExtendedTriggerMessage"
19
+    FirmwareStatusNotification = "FirmwareStatusNotification"
20
+    GetCompositeSchedule = "GetCompositeSchedule"
21
+    GetConfiguration = "GetConfiguration"
22
+    GetDiagnostics = "GetDiagnostics"
23
+    GetInstalledCertificateIds = "GetInstalledCertificateIds"
24
+    GetLocalListVersion = "GetLocalListVersion"
25
+    GetLog = "GetLog"
26
+    Heartbeat = "Heartbeat"
27
+    InstallCertificate = "InstallCertificate"
28
+    LogStatusNotification = "LogStatusNotification"
29
+    MeterValues = "MeterValues"
30
+    RemoteStartTransaction = "RemoteStartTransaction"
31
+    RemoteStopTransaction = "RemoteStopTransaction"
32
+    ReserveNow = "ReserveNow"
33
+    Reset = "Reset"
34
+    SecurityEventNotification = "SecurityEventNotification"
35
+    SendLocalList = "SendLocalList"
36
+    SetChargingProfile = "SetChargingProfile"
37
+    SignCertificate = "SignCertificate"
38
+    SignedFirmwareStatusNotification = "SignedFirmwareStatusNotification"
39
+    SignedUpdateFirmware = "SignedUpdateFirmware"
40
+    StartTransaction = "StartTransaction"
41
+    StatusNotification = "StatusNotification"
42
+    StopTransaction = "StopTransaction"
43
+    TriggerMessage = "TriggerMessage"
44
+    UnlockConnector = "UnlockConnector"
45
+    UpdateFirmware = "UpdateFirmware"
46
+
47
+
48
+class AuthorizationStatus(str, Enum):
49
+    """
50
+    Elements that constitute an entry of a Local Authorization List update.
51
+    """
52
+
53
+    accepted = "Accepted"
54
+    blocked = "Blocked"
55
+    expired = "Expired"
56
+    invalid = "Invalid"
57
+    concurrent_tx = "ConcurrentTx"
58
+
59
+
60
+class AvailabilityStatus(str, Enum):
61
+    """
62
+    Status returned in response to ChangeAvailability.req.
63
+    """
64
+
65
+    accepted = "Accepted"
66
+    rejected = "Rejected"
67
+    scheduled = "Scheduled"
68
+
69
+
70
+class AvailabilityType(str, Enum):
71
+    """
72
+    Requested availability change in ChangeAvailability.req.
73
+    """
74
+
75
+    inoperative = "Inoperative"
76
+    operative = "Operative"
77
+
78
+
79
+class CancelReservationStatus(str, Enum):
80
+    """
81
+    Status in CancelReservation.conf.
82
+    """
83
+
84
+    accepted = "Accepted"
85
+    rejected = "Rejected"
86
+
87
+
88
+class CertificateSignedStatus(str, Enum):
89
+    """
90
+    CertificateSignedStatusEnumType is used by: CertificateSigned.conf
91
+    """
92
+
93
+    accepted = "Accepted"
94
+    rejected = "Rejected"
95
+
96
+
97
+class CertificateStatus(str, Enum):
98
+    """
99
+    CertificateStatusEnumType is used by: InstallCertificate.conf
100
+    """
101
+
102
+    accepted = "Accepted"
103
+    rejected = "Rejected"
104
+    failed = "Failed"
105
+
106
+
107
+class CertificateUse(str, Enum):
108
+    """
109
+    CertificateUseEnumType is used by: GetInstalledCertificateIds.req,
110
+    InstallCertificate.req
111
+    """
112
+
113
+    central_system_root_certificate = "CentralSystemRootCertificate"
114
+    manufacturer_root_certificate = "ManufacturerRootCertificate"
115
+
116
+
117
+class ChargePointErrorCode(str, Enum):
118
+    """
119
+    Charge Point status reported in StatusNotification.req.
120
+    """
121
+
122
+    connector_lock_failure = "ConnectorLockFailure"
123
+    ev_communication_error = "EVCommunicationError"
124
+    ground_failure = "GroundFailure"
125
+    high_temperature = "HighTemperature"
126
+    internal_error = "InternalError"
127
+    local_list_conflict = "LocalListConflict"
128
+    no_error = "NoError"
129
+    other_error = "OtherError"
130
+    over_current_failure = "OverCurrentFailure"
131
+    over_voltage = "OverVoltage"
132
+    power_meter_failure = "PowerMeterFailure"
133
+    power_switch_failure = "PowerSwitchFailure"
134
+    reader_failure = "ReaderFailure"
135
+    reset_failure = "ResetFailure"
136
+    under_voltage = "UnderVoltage"
137
+    weak_signal = "WeakSignal"
138
+
139
+    # Soon to be deprecated enums
140
+    connectorLockFailure = "ConnectorLockFailure"
141
+    evCommunicationError = "EVCommunicationError"
142
+    groundFailure = "GroundFailure"
143
+    highTemperature = "HighTemperature"
144
+    internalError = "InternalError"
145
+    localListConflict = "LocalListConflict"
146
+    noError = "NoError"
147
+    otherError = "OtherError"
148
+    overCurrentFailure = "OverCurrentFailure"
149
+    overVoltage = "OverVoltage"
150
+    powerMeterFailure = "PowerMeterFailure"
151
+    powerSwitchFailure = "PowerSwitchFailure"
152
+    readerFailure = "ReaderFailure"
153
+    resetFailure = "ResetFailure"
154
+    underVoltage = "UnderVoltage"
155
+    weakSignal = "WeakSignal"
156
+
157
+
158
+class ChargePointStatus(str, Enum):
159
+    """
160
+    Status reported in StatusNotification.req. A status can be reported for
161
+    the Charge Point main controller (connectorId = 0) or for a specific
162
+    connector. Status for the Charge Point main controller is a subset of the
163
+    enumeration: Available, Unavailable or Faulted.
164
+
165
+    States considered Operative are: Available, Preparing, Charging,
166
+    SuspendedEVSE, SuspendedEV, Finishing, Reserved.
167
+    States considered Inoperative are: Unavailable, Faulted.
168
+    """
169
+
170
+    available = "Available"
171
+    preparing = "Preparing"
172
+    charging = "Charging"
173
+    suspended_evse = "SuspendedEVSE"
174
+    suspended_ev = "SuspendedEV"
175
+    finishing = "Finishing"
176
+    reserved = "Reserved"
177
+    unavailable = "Unavailable"
178
+    faulted = "Faulted"
179
+
180
+    # Soon to be deprecated enums
181
+    suspendedevse = "SuspendedEVSE"
182
+    suspendedev = "SuspendedEV"
183
+
184
+
185
+class ChargingProfileKindType(str, Enum):
186
+    """
187
+    "Absolute": Schedule periods are relative to a fixed point in time defined
188
+                in the schedule.
189
+    "Recurring": Schedule restarts periodically at the first schedule period.
190
+    "Relative": Schedule periods are relative to a situation- specific start
191
+                point(such as the start of a session)
192
+    """
193
+
194
+    absolute = "Absolute"
195
+    recurring = "Recurring"
196
+    relative = "Relative"
197
+
198
+
199
+class ChargingProfilePurposeType(str, Enum):
200
+    """
201
+    In load balancing scenarios, the Charge Point has one or more local
202
+    charging profiles that limit the power or current to be shared by all
203
+    connectors of the Charge Point. The Central System SHALL configure such
204
+    a profile with ChargingProfilePurpose set to “ChargePointMaxProfile”.
205
+    ChargePointMaxProfile can only be set at Charge Point ConnectorId 0.
206
+
207
+    Default schedules for new transactions MAY be used to impose charging
208
+    policies. An example could be a policy that prevents charging during
209
+    the day. For schedules of this purpose, ChargingProfilePurpose SHALL
210
+    be set to TxDefaultProfile. If TxDefaultProfile is set to ConnectorId 0,
211
+    the TxDefaultProfile is applicable to all Connectors. If ConnectorId is
212
+    set >0, it only applies to that specific connector. In the event a
213
+    TxDefaultProfile for connector 0 is installed, and the Central
214
+    System sends a new profile with ConnectorId >0, the TxDefaultProfile
215
+    SHALL be replaced only for that specific connector.
216
+
217
+    If a transaction-specific profile with purpose TxProfile is present,
218
+    it SHALL overrule the default charging profile with purpose
219
+    TxDefaultProfile for the duration of the current transaction only.
220
+    After the transaction is stopped, the profile SHOULD be deleted.
221
+    If there is no transaction active on the connector specified in a
222
+    charging profile of type TxProfile, then the Charge Point SHALL
223
+    discard it and return an error status in SetChargingProfile.conf.
224
+    TxProfile SHALL only be set at Charge Point ConnectorId >0.
225
+
226
+    It is not possible to set a ChargingProfile with purpose set to
227
+    TxProfile without presence of an active transaction, or in advance of
228
+    a transaction.
229
+
230
+    In order to ensure that the updated charging profile applies only to the
231
+    current transaction, the chargingProfilePurpose of the ChargingProfile
232
+    MUST be set to TxProfile.
233
+    """
234
+
235
+    charge_point_max_profile = "ChargePointMaxProfile"
236
+    tx_default_profile = "TxDefaultProfile"
237
+    tx_profile = "TxProfile"
238
+
239
+    # Soon to be deprecated enums
240
+    chargepointmaxprofile = "ChargePointMaxProfile"
241
+    txdefaultprofile = "TxDefaultProfile"
242
+    txprofile = "TxProfile"
243
+
244
+
245
+class ChargingProfileStatus(str, Enum):
246
+    """
247
+    Status returned in response to SetChargingProfile.req.
248
+    """
249
+
250
+    accepted = "Accepted"
251
+    rejected = "Rejected"
252
+    not_supported = "NotSupported"
253
+    # Soon to be deprecated enums
254
+    notSupported = "NotSupported"
255
+
256
+
257
+class ChargingRateUnitType(str, Enum):
258
+    """
259
+    Unit in which a charging schedule is defined, as used in:
260
+    GetCompositeSchedule.req and ChargingSchedule
261
+    """
262
+
263
+    watts = "W"
264
+    amps = "A"
265
+
266
+
267
+class CiStringType(int):
268
+    """
269
+    Generic used case insensitive string of X characters
270
+    """
271
+
272
+    ci_string_20 = 20
273
+    ci_string_25 = 25
274
+    ci_string_50 = 50
275
+    ci_string_255 = 255
276
+    ci_string_500 = 500
277
+
278
+
279
+class ClearCacheStatus(str, Enum):
280
+    """
281
+    Status returned in response to ClearCache.req.
282
+    """
283
+
284
+    accepted = "Accepted"
285
+    rejected = "Rejected"
286
+
287
+
288
+class ClearChargingProfileStatus(str, Enum):
289
+    """
290
+    Status returned in response to ClearChargingProfile.req.
291
+    """
292
+
293
+    accepted = "Accepted"
294
+    unknown = "Unknown"
295
+
296
+
297
+class ConfigurationStatus(str, Enum):
298
+    """
299
+    Status in ChangeConfiguration.conf.
300
+    """
301
+
302
+    accepted = "Accepted"
303
+    rejected = "Rejected"
304
+    reboot_required = "RebootRequired"
305
+    not_supported = "NotSupported"
306
+
307
+    # Soon to be deprecated enums
308
+    rebootRequired = "RebootRequired"
309
+    notSupported = "NotSupported"
310
+
311
+
312
+class ConfigurationKey(str, Enum):
313
+    """
314
+    Configuration Key Names.
315
+    """
316
+
317
+    # 9.1 Core Profile
318
+    allow_offline_tx_for_unknown_id = "AllowOfflineTxForUnknownId"
319
+    authorization_cache_enabled = "AuthorizationCacheEnabled"
320
+    authorize_remote_tx_requests = "AuthorizeRemoteTxRequests"
321
+    blink_repeat = "BlinkRepeat"
322
+    clock_aligned_data_interval = "ClockAlignedDataInterval"
323
+    connection_time_out = "ConnectionTimeOut"
324
+    connector_phase_rotation = "ConnectorPhaseRotation"
325
+    connector_phase_rotation_max_length = "ConnectorPhaseRotationMaxLength"
326
+    get_configuration_max_keys = "GetConfigurationMaxKeys"
327
+    heartbeat_interval = "HeartbeatInterval"
328
+    light_intensity = "LightIntensity"
329
+    local_authorize_offline = "LocalAuthorizeOffline"
330
+    local_pre_authorize = "LocalPreAuthorize"
331
+    max_energy_on_invalid_id = "MaxEnergyOnInvalidId"
332
+    meter_values_aligned_data = "MeterValuesAlignedData"
333
+    meter_values_aligned_data_max_length = "MeterValuesAlignedDataMaxLength"
334
+    meter_values_sampled_data = "MeterValuesSampledData"
335
+    meter_values_sampled_data_max_length = "MeterValuesSampledDataMaxLength"
336
+    meter_value_sample_interval = "MeterValueSampleInterval"
337
+    minimum_status_duration = "MinimumStatusDuration"
338
+    number_of_connectors = "NumberOfConnectors"
339
+    reset_retries = "ResetRetries"
340
+    stop_transaction_on_ev_side_disconnect = "StopTransactionOnEVSideDisconnect"
341
+    stop_transaction_on_invalid_id = "StopTransactionOnInvalidId"
342
+    stop_txn_aligned_data = "StopTxnAlignedData"
343
+    stop_txn_aligned_data_max_length = "StopTxnAlignedDataMaxLength"
344
+    stop_txn_sampled_data = "StopTxnSampledData"
345
+    stop_txn_sampled_data_max_length = "StopTxnSampledDataMaxLength"
346
+    supported_feature_profiles = "SupportedFeatureProfiles"
347
+    supported_feature_profiles_max_length = "SupportedFeatureProfilesMaxLength"
348
+    transaction_message_attempts = "TransactionMessageAttempts"
349
+    transaction_message_retry_interval = "TransactionMessageRetryInterval"
350
+    unlock_connector_on_ev_side_disconnect = "UnlockConnectorOnEVSideDisconnect"
351
+    web_socket_ping_interval = "WebSocketPingInterval"
352
+
353
+    # 9.2 Local Auth List Management Profile
354
+    local_auth_list_enabled = "LocalAuthListEnabled"
355
+    local_auth_list_max_length = "LocalAuthListMaxLength"
356
+    send_local_list_max_length = "SendLocalListMaxLength"
357
+
358
+    # 9.3 Reservation Profile
359
+    reserve_connector_zero_supported = "ReserveConnectorZeroSupported"
360
+
361
+    # 9.4 Smart Charging Profile
362
+    charge_profile_max_stack_level = "ChargeProfileMaxStackLevel"
363
+    charging_schedule_allowed_charging_rate_unit = (
364
+        "ChargingScheduleAllowedChargingRateUnit"
365
+    )
366
+    charging_schedule_max_periods = "ChargingScheduleMaxPeriods"
367
+    connector_switch_3to1_phase_supported = "ConnectorSwitch3to1PhaseSupported"
368
+    max_charging_profiles_installed = "MaxChargingProfilesInstalled"
369
+
370
+    # OCPP 1.6 ISO 15118 v10 added configuration keys
371
+    central_contract_validation_allowed = "CentralContractValidationAllowed"
372
+    certificate_signed_max_chain_size = "CertificateSignedMaxChainSize"
373
+    cert_signing_wait_minimum = "CertSigningWaitMinimum"
374
+    cert_signing_repeat_times = "CertSigningRepeatTimes"
375
+    certificate_store_max_length = "CertificateStoreMaxLength"
376
+    contract_validation_offline = "ContractValidationOffline"
377
+    iso_15118_pnc_enabled = "ISO15118PnCEnabled"
378
+
379
+    # OCPP security whitepaper added configuration keys
380
+    additional_root_certificate_check = "AdditionalRootCertificateCheck"
381
+    authorization_key = "AuthorizationKey"
382
+    cpo_name = "CpoName"
383
+    security_profile = "SecurityProfile"
384
+
385
+
386
+class DataTransferStatus(str, Enum):
387
+    """
388
+    Status in DataTransfer.conf.
389
+    """
390
+
391
+    accepted = "Accepted"
392
+    rejected = "Rejected"
393
+    unknown_message_id = "UnknownMessageId"
394
+    unknown_vendor_id = "UnknownVendorId"
395
+
396
+    # Soon to be deprecated enums
397
+    unknownMessageId = "UnknownMessageId"
398
+    unknownVendorId = "UnknownVendorId"
399
+
400
+
401
+class DeleteCertificateStatus(str, Enum):
402
+    """
403
+    DeleteCertificateStatusEnumType is used by: DeleteCertificate.conf
404
+    """
405
+
406
+    accepted = "Accepted"
407
+    failed = "Failed"
408
+    not_found = "NotFound"
409
+
410
+
411
+class DiagnosticsStatus(str, Enum):
412
+    """
413
+    Status in DiagnosticsStatusNotification.req.
414
+    """
415
+
416
+    idle = "Idle"
417
+    uploaded = "Uploaded"
418
+    upload_failed = "UploadFailed"
419
+    uploading = "Uploading"
420
+
421
+    # Soon to be deprecated enums
422
+    uploadFailed = "UploadFailed"
423
+
424
+
425
+class FirmwareStatus(str, Enum):
426
+    """
427
+    Status of a firmware download as reported in FirmwareStatusNotification.req
428
+    """
429
+
430
+    # Common for:
431
+    # FirmwareStatusNotification.req and SignedFirmwareStatusNotification.req
432
+    downloaded = "Downloaded"
433
+    download_failed = "DownloadFailed"
434
+    downloading = "Downloading"
435
+    idle = "Idle"
436
+    installation_failed = "InstallationFailed"
437
+    installing = "Installing"
438
+    installed = "Installed"
439
+
440
+    # Only for SignedFirmwareStatusNotification.reg
441
+    download_scheduled = "DownloadScheduled"
442
+    download_paused = "DownloadPaused"
443
+    install_rebooting = "InstallRebooting"
444
+    install_scheduled = "InstallScheduled"
445
+    install_verification_failed = "InstallVerificationFailed"
446
+    invalid_signature = "InvalidSignature"
447
+    signature_verified = "SignatureVerified"
448
+
449
+    # Soon to be deprecated enums
450
+    downloadFailed = "DownloadFailed"
451
+    installationFailed = "InstallationFailed"
452
+
453
+
454
+class GenericStatus(str, Enum):
455
+    """
456
+    Generic message response status
457
+    """
458
+
459
+    accepted = "Accepted"
460
+    rejected = "Rejected"
461
+
462
+
463
+class GetCompositeScheduleStatus(str, Enum):
464
+    """
465
+    Status returned in response to GetCompositeSchedule.req
466
+    """
467
+
468
+    accepted = "Accepted"
469
+    rejected = "Rejected"
470
+
471
+
472
+class GetInstalledCertificateStatus(str, Enum):
473
+    """
474
+    GetInstalledCertificateStatusEnumType is used by:
475
+    GetInstalledCertificateIds.conf
476
+    """
477
+
478
+    accepted = "Accepted"
479
+    not_found = "NotFound"
480
+
481
+
482
+class HashAlgorithm(str, Enum):
483
+    """
484
+    HashAlgorithmEnumType is used by: CertificateHashDataType
485
+    """
486
+
487
+    sha256 = "SHA256"
488
+    sha384 = "SHA384"
489
+    sha512 = "SHA512"
490
+
491
+
492
+class Location(str, Enum):
493
+    """
494
+    Allowable values of the optional "location" field of a value element in
495
+    SampledValue.
496
+    """
497
+
498
+    inlet = "Inlet"
499
+    outlet = "Outlet"
500
+    body = "Body"
501
+    cable = "Cable"
502
+    ev = "EV"
503
+
504
+
505
+class Log(str, Enum):
506
+    """
507
+    LogEnumType is used by GetLog.req
508
+    """
509
+
510
+    diagnostics_log = "DiagnosticsLog"
511
+    security_log = "SecurityLog"
512
+
513
+
514
+class LogStatus(str, Enum):
515
+    """
516
+    LogStatusEnumType is used by: GetLog.conf
517
+    """
518
+
519
+    accepted = "Accepted"
520
+    rejected = "Rejected"
521
+    accepted_canceled = "AcceptedCanceled"
522
+
523
+
524
+class Measurand(str, Enum):
525
+    """
526
+    Allowable values of the optional "measurand" field of a Value element, as
527
+    used in MeterValues.req and StopTransaction.req messages. Default value of
528
+    "measurand" is always "Energy.Active.Import.Register"
529
+    """
530
+
531
+    current_export = "Current.Export"
532
+    current_import = "Current.Import"
533
+    current_offered = "Current.Offered"
534
+    energy_active_export_register = "Energy.Active.Export.Register"
535
+    energy_active_import_register = "Energy.Active.Import.Register"
536
+    energy_reactive_export_register = "Energy.Reactive.Export.Register"
537
+    energy_reactive_import_register = "Energy.Reactive.Import.Register"
538
+    energy_active_export_interval = "Energy.Active.Export.Interval"
539
+    energy_active_import_interval = "Energy.Active.Import.Interval"
540
+    energy_reactive_export_interval = "Energy.Reactive.Export.Interval"
541
+    energy_reactive_import_interval = "Energy.Reactive.Import.Interval"
542
+    frequency = "Frequency"
543
+    power_active_export = "Power.Active.Export"
544
+    power_active_import = "Power.Active.Import"
545
+    power_factor = "Power.Factor"
546
+    power_offered = "Power.Offered"
547
+    power_reactive_export = "Power.Reactive.Export"
548
+    power_reactive_import = "Power.Reactive.Import"
549
+    rpm = "RPM"
550
+    soc = "SoC"
551
+    temperature = "Temperature"
552
+    voltage = "Voltage"
553
+
554
+    # Soon to be deprecated enums
555
+    currentExport = "Current.Export"
556
+    currentImport = "Current.Import"
557
+    currentOffered = "Current.Offered"
558
+    energyActiveExportRegister = "Energy.Active.Export.Register"
559
+    energyActiveImportRegister = "Energy.Active.Import.Register"
560
+    energyReactiveExportRegister = "Energy.Reactive.Export.Register"
561
+    energyReactiveImportRegister = "Energy.Reactive.Import.Register"
562
+    energyActiveExportInterval = "Energy.Active.Export.Interval"
563
+    energyActiveImportInterval = "Energy.Active.Import.Interval"
564
+    energyReactiveExportInterval = "Energy.Reactive.Export.Interval"
565
+    energyReactiveImportInterval = "Energy.Reactive.Import.Interval"
566
+    powerActiveExport = "Power.Active.Export"
567
+    powerActiveImport = "Power.Active.Import"
568
+    powerFactor = "Power.Factor"
569
+    powerOffered = "Power.Offered"
570
+    powerReactiveExport = "Power.Reactive.Export"
571
+    powerReactiveImport = "Power.Reactive.Import"
572
+
573
+
574
+class MessageTrigger(str, Enum):
575
+    """
576
+    Type of request to be triggered in a TriggerMessage.req
577
+    """
578
+
579
+    # Common for TriggerMessage.req and ExtendedTriggerMessage.req
580
+    boot_notification = "BootNotification"
581
+    firmware_status_notification = "FirmwareStatusNotification"
582
+    heartbeat = "Heartbeat"
583
+    meter_values = "MeterValues"
584
+    status_notification = "StatusNotification"
585
+
586
+    # Only for TriggerMessage.req
587
+    diagnostics_status_notification = "DiagnosticsStatusNotification"
588
+
589
+    # Only for ExtendedTriggerMessage.req
590
+    log_status_notification = "LogStatusNotification"
591
+    sign_charge_point_certificate = "SignChargePointCertificate"
592
+
593
+    # Soon to be deprecated enums
594
+    bootNotification = "BootNotification"
595
+    diagnosticsStatusNotification = "DiagnosticsStatusNotification"
596
+    firmwareStatusNotification = "FirmwareStatusNotification"
597
+    meterValues = "MeterValues"
598
+    statusNotification = "StatusNotification"
599
+
600
+
601
+class Phase(str, Enum):
602
+    """
603
+    Phase as used in SampledValue. Phase specifies how a measured value is to
604
+    be interpreted. Please note that not all values of Phase are applicable to
605
+    all Measurands.
606
+    """
607
+
608
+    l1 = "L1"
609
+    l2 = "L2"
610
+    l3 = "L3"
611
+    n = "N"
612
+    l1_n = "L1-N"
613
+    l2_n = "L2-N"
614
+    l3_n = "L3-N"
615
+    l1_l2 = "L1-L2"
616
+    l2_l3 = "L2-L3"
617
+    l3_l1 = "L3-L1"
618
+
619
+    # Soon to be deprecated enums
620
+    l1n = "L1-N"
621
+    l2n = "L2-N"
622
+    l3n = "L3-N"
623
+    l1l2 = "L1-L2"
624
+    l2l3 = "L2-L3"
625
+    l3l1 = "L3-L1"
626
+
627
+
628
+class ReadingContext(str, Enum):
629
+    """
630
+    Values of the context field of a value in SampledValue.
631
+    """
632
+
633
+    interruption_begin = "Interruption.Begin"
634
+    interruption_end = "Interruption.End"
635
+    other = "Other"
636
+    sample_clock = "Sample.Clock"
637
+    sample_periodic = "Sample.Periodic"
638
+    transaction_begin = "Transaction.Begin"
639
+    transaction_end = "Transaction.End"
640
+    trigger = "Trigger"
641
+
642
+    # Soon to be deprecated enums
643
+    interruptionBegin = "Interruption.Begin"
644
+    interruptionEnd = "Interruption.End"
645
+    sampleClock = "Sample.Clock"
646
+    samplePeriodic = "Sample.Periodic"
647
+    transactionBegin = "Transaction.Begin"
648
+    transactionEnd = "Transaction.End"
649
+
650
+
651
+class Reason(str, Enum):
652
+    """
653
+    Reason for stopping a transaction in StopTransaction.req.
654
+    """
655
+
656
+    emergency_stop = "EmergencyStop"
657
+    ev_disconnected = "EVDisconnected"
658
+    hard_reset = "HardReset"
659
+    local = "Local"
660
+    other = "Other"
661
+    power_loss = "PowerLoss"
662
+    reboot = "Reboot"
663
+    remote = "Remote"
664
+    soft_reset = "SoftReset"
665
+    unlock_command = "UnlockCommand"
666
+    de_authorized = "DeAuthorized"
667
+
668
+    # Soon to be deprecated enums
669
+    emergencyStop = "EmergencyStop"
670
+    evDisconnected = "EVDisconnected"
671
+    hardReset = "HardReset"
672
+    powerLoss = "PowerLoss"
673
+    softReset = "SoftReset"
674
+    unlockCommand = "UnlockCommand"
675
+    deAuthorized = "DeAuthorized"
676
+
677
+
678
+class RecurrencyKind(str, Enum):
679
+    """
680
+    "Daily": The schedule restarts at the beginning of the next day.
681
+    "Weekly": The schedule restarts at the beginning of the next week
682
+              (defined as Monday morning)
683
+    """
684
+
685
+    daily = "Daily"
686
+    weekly = "Weekly"
687
+
688
+
689
+class RegistrationStatus(str, Enum):
690
+    """
691
+    Result of registration in response to BootNotification.req.
692
+    """
693
+
694
+    accepted = "Accepted"
695
+    pending = "Pending"
696
+    rejected = "Rejected"
697
+
698
+
699
+class RemoteStartStopStatus(str, Enum):
700
+    """
701
+    The result of a RemoteStartTransaction.req or RemoteStopTransaction.req
702
+    request.
703
+    """
704
+
705
+    accepted = "Accepted"
706
+    rejected = "Rejected"
707
+
708
+
709
+class ReservationStatus(str, Enum):
710
+    """
711
+    Status in ReserveNow.conf.
712
+    """
713
+
714
+    accepted = "Accepted"
715
+    faulted = "Faulted"
716
+    occupied = "Occupied"
717
+    rejected = "Rejected"
718
+    unavailable = "Unavailable"
719
+
720
+
721
+class ResetStatus(str, Enum):
722
+    """
723
+    Result of Reset.req
724
+    """
725
+
726
+    accepted = "Accepted"
727
+    rejected = "Rejected"
728
+
729
+
730
+class ResetType(str, Enum):
731
+    """
732
+    Type of reset requested by Reset.req
733
+    """
734
+
735
+    hard = "Hard"
736
+    soft = "Soft"
737
+
738
+
739
+class TriggerMessageStatus(str, Enum):
740
+    """
741
+    Status in TriggerMessage.conf.
742
+    """
743
+
744
+    accepted = "Accepted"
745
+    rejected = "Rejected"
746
+    not_implemented = "NotImplemented"
747
+
748
+    # Soon to be deprecated enums
749
+    notImplemented = "NotImplemented"
750
+
751
+
752
+class UnitOfMeasure(str, Enum):
753
+    """
754
+    Allowable values of the optional "unit" field of a Value element, as used
755
+    in MeterValues.req and StopTransaction.req messages. Default value of
756
+    "unit" is always "Wh".
757
+    """
758
+
759
+    wh = "Wh"
760
+    kwh = "kWh"
761
+    varh = "varh"
762
+    kvarh = "kvarh"
763
+    w = "W"
764
+    kw = "kW"
765
+    va = "VA"
766
+    kva = "kVA"
767
+    var = "var"
768
+    kvar = "kvar"
769
+    a = "A"
770
+    v = "V"
771
+    celsius = "Celsius"
772
+    fahrenheit = "Fahrenheit"
773
+    k = "K"
774
+    percent = "Percent"
775
+    hertz = "Hertz"
776
+
777
+
778
+class UnlockStatus(str, Enum):
779
+    """
780
+    Status in response to UnlockConnector.req.
781
+    """
782
+
783
+    unlocked = "Unlocked"
784
+    unlock_failed = "UnlockFailed"
785
+    not_supported = "NotSupported"
786
+
787
+    # Soon to be deprecated enums
788
+    unlockFailed = "UnlockFailed"
789
+    notSupported = "NotSupported"
790
+
791
+
792
+class UpdateFirmwareStatus(str, Enum):
793
+    """
794
+    UpdateFirmwareStatusEnumType is used by: SignedUpdateFirmware.conf
795
+    """
796
+
797
+    accepted = "Accepted"
798
+    rejected = "Rejected"
799
+    accepted_canceled = "AcceptedCanceled"
800
+    invalid_certificate = "InvalidCertificate"
801
+    revoked_certificate = "RevokedCertificate"
802
+
803
+
804
+class UploadLogStatus(str, Enum):
805
+    """
806
+    UploadLogStatusEnumType is used by: LogStatusNotification.req
807
+    """
808
+
809
+    bad_message = "BadMessage"
810
+    idle = "Idle"
811
+    not_supported_operation = "NotSupportedOperation"
812
+    permission_denied = "PermissionDenied"
813
+    uploaded = "Uploaded"
814
+    upload_failure = "UploadFailure"
815
+    uploading = "Uploading"
816
+
817
+
818
+class UpdateStatus(str, Enum):
819
+    """
820
+    Type of update for a SendLocalList.req.
821
+    """
822
+
823
+    accepted = "Accepted"
824
+    failed = "Failed"
825
+    not_supported = "NotSupported"
826
+    version_mismatch = "VersionMismatch"
827
+
828
+    # Soon to be deprecated enums
829
+    notSupported = "NotSupported"
830
+    versionMismatch = "VersionMismatch"
831
+
832
+
833
+class UpdateType(str, Enum):
834
+    """
835
+    Type of update for a SendLocalList.req.
836
+    """
837
+
838
+    differential = "Differential"
839
+    full = "Full"
840
+
841
+
842
+class ValueFormat(str, Enum):
843
+    """
844
+    Format that specifies how the value element in SampledValue is to be
845
+    interpreted.
846
+    """
847
+
848
+    raw = "Raw"
849
+    signed_data = "SignedData"
850
+
851
+    # Soon to be deprecated enums
852
+    signedData = "SignedData"

+ 15 - 0
ocpp/ocpp/v16/schemas/Authorize.json

@@ -0,0 +1,15 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "AuthorizeRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "idTag": {
7
+            "type": "string",
8
+            "maxLength": 20
9
+        }
10
+    },
11
+    "additionalProperties": false,
12
+    "required": [
13
+        "idTag"
14
+    ]
15
+}

+ 39 - 0
ocpp/ocpp/v16/schemas/AuthorizeResponse.json

@@ -0,0 +1,39 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "AuthorizeResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "idTagInfo": {
7
+            "type": "object",
8
+            "properties": {
9
+                "expiryDate": {
10
+                    "type": "string",
11
+                    "format": "date-time"
12
+                },
13
+                "parentIdTag": {
14
+                    "type": "string",
15
+                    "maxLength": 20
16
+                },
17
+                "status": {
18
+                    "type": "string",
19
+                    "additionalProperties": false,
20
+                    "enum": [
21
+                        "Accepted",
22
+                        "Blocked",
23
+                        "Expired",
24
+                        "Invalid",
25
+                        "ConcurrentTx"
26
+                    ]
27
+                }
28
+            },
29
+            "additionalProperties": false,
30
+            "required": [
31
+                "status"
32
+            ]
33
+        }
34
+    },
35
+    "additionalProperties": false,
36
+    "required": [
37
+        "idTagInfo"
38
+    ]
39
+}

+ 48 - 0
ocpp/ocpp/v16/schemas/BootNotification.json

@@ -0,0 +1,48 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "BootNotificationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "chargePointVendor": {
7
+            "type": "string",
8
+            "maxLength": 20
9
+        },
10
+        "chargePointModel": {
11
+            "type": "string",
12
+            "maxLength": 20
13
+        },
14
+        "chargePointSerialNumber": {
15
+            "type": "string",
16
+            "maxLength": 25
17
+        },
18
+        "chargeBoxSerialNumber": {
19
+            "type": "string",
20
+            "maxLength": 25
21
+        },
22
+        "firmwareVersion": {
23
+            "type": "string",
24
+            "maxLength": 50
25
+        },
26
+        "iccid": {
27
+            "type": "string",
28
+            "maxLength": 20
29
+        },
30
+        "imsi": {
31
+            "type": "string",
32
+            "maxLength": 20
33
+        },
34
+        "meterType": {
35
+            "type": "string",
36
+            "maxLength": 25
37
+        },
38
+        "meterSerialNumber": {
39
+            "type": "string",
40
+            "maxLength": 25
41
+        }
42
+    },
43
+    "additionalProperties": false,
44
+    "required": [
45
+        "chargePointVendor",
46
+        "chargePointModel"
47
+    ]
48
+}

+ 29 - 0
ocpp/ocpp/v16/schemas/BootNotificationResponse.json

@@ -0,0 +1,29 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "BootNotificationResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Pending",
12
+                "Rejected"
13
+            ]
14
+        },
15
+        "currentTime": {
16
+            "type": "string",
17
+            "format": "date-time"
18
+        },
19
+        "interval": {
20
+            "type": "integer"
21
+        }
22
+    },
23
+    "additionalProperties": false,
24
+    "required": [
25
+        "status",
26
+        "currentTime",
27
+        "interval"
28
+    ]
29
+}

+ 14 - 0
ocpp/ocpp/v16/schemas/CancelReservation.json

@@ -0,0 +1,14 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "CancelReservationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "reservationId": {
7
+            "type": "integer"
8
+        }
9
+    },
10
+    "additionalProperties": false,
11
+    "required": [
12
+        "reservationId"
13
+    ]
14
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/CancelReservationResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "CancelReservationResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 15 - 0
ocpp/ocpp/v16/schemas/CertificateSigned.json

@@ -0,0 +1,15 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:CertificateSigned.req",
4
+    "type": "object",
5
+    "properties": {
6
+        "certificateChain": {
7
+            "type": "string",
8
+            "maxLength": 10000
9
+        }
10
+    },
11
+    "additionalProperties": false,
12
+    "required": [
13
+        "certificateChain"
14
+    ]
15
+}

+ 24 - 0
ocpp/ocpp/v16/schemas/CertificateSignedResponse.json

@@ -0,0 +1,24 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:CertificateSigned.conf",
4
+  "definitions": {
5
+    "CertificateSignedStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "Rejected"
11
+      ]
12
+    }
13
+  },
14
+  "type": "object",
15
+  "additionalProperties": false,
16
+  "properties": {
17
+    "status": {
18
+      "$ref": "#/definitions/CertificateSignedStatusEnumType"
19
+    }
20
+  },
21
+  "required": [
22
+    "status"
23
+  ]
24
+}

+ 23 - 0
ocpp/ocpp/v16/schemas/ChangeAvailability.json

@@ -0,0 +1,23 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ChangeAvailabilityRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "connectorId": {
7
+            "type": "integer"
8
+        },
9
+        "type": {
10
+            "type": "string",
11
+            "additionalProperties": false,
12
+            "enum": [
13
+                "Inoperative",
14
+                "Operative"
15
+            ]
16
+        }
17
+    },
18
+    "additionalProperties": false,
19
+    "required": [
20
+        "connectorId",
21
+        "type"
22
+    ]
23
+}

+ 20 - 0
ocpp/ocpp/v16/schemas/ChangeAvailabilityResponse.json

@@ -0,0 +1,20 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ChangeAvailabilityResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected",
12
+                "Scheduled"
13
+            ]
14
+        }
15
+    },
16
+    "additionalProperties": false,
17
+    "required": [
18
+        "status"
19
+    ]
20
+}

+ 20 - 0
ocpp/ocpp/v16/schemas/ChangeConfiguration.json

@@ -0,0 +1,20 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ChangeConfigurationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "key": {
7
+            "type": "string",
8
+            "maxLength": 50
9
+        },
10
+        "value": {
11
+            "type": "string",
12
+            "maxLength": 500
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "key",
18
+        "value"
19
+    ]
20
+}

+ 21 - 0
ocpp/ocpp/v16/schemas/ChangeConfigurationResponse.json

@@ -0,0 +1,21 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ChangeConfigurationResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected",
12
+                "RebootRequired",
13
+                "NotSupported"
14
+            ]
15
+        }
16
+    },
17
+    "additionalProperties": false,
18
+    "required": [
19
+        "status"
20
+    ]
21
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/ClearCache.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ClearCacheRequest",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/ClearCacheResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ClearCacheResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 26 - 0
ocpp/ocpp/v16/schemas/ClearChargingProfile.json

@@ -0,0 +1,26 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ClearChargingProfileRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "id": {
7
+            "type": "integer"
8
+        },
9
+        "connectorId": {
10
+            "type": "integer"
11
+        },
12
+        "chargingProfilePurpose": {
13
+            "type": "string",
14
+            "additionalProperties": false,
15
+            "enum": [
16
+                "ChargePointMaxProfile",
17
+                "TxDefaultProfile",
18
+                "TxProfile"
19
+            ]
20
+        },
21
+        "stackLevel": {
22
+            "type": "integer"
23
+        }
24
+    },
25
+    "additionalProperties": false
26
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/ClearChargingProfileResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ClearChargingProfileResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Unknown"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 22 - 0
ocpp/ocpp/v16/schemas/DataTransfer.json

@@ -0,0 +1,22 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "DataTransferRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "vendorId": {
7
+            "type": "string",
8
+            "maxLength": 255
9
+        },
10
+        "messageId": {
11
+            "type": "string",
12
+            "maxLength": 50
13
+        },
14
+        "data": {
15
+            "type": "string"
16
+        }
17
+    },
18
+    "additionalProperties": false,
19
+    "required": [
20
+        "vendorId"
21
+    ]
22
+}

+ 24 - 0
ocpp/ocpp/v16/schemas/DataTransferResponse.json

@@ -0,0 +1,24 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "DataTransferResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected",
12
+                "UnknownMessageId",
13
+                "UnknownVendorId"
14
+            ]
15
+        },
16
+        "data": {
17
+            "type": "string"
18
+        }
19
+    },
20
+    "additionalProperties": false,
21
+    "required": [
22
+        "status"
23
+    ]
24
+}

+ 52 - 0
ocpp/ocpp/v16/schemas/DeleteCertificate.json

@@ -0,0 +1,52 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:DeleteCertificate.req",
4
+  "definitions": {
5
+    "HashAlgorithmEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "SHA256",
10
+        "SHA384",
11
+        "SHA512"
12
+      ]
13
+    },
14
+    "CertificateHashDataType": {
15
+      "type": "object",
16
+      "additionalProperties": false,
17
+      "properties": {
18
+        "hashAlgorithm": {
19
+          "$ref": "#/definitions/HashAlgorithmEnumType"
20
+        },
21
+        "issuerNameHash": {
22
+          "type": "string",
23
+          "maxLength": 128
24
+        },
25
+        "issuerKeyHash": {
26
+          "type": "string",
27
+          "maxLength": 128
28
+        },
29
+        "serialNumber": {
30
+          "type": "string",
31
+          "maxLength": 40
32
+        }
33
+      },
34
+      "required": [
35
+        "hashAlgorithm",
36
+        "issuerNameHash",
37
+        "issuerKeyHash",
38
+        "serialNumber"
39
+      ]
40
+    }
41
+  },
42
+  "type": "object",
43
+  "additionalProperties": false,
44
+  "properties": {
45
+    "certificateHashData": {
46
+      "$ref": "#/definitions/CertificateHashDataType"
47
+    }
48
+  },
49
+  "required": [
50
+    "certificateHashData"
51
+  ]
52
+}

+ 25 - 0
ocpp/ocpp/v16/schemas/DeleteCertificateResponse.json

@@ -0,0 +1,25 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:DeleteCertificate.conf",
4
+  "definitions": {
5
+    "DeleteCertificateStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "Failed",
11
+        "NotFound"
12
+      ]
13
+    }
14
+  },
15
+  "type": "object",
16
+  "additionalProperties": false,
17
+  "properties": {
18
+    "status": {
19
+      "$ref": "#/definitions/DeleteCertificateStatusEnumType"
20
+    }
21
+  },
22
+  "required": [
23
+    "status"
24
+  ]
25
+}

+ 21 - 0
ocpp/ocpp/v16/schemas/DiagnosticsStatusNotification.json

@@ -0,0 +1,21 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "DiagnosticsStatusNotificationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Idle",
11
+                "Uploaded",
12
+                "UploadFailed",
13
+                "Uploading"
14
+            ]
15
+        }
16
+    },
17
+    "additionalProperties": false,
18
+    "required": [
19
+        "status"
20
+    ]
21
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/DiagnosticsStatusNotificationResponse.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "DiagnosticsStatusNotificationResponse",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 32 - 0
ocpp/ocpp/v16/schemas/ExtendedTriggerMessage.json

@@ -0,0 +1,32 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:ExtendedTriggerMessage.req",
4
+  "definitions": {
5
+    "MessageTriggerEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "BootNotification",
10
+        "LogStatusNotification",
11
+        "FirmwareStatusNotification",
12
+        "Heartbeat",
13
+        "MeterValues",
14
+        "SignChargePointCertificate",        
15
+        "StatusNotification"        
16
+      ]
17
+    }	  
18
+  },
19
+  "type": "object",
20
+  "additionalProperties": false,
21
+  "properties": {    
22
+    "requestedMessage": {
23
+      "$ref": "#/definitions/MessageTriggerEnumType"
24
+    },
25
+	"connectorId": {
26
+          "type": "integer"
27
+    }  
28
+  },
29
+  "required": [
30
+    "requestedMessage"
31
+  ]
32
+}

+ 25 - 0
ocpp/ocpp/v16/schemas/ExtendedTriggerMessageResponse.json

@@ -0,0 +1,25 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:ExtendedTriggerMessage.conf",
4
+  "definitions": {
5
+    "TriggerMessageStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "Rejected",
11
+        "NotImplemented"
12
+      ]
13
+    }
14
+  },
15
+  "type": "object",
16
+  "additionalProperties": false,
17
+  "properties": {
18
+    "status": {
19
+      "$ref": "#/definitions/TriggerMessageStatusEnumType"
20
+    }
21
+  },
22
+  "required": [
23
+    "status"
24
+  ]
25
+}

+ 24 - 0
ocpp/ocpp/v16/schemas/FirmwareStatusNotification.json

@@ -0,0 +1,24 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "FirmwareStatusNotificationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Downloaded",
11
+                "DownloadFailed",
12
+                "Downloading",
13
+                "Idle",
14
+                "InstallationFailed",
15
+                "Installing",
16
+                "Installed"
17
+            ]
18
+        }
19
+    },
20
+    "additionalProperties": false,
21
+    "required": [
22
+        "status"
23
+    ]
24
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/FirmwareStatusNotificationResponse.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "FirmwareStatusNotificationResponse",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 26 - 0
ocpp/ocpp/v16/schemas/GetCompositeSchedule.json

@@ -0,0 +1,26 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetCompositeScheduleRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "connectorId": {
7
+            "type": "integer"
8
+        },
9
+    "duration": {
10
+        "type": "integer"
11
+    },
12
+    "chargingRateUnit": {
13
+        "type": "string",
14
+        "additionalProperties": false,
15
+        "enum": [
16
+            "A",
17
+            "W"
18
+            ]
19
+        }
20
+    },
21
+    "additionalProperties": false,
22
+    "required": [
23
+        "connectorId",
24
+        "duration"
25
+    ]
26
+}

+ 78 - 0
ocpp/ocpp/v16/schemas/GetCompositeScheduleResponse.json

@@ -0,0 +1,78 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetCompositeScheduleResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        },
14
+    "connectorId": {
15
+        "type": "integer"
16
+        },
17
+    "scheduleStart": {
18
+        "type": "string",
19
+        "format": "date-time"
20
+    },
21
+    "chargingSchedule": {
22
+        "type": "object",
23
+        "properties": {
24
+            "duration": {
25
+                "type": "integer"
26
+            },
27
+            "startSchedule": {
28
+                "type": "string",
29
+                "format": "date-time"
30
+            },
31
+            "chargingRateUnit": {
32
+                "type": "string",
33
+                "additionalProperties": false,
34
+                "enum": [
35
+                    "A",
36
+                    "W"
37
+                    ]
38
+            },
39
+            "chargingSchedulePeriod": {
40
+                "type": "array",
41
+                "items": {
42
+                    "type": "object",
43
+                    "properties": {
44
+                        "startPeriod": {
45
+                            "type": "integer"
46
+                        },
47
+                        "limit": {
48
+                            "type": "number",
49
+                            "multipleOf" : 0.1
50
+                        },
51
+                        "numberPhases": {
52
+                            "type": "integer"
53
+                        }
54
+                    },
55
+                    "additionalProperties": false,
56
+                    "required": [
57
+                        "startPeriod",
58
+                        "limit"
59
+                        ]
60
+                }
61
+            },
62
+            "minChargingRate": {
63
+                "type": "number",
64
+                "multipleOf" : 0.1
65
+            }
66
+        },
67
+        "additionalProperties": false,
68
+        "required": [
69
+            "chargingRateUnit",
70
+            "chargingSchedulePeriod"
71
+        ]
72
+        }
73
+    },
74
+    "additionalProperties": false,
75
+    "required": [
76
+        "status"
77
+    ]
78
+}

+ 15 - 0
ocpp/ocpp/v16/schemas/GetConfiguration.json

@@ -0,0 +1,15 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetConfigurationRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "key": {
7
+            "type": "array",
8
+            "items": {
9
+                "type": "string",
10
+                "maxLength": 50
11
+            }
12
+        }
13
+    },
14
+    "additionalProperties": false
15
+}

+ 39 - 0
ocpp/ocpp/v16/schemas/GetConfigurationResponse.json

@@ -0,0 +1,39 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetConfigurationResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "configurationKey": {
7
+            "type": "array",
8
+            "items": {
9
+                "type": "object",
10
+                "properties": {
11
+                    "key": {
12
+                        "type": "string",
13
+                        "maxLength": 50
14
+                    },
15
+                    "readonly": {
16
+                        "type": "boolean"
17
+                    },
18
+                    "value": {
19
+                        "type": "string",
20
+                        "maxLength": 500
21
+                    }
22
+                },
23
+                "additionalProperties": false,
24
+                "required": [
25
+                    "key",
26
+                    "readonly"
27
+                ]
28
+            }
29
+        },
30
+        "unknownKey": {
31
+            "type": "array",
32
+            "items": {
33
+                "type": "string",
34
+                "maxLength": 50
35
+            }
36
+        }
37
+    },
38
+    "additionalProperties": false
39
+}

+ 29 - 0
ocpp/ocpp/v16/schemas/GetDiagnostics.json

@@ -0,0 +1,29 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetDiagnosticsRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "location": {
7
+            "type": "string",
8
+            "format": "uri"
9
+        },
10
+        "retries": {
11
+            "type": "integer"
12
+        },
13
+        "retryInterval": {
14
+            "type": "integer"
15
+        },
16
+        "startTime": {
17
+            "type": "string",
18
+            "format": "date-time"
19
+        },
20
+        "stopTime": {
21
+            "type": "string",
22
+            "format": "date-time"
23
+        }
24
+    },
25
+    "additionalProperties": false,
26
+    "required": [
27
+        "location"
28
+    ]
29
+}

+ 12 - 0
ocpp/ocpp/v16/schemas/GetDiagnosticsResponse.json

@@ -0,0 +1,12 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetDiagnosticsResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "fileName": {
7
+            "type": "string",
8
+            "maxLength": 255
9
+        }
10
+    },
11
+    "additionalProperties": false
12
+}

+ 24 - 0
ocpp/ocpp/v16/schemas/GetInstalledCertificateIds.json

@@ -0,0 +1,24 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:GetInstalledCertificateIds.req",
4
+  "definitions": {
5
+    "CertificateUseEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "CentralSystemRootCertificate",
10
+        "ManufacturerRootCertificate"
11
+      ]
12
+    }
13
+  },
14
+  "type": "object",
15
+  "additionalProperties": false,
16
+  "properties": {
17
+    "certificateType": {
18
+      "$ref": "#/definitions/CertificateUseEnumType"
19
+    }
20
+  },
21
+  "required": [
22
+    "certificateType"
23
+  ]
24
+}

+ 69 - 0
ocpp/ocpp/v16/schemas/GetInstalledCertificateIdsResponse.json

@@ -0,0 +1,69 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:GetInstalledCertificateIds.conf",
4
+  "definitions": {
5
+    "GetInstalledCertificateStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "NotFound"
11
+      ]
12
+    },
13
+    "HashAlgorithmEnumType": {
14
+      "type": "string",
15
+      "additionalProperties": false,
16
+      "enum": [
17
+        "SHA256",
18
+        "SHA384",
19
+        "SHA512"
20
+      ]
21
+    },
22
+    "CertificateHashDataType": {
23
+      "javaType": "CertificateHashData",
24
+      "type": "object",
25
+      "additionalProperties": false,
26
+      "properties": {
27
+        "hashAlgorithm": {
28
+          "$ref": "#/definitions/HashAlgorithmEnumType"
29
+        },
30
+        "issuerNameHash": {
31
+          "type": "string",
32
+          "maxLength": 128
33
+        },
34
+        "issuerKeyHash": {
35
+          "type": "string",
36
+          "maxLength": 128
37
+        },
38
+        "serialNumber": {
39
+          "type": "string",
40
+          "maxLength": 40
41
+        }
42
+      },
43
+      "required": [
44
+        "hashAlgorithm",
45
+        "issuerNameHash",
46
+        "issuerKeyHash",
47
+        "serialNumber"
48
+      ]
49
+    }
50
+  },
51
+  "type": "object",
52
+  "additionalProperties": false,
53
+  "properties": {
54
+    "certificateHashData": {
55
+      "type": "array",
56
+      "additionalItems": false,
57
+      "items": {
58
+        "$ref": "#/definitions/CertificateHashDataType"
59
+      },
60
+      "minItems": 1
61
+    },
62
+    "status": {
63
+      "$ref": "#/definitions/GetInstalledCertificateStatusEnumType"
64
+    }
65
+  },
66
+  "required": [
67
+    "status"
68
+  ]
69
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/GetLocalListVersion.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetLocalListVersionRequest",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 14 - 0
ocpp/ocpp/v16/schemas/GetLocalListVersionResponse.json

@@ -0,0 +1,14 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "GetLocalListVersionResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "listVersion": {
7
+            "type": "integer"
8
+        }
9
+    },
10
+    "additionalProperties": false,
11
+    "required": [
12
+        "listVersion"
13
+    ]
14
+}

+ 59 - 0
ocpp/ocpp/v16/schemas/GetLog.json

@@ -0,0 +1,59 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:GetLog.req",
4
+  "definitions": {
5
+    "LogEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "DiagnosticsLog",
10
+        "SecurityLog"
11
+      ]
12
+    },
13
+    "LogParametersType": {
14
+      "type": "object",
15
+      "additionalProperties": false,
16
+      "properties": {
17
+        "remoteLocation": {
18
+          "type": "string",
19
+          "maxLength": 512
20
+        },
21
+        "oldestTimestamp": {
22
+          "type": "string",
23
+          "format": "date-time"
24
+        },
25
+        "latestTimestamp": {
26
+          "type": "string",
27
+          "format": "date-time"
28
+        }
29
+      },
30
+      "required": [
31
+        "remoteLocation"
32
+      ]
33
+    }
34
+  },
35
+  "type": "object",
36
+  "additionalProperties": false,
37
+  "properties": {
38
+    "log": {
39
+      "$ref": "#/definitions/LogParametersType"
40
+    },
41
+    "logType": {
42
+      "$ref": "#/definitions/LogEnumType"
43
+    },
44
+    "requestId": {
45
+      "type": "integer"
46
+    },
47
+    "retries": {
48
+      "type": "integer"
49
+    },
50
+    "retryInterval": {
51
+      "type": "integer"
52
+    }
53
+  },
54
+  "required": [
55
+    "logType",
56
+    "requestId",
57
+    "log"
58
+  ]
59
+}

+ 29 - 0
ocpp/ocpp/v16/schemas/GetLogResponse.json

@@ -0,0 +1,29 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:GetLog.conf",
4
+  "definitions": {
5
+    "LogStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "Rejected",
11
+        "AcceptedCanceled"
12
+      ]
13
+    }
14
+  },
15
+  "type": "object",
16
+  "additionalProperties": false,
17
+  "properties": {
18
+    "status": {
19
+      "$ref": "#/definitions/LogStatusEnumType"
20
+    },
21
+    "filename": {
22
+      "type": "string",
23
+      "maxLength": 255
24
+    }
25
+  },
26
+  "required": [
27
+    "status"
28
+  ]
29
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/Heartbeat.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "HeartbeatRequest",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 15 - 0
ocpp/ocpp/v16/schemas/HeartbeatResponse.json

@@ -0,0 +1,15 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "HeartbeatResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "currentTime": {
7
+            "type": "string",
8
+            "format": "date-time"
9
+        }
10
+    },
11
+    "additionalProperties": false,
12
+    "required": [
13
+        "currentTime"
14
+    ]
15
+}

+ 29 - 0
ocpp/ocpp/v16/schemas/InstallCertificate.json

@@ -0,0 +1,29 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:InstallCertificate.req",
4
+  "definitions": {
5
+    "CertificateUseEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "CentralSystemRootCertificate",
10
+        "ManufacturerRootCertificate"
11
+      ]
12
+    }
13
+  },
14
+  "type": "object",
15
+  "additionalProperties": false,
16
+  "properties": {
17
+    "certificateType": {
18
+      "$ref": "#/definitions/CertificateUseEnumType"
19
+    },
20
+    "certificate": {
21
+      "type": "string",
22
+      "maxLength": 5500
23
+    }
24
+  },
25
+  "required": [
26
+    "certificateType",
27
+    "certificate"
28
+  ]
29
+}

+ 25 - 0
ocpp/ocpp/v16/schemas/InstallCertificateResponse.json

@@ -0,0 +1,25 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:InstallCertificate.conf",
4
+  "definitions": {
5
+    "InstallCertificateStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "Accepted",
10
+        "Failed",
11
+        "Rejected"
12
+      ]
13
+    }
14
+  },
15
+  "type": "object",
16
+  "additionalProperties": false,
17
+  "properties": {
18
+    "status": {
19
+      "$ref": "#/definitions/InstallCertificateStatusEnumType"
20
+    }
21
+  },
22
+  "required": [
23
+    "status"
24
+  ]
25
+}

+ 32 - 0
ocpp/ocpp/v16/schemas/LogStatusNotification.json

@@ -0,0 +1,32 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:LogStatusNotification.req",
4
+  "definitions": {
5
+    "UploadLogStatusEnumType": {
6
+      "type": "string",
7
+      "additionalProperties": false,
8
+      "enum": [
9
+        "BadMessage",
10
+        "Idle",
11
+        "NotSupportedOperation",
12
+        "PermissionDenied",
13
+        "Uploaded",
14
+        "UploadFailure",
15
+        "Uploading"
16
+      ]
17
+    }
18
+  },
19
+  "type": "object",
20
+  "additionalProperties": false,
21
+  "properties": {
22
+    "status": {
23
+      "$ref": "#/definitions/UploadLogStatusEnumType"
24
+    },
25
+    "requestId": {
26
+      "type": "integer"
27
+    }
28
+  },
29
+  "required": [
30
+    "status"
31
+  ]
32
+}

+ 6 - 0
ocpp/ocpp/v16/schemas/LogStatusNotificationResponse.json

@@ -0,0 +1,6 @@
1
+{
2
+  "$schema": "http://json-schema.org/draft-06/schema#",
3
+  "$id": "urn:OCPP:Cp:1.6:2020:3:LogStatusNotification.conf",
4
+  "type": "object",
5
+  "additionalProperties": false
6
+}

+ 153 - 0
ocpp/ocpp/v16/schemas/MeterValues.json

@@ -0,0 +1,153 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "MeterValuesRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "connectorId": {
7
+            "type": "integer"
8
+        },
9
+        "transactionId": {
10
+            "type": "integer"
11
+        },
12
+        "meterValue": {
13
+            "type": "array",
14
+            "minItems": 1,
15
+            "items": {
16
+                "type": "object",
17
+                "properties": {
18
+                    "timestamp": {
19
+                        "type": "string",
20
+                        "format": "date-time"
21
+                    },
22
+                    "sampledValue": {
23
+                        "type": "array",
24
+                        "minItems": 1,
25
+                        "items": {
26
+                            "type": "object",
27
+                            "properties": {
28
+                                "value": {
29
+                                    "type": "string"
30
+                                },
31
+                                "context": {
32
+                                    "type": "string",
33
+                                    "additionalProperties": false,
34
+                                    "enum": [
35
+                                        "Interruption.Begin",
36
+                                        "Interruption.End",
37
+                                        "Sample.Clock",
38
+                                        "Sample.Periodic",
39
+                                        "Transaction.Begin",
40
+                                        "Transaction.End",
41
+                                        "Trigger",
42
+                                        "Other"
43
+                                    ]
44
+                                },
45
+                                "format": {
46
+                                    "type": "string",
47
+                                    "additionalProperties": false,
48
+                                    "enum": [
49
+                                        "Raw",
50
+                                        "SignedData"
51
+                                    ]
52
+                                },
53
+                                "measurand": {
54
+                                    "type": "string",
55
+                                    "additionalProperties": false,
56
+                                    "enum": [
57
+                                        "Energy.Active.Export.Register",
58
+                                        "Energy.Active.Import.Register",
59
+                                        "Energy.Reactive.Export.Register",
60
+                                        "Energy.Reactive.Import.Register",
61
+                                        "Energy.Active.Export.Interval",
62
+                                        "Energy.Active.Import.Interval",
63
+                                        "Energy.Reactive.Export.Interval",
64
+                                        "Energy.Reactive.Import.Interval",
65
+                                        "Power.Active.Export",
66
+                                        "Power.Active.Import",
67
+                                        "Power.Offered",
68
+                                        "Power.Reactive.Export",
69
+                                        "Power.Reactive.Import",
70
+                                        "Power.Factor",
71
+                                        "Current.Import",
72
+                                        "Current.Export",
73
+                                        "Current.Offered",
74
+                                        "Voltage",
75
+                                        "Frequency",
76
+                                        "Temperature",
77
+                                        "SoC",
78
+                                        "RPM"
79
+                                    ]
80
+                                },
81
+                                "phase": {
82
+                                    "type": "string",
83
+                                    "additionalProperties": false,
84
+                                    "enum": [
85
+                                        "L1",
86
+                                        "L2",
87
+                                        "L3",
88
+                                        "N",
89
+                                        "L1-N",
90
+                                        "L2-N",
91
+                                        "L3-N",
92
+                                        "L1-L2",
93
+                                        "L2-L3",
94
+                                        "L3-L1"
95
+                                    ]
96
+                                },
97
+                                "location": {
98
+                                    "type": "string",
99
+                                    "additionalProperties": false,
100
+                                    "enum": [
101
+                                        "Cable",
102
+                                        "EV",
103
+                                        "Inlet",
104
+                                        "Outlet",
105
+                                        "Body"
106
+                                    ]
107
+                                },
108
+                                "unit": {
109
+                                    "type": "string",
110
+                                    "additionalProperties": false,
111
+                                    "enum": [
112
+                                        "Wh",
113
+                                        "kWh",
114
+                                        "varh",
115
+                                        "kvarh",
116
+                                        "W",
117
+                                        "kW",
118
+                                        "VA",
119
+                                        "kVA",
120
+                                        "var",
121
+                                        "kvar",
122
+                                        "A",
123
+                                        "V",
124
+                                        "K",
125
+                                        "Celcius",
126
+                                        "Celsius",
127
+                                        "Fahrenheit",
128
+                                        "Percent",
129
+                                        "Hertz"
130
+                                    ]
131
+                                }
132
+                            },
133
+                            "additionalProperties": false,
134
+                            "required": [
135
+                                "value"
136
+                            ]
137
+                        }
138
+                    }
139
+                },
140
+                "additionalProperties": false,
141
+                "required": [
142
+                    "timestamp",
143
+                    "sampledValue"
144
+                ]       
145
+            }
146
+        }
147
+    },
148
+    "additionalProperties": false,
149
+    "required": [
150
+        "connectorId",
151
+        "meterValue"
152
+    ]
153
+}

+ 7 - 0
ocpp/ocpp/v16/schemas/MeterValuesResponse.json

@@ -0,0 +1,7 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "MeterValuesResponse",
4
+    "type": "object",
5
+    "properties": {},
6
+    "additionalProperties": false
7
+}

+ 126 - 0
ocpp/ocpp/v16/schemas/RemoteStartTransaction.json

@@ -0,0 +1,126 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "RemoteStartTransactionRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "connectorId": {
7
+            "type": "integer"
8
+        },
9
+        "idTag": {
10
+            "type": "string",
11
+            "maxLength": 20
12
+        },
13
+        "chargingProfile": {
14
+            "type": "object",
15
+            "properties": {
16
+                "chargingProfileId": {
17
+                    "type": "integer"
18
+                },
19
+                "transactionId": {
20
+                    "type": "integer"
21
+                },
22
+                "stackLevel": {
23
+                    "type": "integer"
24
+                },
25
+                "chargingProfilePurpose": {
26
+                    "type": "string",
27
+                    "additionalProperties": false,
28
+                    "enum": [
29
+                        "ChargePointMaxProfile",
30
+                        "TxDefaultProfile",
31
+                        "TxProfile"
32
+                    ]
33
+                },
34
+                "chargingProfileKind": {
35
+                    "type": "string",
36
+                    "additionalProperties": false,
37
+                    "enum": [
38
+                        "Absolute",
39
+                        "Recurring",
40
+                        "Relative"
41
+                    ]
42
+                },
43
+                "recurrencyKind": {
44
+                    "type": "string",
45
+                    "additionalProperties": false,
46
+                    "enum": [
47
+                        "Daily",
48
+                        "Weekly"
49
+                    ]
50
+                },
51
+                "validFrom": {
52
+                    "type": "string",
53
+                    "format": "date-time"
54
+                },
55
+                "validTo": {
56
+                    "type": "string",
57
+                    "format": "date-time"
58
+                },
59
+                "chargingSchedule": {
60
+                    "type": "object",
61
+                    "properties": {
62
+                        "duration": {
63
+                            "type": "integer"
64
+                        },
65
+                        "startSchedule": {
66
+                            "type": "string",
67
+                            "format": "date-time"
68
+                        },
69
+                        "chargingRateUnit": {
70
+                            "type": "string",
71
+                            "additionalProperties": false,
72
+                            "enum": [
73
+                                "A",
74
+                                "W"
75
+                            ]
76
+                        },
77
+                        "chargingSchedulePeriod": {
78
+                            "type": "array",
79
+                            "items": {
80
+                                "type": "object",
81
+                                "properties": {
82
+                                    "startPeriod": {
83
+                                        "type": "integer"
84
+                                    },
85
+                                    "limit": {
86
+                                        "type": "number",
87
+                                        "multipleOf" : 0.1
88
+                                    },
89
+                                    "numberPhases": {
90
+                                        "type": "integer"
91
+                                    }
92
+                                },
93
+                                "additionalProperties": false,
94
+                                "required": [
95
+                                    "startPeriod",
96
+                                    "limit"
97
+                                ]
98
+                            }
99
+                        },
100
+                        "minChargingRate": {
101
+                            "type": "number",
102
+                            "multipleOf" : 0.1
103
+                        }
104
+                    },
105
+                    "additionalProperties": false,
106
+                    "required": [
107
+                        "chargingRateUnit",
108
+                        "chargingSchedulePeriod"
109
+                    ]
110
+                }
111
+            },
112
+            "additionalProperties": false,
113
+            "required": [
114
+                "chargingProfileId",
115
+                "stackLevel",
116
+                "chargingProfilePurpose",
117
+                "chargingProfileKind",
118
+                "chargingSchedule"
119
+            ]
120
+        }
121
+    },
122
+    "additionalProperties": false,
123
+    "required": [
124
+        "idTag"
125
+    ]
126
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/RemoteStartTransactionResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "RemoteStartTransactionResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 14 - 0
ocpp/ocpp/v16/schemas/RemoteStopTransaction.json

@@ -0,0 +1,14 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "RemoteStopTransactionRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "transactionId": {
7
+            "type": "integer"
8
+        }
9
+    },
10
+    "additionalProperties": false,
11
+    "required": [
12
+        "transactionId"
13
+    ]
14
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/RemoteStopTransactionResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "RemoteStopTransactionResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 32 - 0
ocpp/ocpp/v16/schemas/ReserveNow.json

@@ -0,0 +1,32 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ReserveNowRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "connectorId": {
7
+            "type": "integer"
8
+        },
9
+        "expiryDate": {
10
+            "type": "string",
11
+            "format": "date-time"
12
+        },
13
+        "idTag": {
14
+            "type": "string",
15
+            "maxLength": 20
16
+        },
17
+        "parentIdTag": {
18
+            "type": "string",
19
+            "maxLength": 20
20
+        },
21
+        "reservationId": {
22
+            "type": "integer"
23
+        }
24
+    },
25
+    "additionalProperties": false,
26
+    "required": [
27
+        "connectorId",
28
+        "expiryDate",
29
+        "idTag",
30
+        "reservationId"
31
+    ]
32
+}

+ 22 - 0
ocpp/ocpp/v16/schemas/ReserveNowResponse.json

@@ -0,0 +1,22 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ReserveNowResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Faulted",
12
+                "Occupied",
13
+                "Rejected",
14
+                "Unavailable"
15
+            ]
16
+        }
17
+    },
18
+    "additionalProperties": false,
19
+    "required": [
20
+        "status"
21
+    ]
22
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/Reset.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ResetRequest",
4
+    "type": "object",
5
+    "properties": {
6
+        "type": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Hard",
11
+                "Soft"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "type"
18
+    ]
19
+}

+ 19 - 0
ocpp/ocpp/v16/schemas/ResetResponse.json

@@ -0,0 +1,19 @@
1
+{
2
+    "$schema": "http://json-schema.org/draft-04/schema#",
3
+    "title": "ResetResponse",
4
+    "type": "object",
5
+    "properties": {
6
+        "status": {
7
+            "type": "string",
8
+            "additionalProperties": false,
9
+            "enum": [
10
+                "Accepted",
11
+                "Rejected"
12
+            ]
13
+        }
14
+    },
15
+    "additionalProperties": false,
16
+    "required": [
17
+        "status"
18
+    ]
19
+}

+ 0 - 0
ocpp/ocpp/v16/schemas/SecurityEventNotification.json


Some files were not shown because too many files changed in this diff

tum/soc - Gogs: Simplico Git Service

Nav apraksta

dockerpull.sh 756B

123456
  1. #!/bin/sh
  2. #curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
  3. #curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -O -J
  4. #curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
  5. curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:shuffle-tools_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz