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
ocpp/docs/v16/ocpp-1.6 edition 2.pdf


BIN
ocpp/docs/v16/ocpp-1.6-errata-sheet.pdf


BIN
ocpp/docs/v16/ocpp-1.6-security-whitepaper-edition-2.pdf


Dosya farkı çok büyük olduğundan ihmal edildi
+ 188194 - 0
ocpp/docs/v16/ocpp-1.6.pdf


BIN
ocpp/docs/v16/ocpp-j-1.6-errata-sheet.pdf


Dosya farkı çok büyük olduğundan ihmal edildi
+ 16654 - 0
ocpp/docs/v16/ocpp-j-1.6-specification.pdf


BIN
ocpp/docs/v201/Changelog OCPP 2.0 - 2.0.1.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_edition2_errata_2023-12.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part0_introduction.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part1_architecture_topology.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part2_appendices_v13.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part2_errata_v20.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part2_specification_edition2.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part4_ocpp-j-specification.pdf


BIN
ocpp/docs/v201/OCPP-2.0.1_part5_certification_profiles.pdf


BIN
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


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor

tum/tmt_learning - Gogs: Simplico Git Service

Bez popisu

LICENSE 1.0KB

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