pybluecurrent 0.2.0__tar.gz → 0.3.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.
Files changed (56) hide show
  1. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/.github/workflows/publish.yaml +4 -4
  2. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/.github/workflows/test.yaml +4 -4
  3. pybluecurrent-0.3.0/CHANGELOG.md +66 -0
  4. pybluecurrent-0.3.0/PKG-INFO +445 -0
  5. pybluecurrent-0.3.0/README.md +417 -0
  6. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/pyproject.toml +5 -1
  7. pybluecurrent-0.3.0/src/pybluecurrent/__init__.py +4 -0
  8. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent/_version.py +3 -3
  9. pybluecurrent-0.3.0/src/pybluecurrent/cli/__init__.py +19 -0
  10. pybluecurrent-0.3.0/src/pybluecurrent/cli/transactions.py +128 -0
  11. pybluecurrent-0.3.0/src/pybluecurrent/client.py +1070 -0
  12. pybluecurrent-0.3.0/src/pybluecurrent/enums.py +31 -0
  13. pybluecurrent-0.3.0/src/pybluecurrent/exceptions.py +31 -0
  14. pybluecurrent-0.3.0/src/pybluecurrent/models.py +282 -0
  15. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent/utilities.py +31 -1
  16. pybluecurrent-0.3.0/src/pybluecurrent.egg-info/PKG-INFO +445 -0
  17. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent.egg-info/SOURCES.txt +11 -1
  18. pybluecurrent-0.3.0/src/pybluecurrent.egg-info/entry_points.txt +2 -0
  19. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent.egg-info/requires.txt +2 -1
  20. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent.egg-info/scm_file_list.json +23 -14
  21. pybluecurrent-0.3.0/src/pybluecurrent.egg-info/scm_version.json +8 -0
  22. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/conftest.py +19 -3
  23. pybluecurrent-0.3.0/tests/fake_rest.py +54 -0
  24. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fake_socket.py +33 -0
  25. pybluecurrent-0.3.0/tests/fixtures/charge_point_settings.json +92 -0
  26. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/charge_points.json +9 -2
  27. pybluecurrent-0.3.0/tests/fixtures/transactions.json +60 -0
  28. pybluecurrent-0.3.0/tests/models_check.py +66 -0
  29. pybluecurrent-0.3.0/tests/test_cli.py +121 -0
  30. pybluecurrent-0.3.0/tests/test_client.py +356 -0
  31. pybluecurrent-0.3.0/tests/test_enums.py +32 -0
  32. pybluecurrent-0.3.0/tests/test_offline.py +834 -0
  33. pybluecurrent-0.2.0/CHANGELOG.md +0 -40
  34. pybluecurrent-0.2.0/PKG-INFO +0 -417
  35. pybluecurrent-0.2.0/README.md +0 -390
  36. pybluecurrent-0.2.0/src/pybluecurrent/__init__.py +0 -3
  37. pybluecurrent-0.2.0/src/pybluecurrent/client.py +0 -574
  38. pybluecurrent-0.2.0/src/pybluecurrent/exceptions.py +0 -6
  39. pybluecurrent-0.2.0/src/pybluecurrent.egg-info/PKG-INFO +0 -417
  40. pybluecurrent-0.2.0/src/pybluecurrent.egg-info/scm_version.json +0 -8
  41. pybluecurrent-0.2.0/tests/fixtures/charge_point_settings.json +0 -50
  42. pybluecurrent-0.2.0/tests/test_client.py +0 -181
  43. pybluecurrent-0.2.0/tests/test_offline.py +0 -225
  44. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/.gitignore +0 -0
  45. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/.pre-commit-config.yaml +0 -0
  46. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/LICENSE +0 -0
  47. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/setup.cfg +0 -0
  48. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent/py.typed +0 -0
  49. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent.egg-info/dependency_links.txt +0 -0
  50. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/src/pybluecurrent.egg-info/top_level.txt +0 -0
  51. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/account.json +0 -0
  52. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/charge_cards.json +0 -0
  53. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/error_forbidden.json +0 -0
  54. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/grid_status.json +0 -0
  55. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/fixtures/sustainability_status.json +0 -0
  56. {pybluecurrent-0.2.0 → pybluecurrent-0.3.0}/tests/test_utilities.py +0 -0
@@ -13,10 +13,10 @@ jobs:
13
13
  timeout-minutes: 10
14
14
 
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v5
17
17
 
18
18
  - name: Set up Python
19
- uses: actions/setup-python@v5
19
+ uses: actions/setup-python@v6
20
20
  with:
21
21
  python-version: "3.x"
22
22
 
@@ -36,10 +36,10 @@ jobs:
36
36
  contents: read
37
37
 
38
38
  steps:
39
- - uses: actions/checkout@v4
39
+ - uses: actions/checkout@v5
40
40
 
41
41
  - name: Set up Python
42
- uses: actions/setup-python@v5
42
+ uses: actions/setup-python@v6
43
43
  with:
44
44
  python-version: "3.x"
45
45
 
@@ -13,10 +13,10 @@ jobs:
13
13
  timeout-minutes: 10
14
14
 
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v5
17
17
 
18
18
  - name: Set up Python
19
- uses: actions/setup-python@v5
19
+ uses: actions/setup-python@v6
20
20
  with:
21
21
  python-version: "3.x"
22
22
 
@@ -36,10 +36,10 @@ jobs:
36
36
  python-version: ["3.10", "3.11", "3.12", "3.13"]
37
37
 
38
38
  steps:
39
- - uses: actions/checkout@v4
39
+ - uses: actions/checkout@v5
40
40
 
41
41
  - name: Set up Python
42
- uses: actions/setup-python@v5
42
+ uses: actions/setup-python@v6
43
43
  with:
44
44
  python-version: ${{ matrix.python-version }}
45
45
 
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ This project is pre-1.0: breaking changes may land in minor releases.
7
+
8
+ ## [0.3.0] - 2026-07-25
9
+
10
+ ### Added
11
+
12
+ - A `pybluecurrent` command-line interface. `pybluecurrent transactions` exports your charging transactions to CSV, JSON or JSON Lines, to stdout or to `--output`, optionally limited to the last `--days` or to specific `--evse-id` charge points. Credentials come from `BLUECURRENT_USERNAME`/`BLUECURRENT_PASSWORD` (or `BLUECURRENT_API_TOKEN`) or the matching options.
13
+ - `start_date` and `end_date` arguments on `get_transactions()` and `iterate_transactions()`, so the backend filters by date instead of you fetching everything and discarding the old ones.
14
+ - Typed response models in `pybluecurrent.models`. Each getter now declares a `TypedDict` return type (`Account`, `ChargePoint`, `ChargePointSettings`, `Transaction`, …) instead of a loose `dict`. Responses stay plain dictionaries, so existing `response["key"]` access is unchanged; you now get autocomplete and type checking, and `get_transactions` is fully typed rather than `dict[str, Any]`.
15
+ - Automatic reconnection. A long-lived client now keeps itself connected: when the websocket drops it reconnects in the background with exponential backoff, reusing the cached session token. Calls made while reconnecting block until the connection is restored (bounded by `reconnect_wait_timeout`) rather than failing; calls already in flight when the drop happens still raise `ConnectionLost`. Tunable via the `auto_reconnect` (default on) and `reconnect_*` class attributes. Reconnection gives up after too many attempts, or immediately on rejected credentials, rather than retrying forever.
16
+ - Delayed (time-window) smart charging: `set_delayed_charging()` to switch the profile on or off, and `set_delayed_charging_schedule()` to set the window and the days it applies to.
17
+ - Price-based (dynamic-tariff) smart charging: `set_price_based_charging()` to switch the profile on or off, and `set_price_based_charging_settings()` to set the expected departure time and energy.
18
+ - `boost()` to charge now, overriding whichever smart charging profile (delayed or price-based) is currently active.
19
+ - `Weekday`, an `IntEnum` numbered like `date.isoweekday()`. Days can also be given as plain numbers or as names (`"monday"`, `"mo"`), so importing it is optional.
20
+ - `ConnectionLost` and `RequestTimeout` exceptions. Both derive from `BlueCurrentException`; `RequestTimeout` also subclasses the builtin `TimeoutError` (so existing `except TimeoutError` keeps working), and `AuthenticationFailed` now derives from `BlueCurrentException` as well as `ValueError`.
21
+
22
+ ### Changed
23
+
24
+ - `get_charge_points` and `get_charge_point_settings` now report the delayed- and price-based-charging schedules under the same names used to set them: `days` (previously `selected_days`) and `expected_departure_time` (previously `expected_leave_time`). This is a breaking change for code that read the old keys.
25
+ - The delayed- and price-based-charging schedule times (`start_time`, `end_time`, `expected_departure_time`) are now returned as `datetime.time` objects instead of `"HH:MM"` strings.
26
+ - Websocket connection failures are now surfaced instead of hanging. When the handler stops (the connection drops, or an unexpected error), pending and subsequent calls raise `ConnectionLost` immediately rather than blocking until they time out. A single malformed (non-JSON) frame is now logged and skipped instead of silently killing the connection.
27
+ - `_receive` now enforces a single per-call deadline (a stream of unrelated frames can no longer postpone it indefinitely) and raises `RequestTimeout` rather than a bare `TimeoutError`.
28
+ - `set_status()`, `unlock_connector()` and `soft_reset()` now raise `BlueCurrentException` when the command fails (a `STATUS_` frame with `success: false`), instead of `set_status()` returning silently or the others handing back the failed frame. On success all three now return `None` (previously `unlock_connector()` and `soft_reset()` returned the raw status frame), so the command methods are consistent. Their wait for that verdict is also longer (a new `command_timeout`, default 60s), so the backend's own ~30s answer is no longer cut off just before it arrives.
29
+ - Logging now uses a module logger (`pybluecurrent.client`) instead of a fixed `BlueCurrentClient` name, and the reconnect failure paths (attempts, back-off, wait-timeouts, and permanent give-up) are logged.
30
+
31
+ ### Fixed
32
+
33
+ - The session token is no longer written to debug logs. Received frames were logged verbatim at debug level, which included the token carried by the login-response frames; those fields are now masked.
34
+ - The client no longer leaks the websocket, handler task, or HTTP client when connecting fails partway (for example on a rejected login), and teardown now awaits the handler and closes the HTTP client even if closing the websocket errors.
35
+
36
+ ## [0.2.0] - 2026-07-11
37
+
38
+ ### Added
39
+
40
+ - API-token authentication: construct the client with `BlueCurrentClient(api_token=...)` instead of a username and password.
41
+ - `get_api_token()` and `generate_api_token()` to fetch or rotate your account's API token (home automation key).
42
+
43
+ ### Changed
44
+
45
+ - REST calls now use `api.bluecurrent.nl` instead of the legacy `bo.bluecurrent.nl` backoffice host (same `bc_api` v2.0 API and response shapes).
46
+ - REST requests now use a 30-second timeout instead of httpx's 5-second default, so occasional slow backend responses no longer raise `httpx.ReadTimeout`; override with the `http_timeout` attribute.
47
+ - Internal: added an offline websocket test harness (fake socket + recorded fixtures) so the auth/`_send`/`_receive`/`_handler` logic runs in CI without live credentials.
48
+
49
+ ### Fixed
50
+
51
+ - Concurrent websocket calls are now safe: calls awaiting the same response type are serialized so overlapping same-type calls can no longer receive each other's replies (different-type calls still run concurrently). Errors the backend tags with a request id are routed to the originating call rather than failing every in-flight call.
52
+
53
+ ## [0.1.1] - 2026-07-04
54
+
55
+ ### Fixed
56
+
57
+ - `get_account` no longer raises a `ValueError` on BlueCurrent's current date format; `first_login_app` now parses both the legacy (`01-JAN-20`) and ISO (`2020-01-15T13:33:52`) formats.
58
+
59
+ ### Changed
60
+
61
+ - `get_account` returns `first_login_app` as a `datetime` (previously a `date`).
62
+ - Internal: switched tooling to Ruff and ty, added a Python 3.10–3.13 CI matrix, and moved to PyPI trusted publishing (OIDC).
63
+
64
+ [0.3.0]: https://github.com/rogiervandergeer/pybluecurrent/compare/0.2.0...0.3.0
65
+ [0.2.0]: https://github.com/rogiervandergeer/pybluecurrent/compare/0.1.1...0.2.0
66
+ [0.1.1]: https://github.com/rogiervandergeer/pybluecurrent/compare/0.1.0...0.1.1
@@ -0,0 +1,445 @@
1
+ Metadata-Version: 2.4
2
+ Name: pybluecurrent
3
+ Version: 0.3.0
4
+ Summary: Python client for BlueCurrent charge points.
5
+ Author-email: Rogier van der Geer <rogier@vander-geer.nl>
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/rogiervandergeer/pybluecurrent
8
+ Keywords: api,blue current,electric vehicle,ev
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: asyncio-multisubscriber-queue>=0.4.1
18
+ Requires-Dist: httpx>=0.28
19
+ Requires-Dist: sjcl>=0.2.1
20
+ Requires-Dist: typer>=0.12
21
+ Requires-Dist: websockets>=14.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pre-commit>=3.3.3; extra == "dev"
24
+ Requires-Dist: pytest==8.4.2; extra == "dev"
25
+ Requires-Dist: pytest-asyncio==1.2.0; extra == "dev"
26
+ Requires-Dist: typeguard>=4.0; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # pybluecurrent
30
+
31
+ Python client for [BlueCurrent](https://www.bluecurrent.nl) charge points.
32
+
33
+ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/rogiervandergeer/pybluecurrent/test.yaml)
34
+ ![PyPI](https://img.shields.io/pypi/v/pybluecurrent)
35
+ ![PyPI - License](https://img.shields.io/pypi/l/pybluecurrent)
36
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/pybluecurrent)
37
+
38
+ `pybluecurrent` is an **unofficial, third-party async client** — it is not affiliated with
39
+ BlueCurrent.
40
+
41
+ Compared to BlueCurrent's official [`bluecurrent-api`](https://github.com/bluecurrent/HomeAssistantAPI):
42
+
43
+ - **Per-call `async`/`await`** — each method awaits its own response, rather than a single callback
44
+ receiver that routes every server message.
45
+ - **Typed responses** — getters return `TypedDict`-annotated dictionaries; the official client
46
+ hands back untyped dicts.
47
+ - **Instance-scoped state** — no process-global mutable state.
48
+ - **Username/password *or* API token** — the official client is API-token only.
49
+
50
+ ## Usage
51
+
52
+ Using the client is as simple as:
53
+ ```python
54
+ from pybluecurrent import BlueCurrentClient
55
+
56
+ client = BlueCurrentClient("your_username", "your_secret_password")
57
+
58
+ async with client:
59
+ charge_points = await client.get_charge_points()
60
+ transactions = await client.get_transactions(charge_points[0]["evse_id"])
61
+ ```
62
+
63
+ ### Connection
64
+
65
+ The client can only be used while its websocket is connected. For example:
66
+ ```python
67
+ client = BlueCurrentClient("your_username", "your_secret_password")
68
+ async with client:
69
+ result = await client.get_account()
70
+ ```
71
+ Entering the async context automatically logs in.
72
+
73
+ Instead of a username and password, you can authenticate with an API token:
74
+ ```python
75
+ client = BlueCurrentClient(api_token="your_api_token")
76
+ ```
77
+ Retrieve or rotate the token with [`get_api_token`](#get_api_token) and
78
+ [`generate_api_token`](#generate_api_token), or from the [BlueCurrent website](https://my.bluecurrent.nl).
79
+
80
+ ## Command line
81
+
82
+ Installing the package also installs a `pybluecurrent` command, which exports your transactions:
83
+
84
+ ```shell
85
+ export BLUECURRENT_USERNAME="your_username"
86
+ export BLUECURRENT_PASSWORD="your_secret_password"
87
+
88
+ pybluecurrent transactions --format csv -o transactions.csv
89
+ pybluecurrent transactions --format json --days 30
90
+ pybluecurrent transactions --format jsonl --evse-id BCU123456
91
+ ```
92
+
93
+ Credentials come from `BLUECURRENT_USERNAME` / `BLUECURRENT_PASSWORD` (or `BLUECURRENT_API_TOKEN`),
94
+ or from the matching `--username` / `--password` / `--api-token` options.
95
+
96
+ **Options**
97
+ - `--format`: `csv` (default), `json` (one array) or `jsonl` (one object per line).
98
+ - `--evse-id`: Charge point to export, repeatable. Defaults to all of your charge points.
99
+ - `--days`: Only export the last N days. Defaults to your whole history.
100
+ - `-o`, `--output`: Write to a file instead of stdout.
101
+ - `--newest-first` / `--oldest-first`: Output order, newest first by default.
102
+
103
+ ## Methods
104
+
105
+ Every method is a coroutine on `BlueCurrentClient`; call them inside the async context (see
106
+ [Connection](#connection)). Charge points are addressed by their `evse_id`.
107
+
108
+ - **Account & authentication** — [`get_account`](#get_account), [`get_api_token`](#get_api_token), [`generate_api_token`](#generate_api_token), [`get_contracts`](#get_contracts)
109
+ - **Charge points & cards** — [`get_charge_points`](#get_charge_points), [`get_charge_point_settings`](#get_charge_point_settings), [`get_charge_point_status`](#get_charge_point_status), [`get_charge_cards`](#get_charge_cards)
110
+ - **Grid & sustainability** — [`get_grid_status`](#get_grid_status), [`get_grids`](#get_grids), [`get_sustainability_status`](#get_sustainability_status)
111
+ - **Settings & control** — [`set_plug_and_charge_charge_card`](#set_plug_and_charge_charge_card), [`set_status`](#set_status), [`soft_reset`](#soft_reset)
112
+ - **Smart charging** — [`set_delayed_charging`](#set_delayed_charging), [`set_delayed_charging_schedule`](#set_delayed_charging_schedule), [`set_price_based_charging`](#set_price_based_charging), [`set_price_based_charging_settings`](#set_price_based_charging_settings), [`boost`](#boost)
113
+ - **Transactions** — [`get_transactions`](#get_transactions), [`iterate_transactions`](#iterate_transactions)
114
+
115
+ ### Response models
116
+
117
+ The getters return plain dictionaries annotated with `TypedDict`s from
118
+ [`pybluecurrent.models`](https://github.com/rogiervandergeer/pybluecurrent/blob/main/src/pybluecurrent/models.py).
119
+ Access is unchanged — `response["key"]`, `.get()`, `**response` and `json.dumps` all keep working —
120
+ and an unexpected field the backend adds simply rides along; the types just add autocomplete and
121
+ static checking:
122
+
123
+ ```python
124
+ from pybluecurrent.models import ChargePoint, Transaction
125
+ ```
126
+
127
+ **The model definitions are the field-level reference** — each field, its type, and any parsing
128
+ notes live there. The response types are `Account`, `ChargeCard`, `ChargePoint`,
129
+ `ChargePointSettings`, `ChargePointStatus`, `GridStatus`, `Grid`, `SustainabilityStatus`,
130
+ `Contract`, `TransactionsPage` and `Transaction`, built from the nested shapes `Tariff`,
131
+ `Location`, `Address`, `DelayedCharging`, `PriceBasedCharging`, `CardRef`, `BoolSetting` and
132
+ `IntSetting`. Dates and times are parsed for you: `date`/`datetime` fields are Python objects, and
133
+ schedule times (`start_time`, `end_time`, `expected_departure_time`) are `datetime.time`.
134
+
135
+ ### Account & authentication
136
+
137
+ #### get_account
138
+
139
+ ```python
140
+ async def get_account(self) -> Account
141
+ ```
142
+
143
+ Returns your account information as an [`Account`](#response-models).
144
+
145
+ #### get_api_token
146
+
147
+ ```python
148
+ async def get_api_token(self) -> str
149
+ ```
150
+
151
+ Returns the API token (home automation key) for your account. It can be used to authenticate
152
+ instead of a username and password, by constructing the client with `BlueCurrentClient(api_token=...)`.
153
+
154
+ #### generate_api_token
155
+
156
+ ```python
157
+ async def generate_api_token(self) -> str
158
+ ```
159
+
160
+ Generates a new API token and returns it. **Warning:** this rotates the token — any previously
161
+ issued token is invalidated, which will break anything still using the old one.
162
+
163
+ #### get_contracts
164
+
165
+ ```python
166
+ async def get_contracts(self) -> list[Contract]
167
+ ```
168
+
169
+ Returns your contracts, each a [`Contract`](#response-models).
170
+
171
+ ### Charge points & cards
172
+
173
+ #### get_charge_points
174
+
175
+ ```python
176
+ async def get_charge_points(self) -> list[ChargePoint]
177
+ ```
178
+
179
+ Returns your charge points, each a [`ChargePoint`](#response-models). A disabled smart-charging
180
+ profile is still present as its `{value, permission}` wrapper; its schedule/settings fields appear
181
+ only while the profile is enabled.
182
+
183
+ #### get_charge_point_settings
184
+
185
+ ```python
186
+ async def get_charge_point_settings(self, evse_id: str) -> ChargePointSettings
187
+ ```
188
+
189
+ Returns the settings of a charge point as a [`ChargePointSettings`](#response-models). All of this
190
+ is already included in the response of [`get_charge_points`](#get_charge_points).
191
+
192
+ **Arguments**
193
+ - `evse_id`: The ID of the charge point.
194
+
195
+ #### get_charge_point_status
196
+
197
+ ```python
198
+ async def get_charge_point_status(self, evse_id: str) -> ChargePointStatus
199
+ ```
200
+
201
+ Returns the live status of a charge point as a [`ChargePointStatus`](#response-models).
202
+
203
+ **Arguments**
204
+ - `evse_id`: The ID of the charge point.
205
+
206
+ #### get_charge_cards
207
+
208
+ ```python
209
+ async def get_charge_cards(self) -> list[ChargeCard]
210
+ ```
211
+
212
+ Returns your charge cards, each a [`ChargeCard`](#response-models).
213
+
214
+ ### Grid & sustainability
215
+
216
+ #### get_grid_status
217
+
218
+ ```python
219
+ async def get_grid_status(self, evse_id: str) -> GridStatus
220
+ ```
221
+
222
+ Returns the grid status associated with a charge point (currents in amps) as a
223
+ [`GridStatus`](#response-models).
224
+
225
+ **Arguments**
226
+ - `evse_id`: The ID of the charge point.
227
+
228
+ #### get_grids
229
+
230
+ ```python
231
+ async def get_grids(self) -> list[Grid]
232
+ ```
233
+
234
+ Returns your grid connections, each a [`Grid`](#response-models).
235
+
236
+ #### get_sustainability_status
237
+
238
+ ```python
239
+ async def get_sustainability_status(self) -> SustainabilityStatus
240
+ ```
241
+
242
+ Returns sustainability statistics for all your charge points as a
243
+ [`SustainabilityStatus`](#response-models) — `{"trees": ..., "co2": ...}`.
244
+
245
+ ### Settings & control
246
+
247
+ #### set_plug_and_charge_charge_card
248
+
249
+ ```python
250
+ async def set_plug_and_charge_charge_card(self, evse_id: str, uid: str | None = None) -> None
251
+ ```
252
+
253
+ Sets the plug-and-charge card for the charge point. `uid` must be the `uid` of one of your
254
+ [charge cards](#get_charge_cards), or `None` to charge without a card. Raises `BlueCurrentException`
255
+ if the command fails.
256
+
257
+ **Arguments**
258
+ - `evse_id`: The ID of the charge point.
259
+ - `uid`: A charge card UID, or `None` (the default) to use no charge card.
260
+
261
+ #### set_status
262
+
263
+ ```python
264
+ async def set_status(self, evse_id: str, enabled: bool) -> None
265
+ ```
266
+
267
+ Enables or disables a charge point. Raises `BlueCurrentException` if the command fails.
268
+
269
+ **Arguments**
270
+ - `evse_id`: The ID of the charge point.
271
+ - `enabled`: Boolean that indicates the desired status.
272
+
273
+ #### soft_reset
274
+
275
+ ```python
276
+ async def soft_reset(self, evse_id: str) -> None
277
+ ```
278
+
279
+ Soft-resets a charge point. Raises `BlueCurrentException` if the command fails.
280
+
281
+ **Arguments**
282
+ - `evse_id`: The ID of the charge point.
283
+
284
+ ### Smart charging
285
+
286
+ #### set_delayed_charging
287
+
288
+ ```python
289
+ async def set_delayed_charging(self, evse_id: str, enabled: bool) -> None
290
+ ```
291
+
292
+ Enables or disables delayed charging. While enabled, the charge point only charges within the window
293
+ configured with [`set_delayed_charging_schedule`](#set_delayed_charging_schedule), and delays
294
+ charging outside of it. A charge point has at most one smart-charging profile active, so enabling
295
+ this disables any other profile.
296
+
297
+ **Arguments**
298
+ - `evse_id`: The ID of the charge point.
299
+ - `enabled`: Whether delayed charging should be enabled.
300
+
301
+ #### set_delayed_charging_schedule
302
+
303
+ ```python
304
+ async def set_delayed_charging_schedule(
305
+ self,
306
+ evse_id: str,
307
+ start_time: time | str,
308
+ end_time: time | str,
309
+ days: Iterable[Weekday | int | str],
310
+ ) -> None
311
+ ```
312
+
313
+ Sets the window in which the charge point may charge on the selected days. The window may span
314
+ midnight. It is applied only while delayed charging is enabled with
315
+ [`set_delayed_charging`](#set_delayed_charging).
316
+
317
+ ```python
318
+ from datetime import time
319
+ from pybluecurrent import Weekday
320
+
321
+ await client.set_delayed_charging_schedule(
322
+ "BCU123456", start_time=time(23, 0), end_time=time(7, 0), days=[Weekday.MONDAY, "tu", 3]
323
+ )
324
+ ```
325
+
326
+ **Arguments**
327
+ - `evse_id`: The ID of the charge point.
328
+ - `start_time`: The time at which charging may start, as a `time` or a `"HH:MM"` string.
329
+ - `end_time`: The time at which charging must stop, as a `time` or a `"HH:MM"` string.
330
+ - `days`: The days on which the schedule applies. Each day may be a `pybluecurrent.Weekday`, an
331
+ isoweekday number (1 for Monday through 7 for Sunday), or a name such as `"monday"` or `"mo"`.
332
+
333
+ The schedule is read back from the `delayed_charging` key of
334
+ [`get_charge_point_settings`](#get_charge_point_settings).
335
+
336
+ #### set_price_based_charging
337
+
338
+ ```python
339
+ async def set_price_based_charging(self, evse_id: str, enabled: bool) -> None
340
+ ```
341
+
342
+ Enables or disables price-based charging. While enabled, the charge point charges during the
343
+ cheapest hours before the expected departure time, as configured with
344
+ [`set_price_based_charging_settings`](#set_price_based_charging_settings). A charge point has at most
345
+ one smart-charging profile active, so enabling this disables any other profile.
346
+
347
+ **Arguments**
348
+ - `evse_id`: The ID of the charge point.
349
+ - `enabled`: Whether price-based charging should be enabled.
350
+
351
+ #### set_price_based_charging_settings
352
+
353
+ ```python
354
+ async def set_price_based_charging_settings(
355
+ self,
356
+ evse_id: str,
357
+ expected_departure_time: time | str,
358
+ expected_kwh: float,
359
+ minimum_kwh: float,
360
+ ) -> None
361
+ ```
362
+
363
+ Configures how much energy to charge before departure. Applied only while price-based charging is
364
+ enabled with [`set_price_based_charging`](#set_price_based_charging).
365
+
366
+ **Arguments**
367
+ - `evse_id`: The ID of the charge point.
368
+ - `expected_departure_time`: The time the vehicle is expected to leave, as a `time` or a `"HH:MM"`
369
+ string.
370
+ - `expected_kwh`: The amount of energy, in kWh, expected to be charged before departure.
371
+ - `minimum_kwh`: The amount of energy, in kWh, to charge immediately regardless of price.
372
+
373
+ The settings are read back from the `price_based_charging` key of
374
+ [`get_charge_point_settings`](#get_charge_point_settings).
375
+
376
+ #### boost
377
+
378
+ ```python
379
+ async def boost(self, evse_id: str) -> None
380
+ ```
381
+
382
+ Starts charging immediately, overriding whichever smart-charging profile is currently delaying
383
+ charging — delayed charging or price-based charging — for the ongoing session. The override cannot
384
+ be undone. While it is active, [`get_charge_point_status`](#get_charge_point_status) reports
385
+ `"boosting": True`. Raises `ValueError` if no smart-charging profile is active.
386
+
387
+ **Arguments**
388
+ - `evse_id`: The ID of the charge point.
389
+
390
+ ### Transactions
391
+
392
+ #### get_transactions
393
+
394
+ ```python
395
+ async def get_transactions(
396
+ self,
397
+ evse_id: str,
398
+ newest_first: bool = True,
399
+ page: int = 1,
400
+ start_date: date | None = None,
401
+ end_date: date | None = None,
402
+ ) -> TransactionsPage
403
+ ```
404
+
405
+ Returns a single page of transactions as a [`TransactionsPage`](#response-models); its
406
+ `transactions` key holds a list of [`Transaction`](#response-models).
407
+
408
+ **Arguments**
409
+ - `evse_id`: The ID of the charge point.
410
+ - `newest_first`: If `True`, start with the most recent transaction. Defaults to `True`.
411
+ - `page`: Page number to get. Defaults to `1`.
412
+ - `start_date`: Only return transactions from this date onwards. Omitted by default.
413
+ - `end_date`: Only return transactions up to this date. Omitted by default.
414
+
415
+ #### iterate_transactions
416
+
417
+ ```python
418
+ async def iterate_transactions(
419
+ self,
420
+ evse_id: str,
421
+ newest_first: bool = True,
422
+ start_date: date | None = None,
423
+ end_date: date | None = None,
424
+ ) -> AsyncIterable[Transaction]
425
+ ```
426
+
427
+ Iterates over all your transactions, fetching further pages as needed. Yields
428
+ [`Transaction`](#response-models) dictionaries.
429
+
430
+ **Arguments**
431
+ - `evse_id`: The ID of the charge point.
432
+ - `newest_first`: If `True`, start with the most recent transaction. Defaults to `True`.
433
+ - `start_date`: Only return transactions from this date onwards. Omitted by default.
434
+ - `end_date`: Only return transactions up to this date. Omitted by default.
435
+
436
+ ## Development
437
+
438
+ - **Install** (editable, with dev extras): `uv sync --extra dev` (or `pip install -e ".[dev]"`).
439
+ - **Pre-commit**: the repo ships a `.pre-commit-config.yaml`, but git installs no hooks on clone, so
440
+ it is a one-time manual step — run `uvx pre-commit install`.
441
+ - **Contributions** and feature requests are welcome.
442
+
443
+ ## Changelog
444
+
445
+ See [CHANGELOG.md](https://github.com/rogiervandergeer/pybluecurrent/blob/main/CHANGELOG.md).