pybluecurrent 0.0.3__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.pre-commit-config.yaml +2 -2
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/PKG-INFO +33 -48
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/README.md +26 -42
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/pyproject.toml +9 -4
- pybluecurrent-0.1.0/src/pybluecurrent/_version.py +34 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/client.py +40 -40
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/PKG-INFO +33 -48
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/requires.txt +4 -4
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/conftest.py +4 -19
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/test_client.py +25 -42
- pybluecurrent-0.0.3/src/pybluecurrent/_version.py +0 -16
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.github/workflows/publish.yaml +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.github/workflows/test.yaml +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.gitignore +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/LICENSE +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/setup.cfg +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/__init__.py +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/exceptions.py +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/py.typed +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/utilities.py +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/SOURCES.txt +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/dependency_links.txt +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/top_level.txt +0 -0
- {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/test_utilities.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
fail_fast: true
|
|
2
2
|
repos:
|
|
3
3
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
4
|
-
rev:
|
|
4
|
+
rev: v6.0.0
|
|
5
5
|
hooks:
|
|
6
6
|
- id: trailing-whitespace
|
|
7
7
|
exclude: '\.(md|markdown)$'
|
|
@@ -27,7 +27,7 @@ repos:
|
|
|
27
27
|
- id: isort
|
|
28
28
|
exclude: __init__.py
|
|
29
29
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
30
|
-
rev:
|
|
30
|
+
rev: v1.18.2
|
|
31
31
|
hooks:
|
|
32
32
|
- id: mypy
|
|
33
33
|
additional_dependencies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pybluecurrent
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Python client for BlueCurrent charge points.
|
|
5
5
|
Author-email: Rogier van der Geer <rogier@vander-geer.nl>
|
|
6
6
|
License: MIT
|
|
@@ -15,14 +15,15 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: asyncio-multisubscriber-queue>=0.4.1
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: httpx>=0.28
|
|
19
19
|
Requires-Dist: sjcl>=0.2.1
|
|
20
|
-
Requires-Dist: websockets>=
|
|
20
|
+
Requires-Dist: websockets>=14.0
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: black==23.3.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pre-commit>=3.3.3; extra == "dev"
|
|
24
|
-
Requires-Dist: pytest==8.
|
|
25
|
-
Requires-Dist: pytest-asyncio==
|
|
24
|
+
Requires-Dist: pytest==8.4.2; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio==1.2.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
26
27
|
|
|
27
28
|
# pybluecurrent
|
|
28
29
|
|
|
@@ -42,36 +43,32 @@ from pybluecurrent import BlueCurrentClient
|
|
|
42
43
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
43
44
|
|
|
44
45
|
async with client:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
transactions = client.get_transactions(charge_points[0]["evse_id"])
|
|
46
|
+
charge_points = await client.get_charge_points()
|
|
47
|
+
transactions = await client.get_transactions(charge_points[0]["evse_id"])
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Methods
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
|
|
52
|
+
The `BlueCurrentClient` exposes the following methods:
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
The async methods can only be used when the websocket client is connected. For example:
|
|
54
|
+
- [`get_account`](#getaccount---get-your-account-information)
|
|
55
|
+
- [`get_charge_cards`](#getchargecards---get-your-charge-cards)
|
|
56
|
+
- [`get_charge_points`](#getchargepoints---get-your-charge-points)
|
|
57
|
+
- [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
|
|
58
|
+
- [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
|
|
59
|
+
- [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
|
|
60
|
+
- [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
|
|
61
|
+
- [`set_status`](#setstatus---enable-or-disable-a-charge-point)
|
|
62
|
+
- [`login`](#login---log-in)
|
|
63
|
+
- [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
|
|
64
|
+
- [`get_contracts`](#getcontracts---get-your-contracts)
|
|
65
|
+
- [`get_grids`](#getgrids---get-your-grid-connections)
|
|
66
|
+
- [`get_transactions`](#gettransactions---get-a-list-of-transactions)
|
|
67
|
+
- [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
|
|
68
|
+
|
|
69
|
+
### Connection
|
|
70
|
+
|
|
71
|
+
The client can only be used when the websocket client is connected. For example:
|
|
75
72
|
```python
|
|
76
73
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
77
74
|
async with client:
|
|
@@ -247,22 +244,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
|
|
|
247
244
|
- `evse_id`: The ID of the charge point.
|
|
248
245
|
- `enabled`: Boolean that indicates the desired status.
|
|
249
246
|
|
|
250
|
-
### Synchronous
|
|
251
|
-
|
|
252
|
-
#### `login` - Log in
|
|
253
|
-
```python
|
|
254
|
-
def login(self) -> None
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
This method does not do anything if the client is already logged in.
|
|
258
|
-
Connection to the websocket api (async with client) automatically logs in the client,
|
|
259
|
-
so this endpoint is not needed when using the async API.
|
|
260
|
-
|
|
261
|
-
|
|
262
247
|
#### `get_charge_point_status` - Get the status of a charge point.
|
|
263
248
|
|
|
264
249
|
```python
|
|
265
|
-
def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
250
|
+
async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
266
251
|
```
|
|
267
252
|
|
|
268
253
|
##### Arguments
|
|
@@ -295,7 +280,7 @@ A dictionary with the chargepoint status:
|
|
|
295
280
|
#### `get_contracts` - Get your contracts.
|
|
296
281
|
|
|
297
282
|
```python
|
|
298
|
-
def get_contracts(self) -> list[dict[str, str]]
|
|
283
|
+
async def get_contracts(self) -> list[dict[str, str]]
|
|
299
284
|
```
|
|
300
285
|
|
|
301
286
|
##### Returns
|
|
@@ -315,7 +300,7 @@ A list of dictionaries, each representing a contract:
|
|
|
315
300
|
#### `get_grids` - Get your grid connections.
|
|
316
301
|
|
|
317
302
|
```python
|
|
318
|
-
def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
303
|
+
async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
319
304
|
```
|
|
320
305
|
|
|
321
306
|
##### Returns
|
|
@@ -334,7 +319,7 @@ A list of dictionaries, each representing a grid:
|
|
|
334
319
|
#### `get_transactions` - Get a list of transactions.
|
|
335
320
|
|
|
336
321
|
```python
|
|
337
|
-
def get_transactions(
|
|
322
|
+
async def get_transactions(
|
|
338
323
|
self, evse_id: str, newest_first: bool = True, page: int = 1
|
|
339
324
|
) -> dict[str, int | list[dict[str, Any]]]
|
|
340
325
|
```
|
|
@@ -376,7 +361,7 @@ A dictionary like this:
|
|
|
376
361
|
#### `iterate_transactions` - Iterate through your transactions
|
|
377
362
|
|
|
378
363
|
```python
|
|
379
|
-
def iterate_transactions(self, evse_id: str, newest_first: bool = True) ->
|
|
364
|
+
async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
|
|
380
365
|
```
|
|
381
366
|
|
|
382
367
|
##### Arguments
|
|
@@ -16,36 +16,32 @@ from pybluecurrent import BlueCurrentClient
|
|
|
16
16
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
17
17
|
|
|
18
18
|
async with client:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
transactions = client.get_transactions(charge_points[0]["evse_id"])
|
|
19
|
+
charge_points = await client.get_charge_points()
|
|
20
|
+
transactions = await client.get_transactions(charge_points[0]["evse_id"])
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
## Methods
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
|
|
25
|
+
The `BlueCurrentClient` exposes the following methods:
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
The async methods can only be used when the websocket client is connected. For example:
|
|
27
|
+
- [`get_account`](#getaccount---get-your-account-information)
|
|
28
|
+
- [`get_charge_cards`](#getchargecards---get-your-charge-cards)
|
|
29
|
+
- [`get_charge_points`](#getchargepoints---get-your-charge-points)
|
|
30
|
+
- [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
|
|
31
|
+
- [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
|
|
32
|
+
- [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
|
|
33
|
+
- [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
|
|
34
|
+
- [`set_status`](#setstatus---enable-or-disable-a-charge-point)
|
|
35
|
+
- [`login`](#login---log-in)
|
|
36
|
+
- [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
|
|
37
|
+
- [`get_contracts`](#getcontracts---get-your-contracts)
|
|
38
|
+
- [`get_grids`](#getgrids---get-your-grid-connections)
|
|
39
|
+
- [`get_transactions`](#gettransactions---get-a-list-of-transactions)
|
|
40
|
+
- [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
|
|
41
|
+
|
|
42
|
+
### Connection
|
|
43
|
+
|
|
44
|
+
The client can only be used when the websocket client is connected. For example:
|
|
49
45
|
```python
|
|
50
46
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
51
47
|
async with client:
|
|
@@ -221,22 +217,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
|
|
|
221
217
|
- `evse_id`: The ID of the charge point.
|
|
222
218
|
- `enabled`: Boolean that indicates the desired status.
|
|
223
219
|
|
|
224
|
-
### Synchronous
|
|
225
|
-
|
|
226
|
-
#### `login` - Log in
|
|
227
|
-
```python
|
|
228
|
-
def login(self) -> None
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
This method does not do anything if the client is already logged in.
|
|
232
|
-
Connection to the websocket api (async with client) automatically logs in the client,
|
|
233
|
-
so this endpoint is not needed when using the async API.
|
|
234
|
-
|
|
235
|
-
|
|
236
220
|
#### `get_charge_point_status` - Get the status of a charge point.
|
|
237
221
|
|
|
238
222
|
```python
|
|
239
|
-
def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
223
|
+
async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
240
224
|
```
|
|
241
225
|
|
|
242
226
|
##### Arguments
|
|
@@ -269,7 +253,7 @@ A dictionary with the chargepoint status:
|
|
|
269
253
|
#### `get_contracts` - Get your contracts.
|
|
270
254
|
|
|
271
255
|
```python
|
|
272
|
-
def get_contracts(self) -> list[dict[str, str]]
|
|
256
|
+
async def get_contracts(self) -> list[dict[str, str]]
|
|
273
257
|
```
|
|
274
258
|
|
|
275
259
|
##### Returns
|
|
@@ -289,7 +273,7 @@ A list of dictionaries, each representing a contract:
|
|
|
289
273
|
#### `get_grids` - Get your grid connections.
|
|
290
274
|
|
|
291
275
|
```python
|
|
292
|
-
def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
276
|
+
async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
293
277
|
```
|
|
294
278
|
|
|
295
279
|
##### Returns
|
|
@@ -308,7 +292,7 @@ A list of dictionaries, each representing a grid:
|
|
|
308
292
|
#### `get_transactions` - Get a list of transactions.
|
|
309
293
|
|
|
310
294
|
```python
|
|
311
|
-
def get_transactions(
|
|
295
|
+
async def get_transactions(
|
|
312
296
|
self, evse_id: str, newest_first: bool = True, page: int = 1
|
|
313
297
|
) -> dict[str, int | list[dict[str, Any]]]
|
|
314
298
|
```
|
|
@@ -350,7 +334,7 @@ A dictionary like this:
|
|
|
350
334
|
#### `iterate_transactions` - Iterate through your transactions
|
|
351
335
|
|
|
352
336
|
```python
|
|
353
|
-
def iterate_transactions(self, evse_id: str, newest_first: bool = True) ->
|
|
337
|
+
async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
|
|
354
338
|
```
|
|
355
339
|
|
|
356
340
|
##### Arguments
|
|
@@ -21,18 +21,18 @@ classifiers = [
|
|
|
21
21
|
"License :: OSI Approved :: MIT License",
|
|
22
22
|
"Operating System :: OS Independent",
|
|
23
23
|
"Programming Language :: Python :: 3",
|
|
24
|
-
"Typing :: Typed"
|
|
24
|
+
"Typing :: Typed",
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
27
|
"asyncio-multisubscriber-queue>=0.4.1",
|
|
28
|
-
"
|
|
28
|
+
"httpx>=0.28",
|
|
29
29
|
"sjcl>=0.2.1",
|
|
30
|
-
"websockets>=
|
|
30
|
+
"websockets>=14.0",
|
|
31
31
|
]
|
|
32
32
|
dynamic = ["version"]
|
|
33
33
|
|
|
34
34
|
[project.optional-dependencies]
|
|
35
|
-
dev = ["black==23.3.0", "pre-commit>=3.3.3", "pytest==8.
|
|
35
|
+
dev = ["black==23.3.0", "pre-commit>=3.3.3", "pytest==8.4.2", "pytest-asyncio==1.2.0"]
|
|
36
36
|
|
|
37
37
|
[project.urls]
|
|
38
38
|
Repository = "https://github.com/rogiervandergeer/pybluecurrent"
|
|
@@ -53,3 +53,8 @@ profile = "black"
|
|
|
53
53
|
[tool.ruff]
|
|
54
54
|
line-length = 120
|
|
55
55
|
target-version = "py310"
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
asyncio_mode = "auto"
|
|
59
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
60
|
+
asyncio_default_test_loop_scope = "session"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = 'g1fb6788fb'
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from asyncio import Task, create_task,
|
|
1
|
+
from asyncio import Task, create_task, wait_for
|
|
2
2
|
from datetime import date, datetime
|
|
3
3
|
from json import dumps, loads
|
|
4
4
|
from logging import getLogger
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, AsyncIterable
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
8
|
from asyncio_multisubscriber_queue import MultisubscriberQueue
|
|
9
|
-
from
|
|
9
|
+
from httpx import AsyncClient
|
|
10
10
|
from sjcl import SJCL
|
|
11
|
-
from websockets import
|
|
11
|
+
from websockets.asyncio.client import ClientConnection, connect
|
|
12
12
|
|
|
13
13
|
from pybluecurrent._version import __version__
|
|
14
14
|
from pybluecurrent.exceptions import AuthenticationFailed, BlueCurrentException
|
|
@@ -24,17 +24,18 @@ class BlueCurrentClient:
|
|
|
24
24
|
self.consumer: Task | None = None
|
|
25
25
|
self.credentials: tuple[str, str] = (username, password)
|
|
26
26
|
self.logger = getLogger("BlueCurrentClient")
|
|
27
|
+
self.httpx_client: AsyncClient | None = None
|
|
27
28
|
self.queue = MultisubscriberQueue()
|
|
28
|
-
self.socket:
|
|
29
|
+
self.socket: ClientConnection | None = None
|
|
29
30
|
self.token: str | None = None
|
|
30
31
|
|
|
31
|
-
## Asynchronous API ##
|
|
32
|
-
|
|
33
32
|
async def __aenter__(self) -> "BlueCurrentClient":
|
|
34
33
|
self.logger.debug("Creating BlueCurrent websocket connection")
|
|
35
34
|
self.connection = connect(self.socket_url, user_agent_header=self._user_agent)
|
|
36
35
|
self.socket = await self.connection.__aenter__()
|
|
37
36
|
self.consumer = create_task(self._handler())
|
|
37
|
+
self.httpx_client = AsyncClient()
|
|
38
|
+
await self.httpx_client.__aenter__()
|
|
38
39
|
if self.token is None:
|
|
39
40
|
await self._login()
|
|
40
41
|
await self._hello()
|
|
@@ -44,7 +45,8 @@ class BlueCurrentClient:
|
|
|
44
45
|
self.logger.debug("Closing BlueCurrent connection")
|
|
45
46
|
self.consumer.cancel()
|
|
46
47
|
await self.connection.__aexit__(exc_type, exc_val, exc_tb)
|
|
47
|
-
self.
|
|
48
|
+
await self.httpx_client.__aexit__(exc_type, exc_val, exc_tb)
|
|
49
|
+
self.consumer, self.socket, self.httpx_client = None, None, None
|
|
48
50
|
|
|
49
51
|
async def get_account(self) -> dict[str, bool | date | str]:
|
|
50
52
|
"""
|
|
@@ -212,10 +214,17 @@ class BlueCurrentClient:
|
|
|
212
214
|
|
|
213
215
|
Args:
|
|
214
216
|
evse_id: A charge point ID.
|
|
215
|
-
uid: A charge card UID or None
|
|
217
|
+
uid: A charge card UID or None. Defaults to None.
|
|
218
|
+
Setting the plug-and-charge card to None will result in plug-and-charge
|
|
219
|
+
transactions being started without a charge card. Note that the
|
|
220
|
+
charge point status will show "BCU_HOME_USE" as the charge card.
|
|
221
|
+
Setting the plug-and-charge card to "BCU_HOME_USE" has the same effect
|
|
222
|
+
as setting it to None.
|
|
216
223
|
"""
|
|
224
|
+
token_uid = "BCU-APP" if uid is None or uid == "BCU_HOME_USE" else uid
|
|
217
225
|
await self._send(
|
|
218
|
-
dict(command="SET_PLUG_AND_CHARGE_CHARGE_CARD", evse_id=evse_id, token_uid=
|
|
226
|
+
dict(command="SET_PLUG_AND_CHARGE_CHARGE_CARD", evse_id=evse_id, token_uid=token_uid),
|
|
227
|
+
token=True,
|
|
219
228
|
)
|
|
220
229
|
result = await self._receive("STATUS_SET_PLUG_AND_CHARGE_CHARGE_CARD")
|
|
221
230
|
if not result.get("success"):
|
|
@@ -256,25 +265,7 @@ class BlueCurrentClient:
|
|
|
256
265
|
await self._receive("RECEIVED_SOFT_RESET")
|
|
257
266
|
return await self._receive("STATUS_SOFT_RESET", timeout=30)
|
|
258
267
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def login(self) -> None:
|
|
262
|
-
"""
|
|
263
|
-
Retrieves a connection token.
|
|
264
|
-
|
|
265
|
-
This method does not do anything if the client is already logged in.
|
|
266
|
-
Connection to the websocket api (async with client) automatically logs in the client,
|
|
267
|
-
so this endpoint is not needed when using the async API.
|
|
268
|
-
"""
|
|
269
|
-
|
|
270
|
-
async def _login(client: BlueCurrentClient) -> None:
|
|
271
|
-
async with client:
|
|
272
|
-
pass
|
|
273
|
-
|
|
274
|
-
if self.token is None:
|
|
275
|
-
run(_login(self))
|
|
276
|
-
|
|
277
|
-
def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]:
|
|
268
|
+
async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]:
|
|
278
269
|
"""
|
|
279
270
|
Get the status of a charge point.
|
|
280
271
|
|
|
@@ -300,7 +291,9 @@ class BlueCurrentClient:
|
|
|
300
291
|
"evse_id": "BCU123456",
|
|
301
292
|
}
|
|
302
293
|
"""
|
|
303
|
-
|
|
294
|
+
if self.httpx_client is None:
|
|
295
|
+
raise RuntimeError(f"{self.__class__.__name__} is not connected.")
|
|
296
|
+
response = await self.httpx_client.get(
|
|
304
297
|
f"{self.api_url}/chargepointstatus?evse_id={evse_id}",
|
|
305
298
|
headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
|
|
306
299
|
)
|
|
@@ -314,7 +307,7 @@ class BlueCurrentClient:
|
|
|
314
307
|
},
|
|
315
308
|
)
|
|
316
309
|
|
|
317
|
-
def get_contracts(self) -> list[dict[str, str]]:
|
|
310
|
+
async def get_contracts(self) -> list[dict[str, str]]:
|
|
318
311
|
"""
|
|
319
312
|
Get your contracts.
|
|
320
313
|
|
|
@@ -330,14 +323,16 @@ class BlueCurrentClient:
|
|
|
330
323
|
}
|
|
331
324
|
]
|
|
332
325
|
"""
|
|
333
|
-
|
|
326
|
+
if self.httpx_client is None:
|
|
327
|
+
raise RuntimeError(f"{self.__class__.__name__} is not connected.")
|
|
328
|
+
response = await self.httpx_client.get(
|
|
334
329
|
f"{self.api_url}/getcontracts",
|
|
335
330
|
headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
|
|
336
331
|
)
|
|
337
332
|
response.raise_for_status()
|
|
338
333
|
return response.json()["contracts"]
|
|
339
334
|
|
|
340
|
-
def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]:
|
|
335
|
+
async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]:
|
|
341
336
|
"""
|
|
342
337
|
Get your grid connections.
|
|
343
338
|
|
|
@@ -352,14 +347,16 @@ class BlueCurrentClient:
|
|
|
352
347
|
}
|
|
353
348
|
]
|
|
354
349
|
"""
|
|
355
|
-
|
|
350
|
+
if self.httpx_client is None:
|
|
351
|
+
raise RuntimeError(f"{self.__class__.__name__} is not connected.")
|
|
352
|
+
response = await self.httpx_client.get(
|
|
356
353
|
f"{self.api_url}/getgrids",
|
|
357
354
|
headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
|
|
358
355
|
)
|
|
359
356
|
response.raise_for_status()
|
|
360
357
|
return response.json()["grids"]
|
|
361
358
|
|
|
362
|
-
def get_transactions(
|
|
359
|
+
async def get_transactions(
|
|
363
360
|
self, evse_id: str, newest_first: bool = True, page: int = 1
|
|
364
361
|
) -> dict[str, int | list[dict[str, Any]]]:
|
|
365
362
|
"""
|
|
@@ -398,13 +395,15 @@ class BlueCurrentClient:
|
|
|
398
395
|
}
|
|
399
396
|
|
|
400
397
|
"""
|
|
401
|
-
|
|
398
|
+
if self.httpx_client is None:
|
|
399
|
+
raise RuntimeError(f"{self.__class__.__name__} is not connected.")
|
|
400
|
+
response = await self.httpx_client.post(
|
|
402
401
|
f"{self.api_url}/gettransactions?"
|
|
403
402
|
f"page={page}&"
|
|
404
403
|
f"sort_field_order={'DESC' if newest_first else 'ASC'}&"
|
|
405
404
|
f"sort_field=stoppedtimestamp",
|
|
406
405
|
headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
|
|
407
|
-
data=dumps({"chargepoints": [{"chargepoint_id": evse_id}]})
|
|
406
|
+
data=dumps({"chargepoints": [{"chargepoint_id": evse_id}]}),
|
|
408
407
|
)
|
|
409
408
|
response.raise_for_status()
|
|
410
409
|
result = response.json()["data"]
|
|
@@ -414,7 +413,7 @@ class BlueCurrentClient:
|
|
|
414
413
|
)
|
|
415
414
|
return result
|
|
416
415
|
|
|
417
|
-
def iterate_transactions(self, evse_id: str, newest_first: bool = True) ->
|
|
416
|
+
async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]:
|
|
418
417
|
"""
|
|
419
418
|
Iterate through your transactions.
|
|
420
419
|
|
|
@@ -443,8 +442,9 @@ class BlueCurrentClient:
|
|
|
443
442
|
"""
|
|
444
443
|
next_page = 1
|
|
445
444
|
while next_page is not None:
|
|
446
|
-
transactions = self.get_transactions(evse_id=evse_id, newest_first=newest_first, page=next_page)
|
|
447
|
-
|
|
445
|
+
transactions = await self.get_transactions(evse_id=evse_id, newest_first=newest_first, page=next_page)
|
|
446
|
+
for tx in transactions["transactions"]: # type: ignore
|
|
447
|
+
yield tx
|
|
448
448
|
next_page = transactions["next_page"] # type: ignore
|
|
449
449
|
|
|
450
450
|
async def _login(self) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pybluecurrent
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Python client for BlueCurrent charge points.
|
|
5
5
|
Author-email: Rogier van der Geer <rogier@vander-geer.nl>
|
|
6
6
|
License: MIT
|
|
@@ -15,14 +15,15 @@ Requires-Python: >=3.10
|
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: asyncio-multisubscriber-queue>=0.4.1
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: httpx>=0.28
|
|
19
19
|
Requires-Dist: sjcl>=0.2.1
|
|
20
|
-
Requires-Dist: websockets>=
|
|
20
|
+
Requires-Dist: websockets>=14.0
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: black==23.3.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pre-commit>=3.3.3; extra == "dev"
|
|
24
|
-
Requires-Dist: pytest==8.
|
|
25
|
-
Requires-Dist: pytest-asyncio==
|
|
24
|
+
Requires-Dist: pytest==8.4.2; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-asyncio==1.2.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
26
27
|
|
|
27
28
|
# pybluecurrent
|
|
28
29
|
|
|
@@ -42,36 +43,32 @@ from pybluecurrent import BlueCurrentClient
|
|
|
42
43
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
43
44
|
|
|
44
45
|
async with client:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
transactions = client.get_transactions(charge_points[0]["evse_id"])
|
|
46
|
+
charge_points = await client.get_charge_points()
|
|
47
|
+
transactions = await client.get_transactions(charge_points[0]["evse_id"])
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Methods
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
|
|
52
|
+
The `BlueCurrentClient` exposes the following methods:
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
The async methods can only be used when the websocket client is connected. For example:
|
|
54
|
+
- [`get_account`](#getaccount---get-your-account-information)
|
|
55
|
+
- [`get_charge_cards`](#getchargecards---get-your-charge-cards)
|
|
56
|
+
- [`get_charge_points`](#getchargepoints---get-your-charge-points)
|
|
57
|
+
- [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
|
|
58
|
+
- [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
|
|
59
|
+
- [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
|
|
60
|
+
- [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
|
|
61
|
+
- [`set_status`](#setstatus---enable-or-disable-a-charge-point)
|
|
62
|
+
- [`login`](#login---log-in)
|
|
63
|
+
- [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
|
|
64
|
+
- [`get_contracts`](#getcontracts---get-your-contracts)
|
|
65
|
+
- [`get_grids`](#getgrids---get-your-grid-connections)
|
|
66
|
+
- [`get_transactions`](#gettransactions---get-a-list-of-transactions)
|
|
67
|
+
- [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
|
|
68
|
+
|
|
69
|
+
### Connection
|
|
70
|
+
|
|
71
|
+
The client can only be used when the websocket client is connected. For example:
|
|
75
72
|
```python
|
|
76
73
|
client = BlueCurrentClient("your_username", "your_secret_password")
|
|
77
74
|
async with client:
|
|
@@ -247,22 +244,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
|
|
|
247
244
|
- `evse_id`: The ID of the charge point.
|
|
248
245
|
- `enabled`: Boolean that indicates the desired status.
|
|
249
246
|
|
|
250
|
-
### Synchronous
|
|
251
|
-
|
|
252
|
-
#### `login` - Log in
|
|
253
|
-
```python
|
|
254
|
-
def login(self) -> None
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
This method does not do anything if the client is already logged in.
|
|
258
|
-
Connection to the websocket api (async with client) automatically logs in the client,
|
|
259
|
-
so this endpoint is not needed when using the async API.
|
|
260
|
-
|
|
261
|
-
|
|
262
247
|
#### `get_charge_point_status` - Get the status of a charge point.
|
|
263
248
|
|
|
264
249
|
```python
|
|
265
|
-
def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
250
|
+
async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
|
|
266
251
|
```
|
|
267
252
|
|
|
268
253
|
##### Arguments
|
|
@@ -295,7 +280,7 @@ A dictionary with the chargepoint status:
|
|
|
295
280
|
#### `get_contracts` - Get your contracts.
|
|
296
281
|
|
|
297
282
|
```python
|
|
298
|
-
def get_contracts(self) -> list[dict[str, str]]
|
|
283
|
+
async def get_contracts(self) -> list[dict[str, str]]
|
|
299
284
|
```
|
|
300
285
|
|
|
301
286
|
##### Returns
|
|
@@ -315,7 +300,7 @@ A list of dictionaries, each representing a contract:
|
|
|
315
300
|
#### `get_grids` - Get your grid connections.
|
|
316
301
|
|
|
317
302
|
```python
|
|
318
|
-
def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
303
|
+
async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
|
|
319
304
|
```
|
|
320
305
|
|
|
321
306
|
##### Returns
|
|
@@ -334,7 +319,7 @@ A list of dictionaries, each representing a grid:
|
|
|
334
319
|
#### `get_transactions` - Get a list of transactions.
|
|
335
320
|
|
|
336
321
|
```python
|
|
337
|
-
def get_transactions(
|
|
322
|
+
async def get_transactions(
|
|
338
323
|
self, evse_id: str, newest_first: bool = True, page: int = 1
|
|
339
324
|
) -> dict[str, int | list[dict[str, Any]]]
|
|
340
325
|
```
|
|
@@ -376,7 +361,7 @@ A dictionary like this:
|
|
|
376
361
|
#### `iterate_transactions` - Iterate through your transactions
|
|
377
362
|
|
|
378
363
|
```python
|
|
379
|
-
def iterate_transactions(self, evse_id: str, newest_first: bool = True) ->
|
|
364
|
+
async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
|
|
380
365
|
```
|
|
381
366
|
|
|
382
367
|
##### Arguments
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
asyncio-multisubscriber-queue>=0.4.1
|
|
2
|
-
|
|
2
|
+
httpx>=0.28
|
|
3
3
|
sjcl>=0.2.1
|
|
4
|
-
websockets>=
|
|
4
|
+
websockets>=14.0
|
|
5
5
|
|
|
6
6
|
[dev]
|
|
7
7
|
black==23.3.0
|
|
8
8
|
pre-commit>=3.3.3
|
|
9
|
-
pytest==8.
|
|
10
|
-
pytest-asyncio==
|
|
9
|
+
pytest==8.4.2
|
|
10
|
+
pytest-asyncio==1.2.0
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
from asyncio import get_event_loop_policy, run
|
|
2
1
|
from os import environ
|
|
3
2
|
from typing import AsyncGenerator
|
|
4
3
|
|
|
5
4
|
from pytest import fixture, skip
|
|
6
|
-
from pytest_asyncio import fixture as async_fixture
|
|
7
5
|
|
|
8
6
|
from pybluecurrent import BlueCurrentClient
|
|
9
7
|
|
|
@@ -22,14 +20,6 @@ def client_with_auth() -> BlueCurrentClient | None:
|
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
@fixture(scope="session")
|
|
25
|
-
def event_loop():
|
|
26
|
-
policy = get_event_loop_policy()
|
|
27
|
-
loop = policy.new_event_loop()
|
|
28
|
-
yield loop
|
|
29
|
-
loop.close()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@async_fixture(scope="session")
|
|
33
23
|
async def connected_client() -> AsyncGenerator[BlueCurrentClient, None]:
|
|
34
24
|
try:
|
|
35
25
|
client = BlueCurrentClient(environ["BLUECURRENT_USERNAME"], environ["BLUECURRENT_PASSWORD"])
|
|
@@ -41,13 +31,8 @@ async def connected_client() -> AsyncGenerator[BlueCurrentClient, None]:
|
|
|
41
31
|
|
|
42
32
|
|
|
43
33
|
@fixture(scope="session")
|
|
44
|
-
def evse_id(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
charge_points = await client.get_charge_points()
|
|
48
|
-
return charge_points
|
|
49
|
-
|
|
50
|
-
result: list = run(_get_charge_points(client_with_auth))
|
|
51
|
-
if len(result) == 0:
|
|
34
|
+
async def evse_id(connected_client: BlueCurrentClient) -> str:
|
|
35
|
+
charge_points = await connected_client.get_charge_points()
|
|
36
|
+
if not charge_points:
|
|
52
37
|
skip("No charge points available.")
|
|
53
|
-
return
|
|
38
|
+
return charge_points[0]["evse_id"] # type: ignore
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from datetime import date
|
|
2
|
-
from itertools import islice
|
|
3
2
|
from os import environ
|
|
4
3
|
|
|
5
|
-
from pytest import
|
|
4
|
+
from pytest import mark, raises, skip
|
|
6
5
|
|
|
7
6
|
from pybluecurrent import BlueCurrentClient
|
|
8
7
|
from pybluecurrent.exceptions import AuthenticationFailed, BlueCurrentException
|
|
@@ -17,30 +16,22 @@ class TestHeaders:
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class TestAuthentication:
|
|
20
|
-
@mark.asyncio
|
|
21
19
|
async def test_authenticate(self, client_with_auth: BlueCurrentClient):
|
|
22
20
|
async with client_with_auth:
|
|
23
21
|
assert client_with_auth.token is not None
|
|
24
22
|
|
|
25
|
-
@mark.asyncio
|
|
26
23
|
async def test_authentication_failed(self, client: BlueCurrentClient):
|
|
27
24
|
with raises(AuthenticationFailed):
|
|
28
25
|
async with client:
|
|
29
26
|
pass
|
|
30
27
|
|
|
31
|
-
def test_login(self, client_with_auth: BlueCurrentClient):
|
|
32
|
-
client_with_auth.login()
|
|
33
|
-
assert client_with_auth.token is not None
|
|
34
|
-
|
|
35
28
|
|
|
36
29
|
class TestSocketApi:
|
|
37
|
-
@mark.asyncio
|
|
38
30
|
async def test_get_account(self, connected_client: BlueCurrentClient):
|
|
39
31
|
account = await connected_client.get_account()
|
|
40
32
|
assert "full_name" in account
|
|
41
33
|
assert isinstance(account["first_login_app"], date)
|
|
42
34
|
|
|
43
|
-
@mark.asyncio
|
|
44
35
|
async def test_get_charge_cards(self, connected_client: BlueCurrentClient):
|
|
45
36
|
charge_cards = await connected_client.get_charge_cards()
|
|
46
37
|
if len(charge_cards) == 0:
|
|
@@ -56,7 +47,6 @@ class TestSocketApi:
|
|
|
56
47
|
]
|
|
57
48
|
)
|
|
58
49
|
|
|
59
|
-
@mark.asyncio
|
|
60
50
|
async def test_get_charge_points(self, connected_client: BlueCurrentClient):
|
|
61
51
|
charge_points = await connected_client.get_charge_points()
|
|
62
52
|
if len(charge_points) == 0:
|
|
@@ -64,41 +54,34 @@ class TestSocketApi:
|
|
|
64
54
|
for charge_point in charge_points:
|
|
65
55
|
assert "evse_id" in charge_point
|
|
66
56
|
|
|
67
|
-
@mark.asyncio
|
|
68
57
|
async def test_get_grid_status(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
69
58
|
status = await connected_client.get_grid_status(evse_id=evse_id)
|
|
70
59
|
assert "grid_actual_p1" in status
|
|
71
60
|
assert "id" in status
|
|
72
61
|
|
|
73
|
-
@mark.asyncio
|
|
74
62
|
async def test_get_charge_point_settings(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
75
63
|
settings = await connected_client.get_charge_point_settings(evse_id=evse_id)
|
|
76
64
|
assert isinstance(settings, dict)
|
|
77
65
|
assert settings["evse_id"] == evse_id
|
|
78
66
|
|
|
79
|
-
@mark.asyncio
|
|
80
67
|
@mark.skip("Does not work")
|
|
81
68
|
async def test_get_sessions(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
82
69
|
sessions = await connected_client.get_sessions(evse_id=evse_id)
|
|
83
70
|
print(sessions)
|
|
84
71
|
|
|
85
|
-
@mark.asyncio
|
|
86
72
|
async def test_get_sustainability_status(self, connected_client: BlueCurrentClient):
|
|
87
73
|
sessions = await connected_client.get_sustainability_status()
|
|
88
74
|
assert set(sessions.keys()) == {"trees", "co2"}
|
|
89
75
|
|
|
90
|
-
@mark.asyncio
|
|
91
76
|
@mark.skip("Does not work.")
|
|
92
77
|
async def test_unlock_connector(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
93
78
|
result = await connected_client.unlock_connector(evse_id=evse_id)
|
|
94
79
|
print(result)
|
|
95
80
|
|
|
96
|
-
@mark.asyncio
|
|
97
81
|
@mark.skip("Do not change chargepoint status.")
|
|
98
82
|
async def test_soft_reset(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
99
83
|
_ = await connected_client.soft_reset(evse_id=evse_id)
|
|
100
84
|
|
|
101
|
-
@mark.asyncio
|
|
102
85
|
@mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
|
|
103
86
|
async def test_set_plug_and_charge_card(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
104
87
|
async def _get_plug_and_charge_card_uid() -> str | None:
|
|
@@ -115,7 +98,7 @@ class TestSocketApi:
|
|
|
115
98
|
charge_cards = await connected_client.get_charge_cards()
|
|
116
99
|
if len(charge_cards) == 0:
|
|
117
100
|
skip(reason="No charge cards.")
|
|
118
|
-
uids: list[str | None] = [charge_card["uid"] for charge_card in charge_cards] + [
|
|
101
|
+
uids: list[str | None] = [charge_card["uid"] for charge_card in charge_cards] + ["BCU_HOME_USE"] # type: ignore
|
|
119
102
|
# Set each card as plug_and_charge_card
|
|
120
103
|
for uid in uids:
|
|
121
104
|
if uid != before_card:
|
|
@@ -125,7 +108,6 @@ class TestSocketApi:
|
|
|
125
108
|
await connected_client.set_plug_and_charge_charge_card(evse_id=evse_id, uid=before_card)
|
|
126
109
|
assert await _get_plug_and_charge_card_uid() == before_card
|
|
127
110
|
|
|
128
|
-
@mark.asyncio
|
|
129
111
|
@mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
|
|
130
112
|
async def test_set_invalid_plug_and_charge_card(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
131
113
|
settings = await connected_client.get_charge_point_settings(evse_id=evse_id)
|
|
@@ -133,18 +115,16 @@ class TestSocketApi:
|
|
|
133
115
|
await connected_client.set_plug_and_charge_charge_card(evse_id=evse_id, uid="INVALID_CARD")
|
|
134
116
|
assert await connected_client.get_charge_point_settings(evse_id=evse_id) == settings
|
|
135
117
|
|
|
136
|
-
@mark.asyncio
|
|
137
118
|
@mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
|
|
138
119
|
async def test_set_status(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
139
|
-
before_status = connected_client.get_charge_point_status(evse_id=evse_id)
|
|
120
|
+
before_status = await connected_client.get_charge_point_status(evse_id=evse_id)
|
|
140
121
|
if before_status["activity"] != "available":
|
|
141
122
|
skip(reason="Only perform this test if the charge point is available.")
|
|
142
123
|
await connected_client.set_status(evse_id=evse_id, enabled=False)
|
|
143
|
-
assert connected_client.get_charge_point_status(evse_id=evse_id)["activity"] == "unavailable"
|
|
124
|
+
assert (await connected_client.get_charge_point_status(evse_id=evse_id))["activity"] == "unavailable"
|
|
144
125
|
await connected_client.set_status(evse_id=evse_id, enabled=True)
|
|
145
|
-
assert connected_client.get_charge_point_status(evse_id=evse_id)["activity"] == "available"
|
|
126
|
+
assert (await connected_client.get_charge_point_status(evse_id=evse_id))["activity"] == "available"
|
|
146
127
|
|
|
147
|
-
@mark.asyncio
|
|
148
128
|
async def test_error(self, connected_client: BlueCurrentClient):
|
|
149
129
|
with raises(BlueCurrentException) as e:
|
|
150
130
|
await connected_client.set_status("BCU123456", False)
|
|
@@ -152,33 +132,36 @@ class TestSocketApi:
|
|
|
152
132
|
|
|
153
133
|
|
|
154
134
|
class TestRestApi:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
client_with_auth.login()
|
|
158
|
-
return client_with_auth
|
|
159
|
-
|
|
160
|
-
def test_get_contracts(self, authenticated_client: BlueCurrentClient):
|
|
161
|
-
contracts = authenticated_client.get_contracts()
|
|
135
|
+
async def test_get_contracts(self, connected_client: BlueCurrentClient):
|
|
136
|
+
contracts = await connected_client.get_contracts()
|
|
162
137
|
assert len(contracts) > 0
|
|
163
138
|
assert "contract_id" in contracts[0]
|
|
164
139
|
|
|
165
|
-
def test_get_charge_point_status(self,
|
|
166
|
-
status =
|
|
140
|
+
async def test_get_charge_point_status(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
141
|
+
status = await connected_client.get_charge_point_status(evse_id)
|
|
167
142
|
assert status["evse_id"] == evse_id
|
|
168
143
|
assert "activity" in status
|
|
169
144
|
|
|
170
|
-
def test_get_grids(self,
|
|
171
|
-
grids =
|
|
145
|
+
async def test_get_grids(self, connected_client: BlueCurrentClient):
|
|
146
|
+
grids = await connected_client.get_grids()
|
|
172
147
|
assert len(grids) > 0
|
|
173
148
|
assert "id" in grids[0]
|
|
174
149
|
|
|
175
|
-
def test_get_transactions(self,
|
|
176
|
-
transactions =
|
|
150
|
+
async def test_get_transactions(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
151
|
+
transactions = await connected_client.get_transactions(evse_id)
|
|
177
152
|
assert "transactions" in transactions
|
|
178
153
|
|
|
179
|
-
def test_iterate_transactions(self,
|
|
180
|
-
transactions =
|
|
181
|
-
# If there are less than three pages, there might be
|
|
154
|
+
async def test_iterate_transactions(self, connected_client: BlueCurrentClient, evse_id: str):
|
|
155
|
+
transactions = await connected_client.get_transactions(evse_id)
|
|
156
|
+
# If there are less than three pages, there might be fewer than 30.
|
|
182
157
|
if transactions["total_pages"] < 3: # type: ignore
|
|
183
158
|
skip("Not enough transactions.")
|
|
184
|
-
|
|
159
|
+
n_transactions = 0
|
|
160
|
+
# Verify pagination works correctly - we get 30 unique transactions from multiple pages.
|
|
161
|
+
unique_transactions = set()
|
|
162
|
+
async for transaction in connected_client.iterate_transactions(evse_id):
|
|
163
|
+
n_transactions += 1
|
|
164
|
+
unique_transactions.add(transaction["transaction_id"])
|
|
165
|
+
if n_transactions >= 30:
|
|
166
|
+
break
|
|
167
|
+
assert len(unique_transactions) == 30
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools_scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
TYPE_CHECKING = False
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple, Union
|
|
6
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
-
else:
|
|
8
|
-
VERSION_TUPLE = object
|
|
9
|
-
|
|
10
|
-
version: str
|
|
11
|
-
__version__: str
|
|
12
|
-
__version_tuple__: VERSION_TUPLE
|
|
13
|
-
version_tuple: VERSION_TUPLE
|
|
14
|
-
|
|
15
|
-
__version__ = version = '0.0.3'
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 0, 3)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|