python-openevse-http 1.6.0__tar.gz → 1.7.1__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.
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/SKILL.md +19 -4
- python_openevse_http-1.7.1/.agents/skills/openevse-api-guide/references/endpoints_matrix.md +110 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/workflows/publish-to-pypi.yml +1 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/workflows/test.yml +1 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/AGENTS.md +3 -2
- {python_openevse_http-1.6.0/python_openevse_http.egg-info → python_openevse_http-1.7.1}/PKG-INFO +34 -7
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/README.md +33 -6
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/client.py +91 -6
- python_openevse_http-1.7.1/openevsehttp/commands.py +33 -0
- python_openevse_http-1.7.1/openevsehttp/commands_base.py +66 -0
- python_openevse_http-1.6.0/openevsehttp/commands.py → python_openevse_http-1.7.1/openevsehttp/commands_core.py +37 -302
- python_openevse_http-1.7.1/openevsehttp/commands_diagnostics.py +229 -0
- python_openevse_http-1.7.1/openevsehttp/commands_firmware.py +260 -0
- python_openevse_http-1.7.1/openevsehttp/commands_schedule.py +147 -0
- python_openevse_http-1.7.1/openevsehttp/commands_security.py +265 -0
- python_openevse_http-1.7.1/openevsehttp/commands_time.py +186 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/const.py +10 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/exceptions.py +13 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/managers.py +6 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/properties.py +95 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/sensors.py +6 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/pyproject.toml +1 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1/python_openevse_http.egg-info}/PKG-INFO +34 -7
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/SOURCES.txt +8 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/scm_file_list.json +8 -0
- python_openevse_http-1.7.1/python_openevse_http.egg-info/scm_version.json +8 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/requirements_lint.txt +2 -2
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/requirements_test.txt +2 -2
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/conftest.py +6 -2
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_client.py +209 -10
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_commands.py +1359 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_managers.py +4 -4
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_mixins.py +31 -1
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_properties.py +72 -0
- python_openevse_http-1.6.0/python_openevse_http.egg-info/scm_version.json +0 -8
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-dev-workflow/SKILL.md +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/dependabot.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/pull_request_template.md +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/release-drafter.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/workflows/autolabeler.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/workflows/links.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.github/workflows/release-drafter.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.gitignore +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.pre-commit-config.yaml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.yamllint +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/EXTERNAL_SESSION.md +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/LICENSE +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/codecov.yml +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/example_external_session.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/__main__.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/py.typed +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/utils.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/requirements.txt +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/setup.cfg +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/setup.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/__init__.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/common.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v2.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v4.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/config.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/status.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-dev.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-extra-version.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-new.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/schedule.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-broken.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-new.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/fixtures/websocket.json +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_external_session.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_main_edge_cases.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_sensors.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_shaper.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tests/test_websocket.py +0 -0
- {python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/tox.ini +0 -0
{python_openevse_http-1.6.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/SKILL.md
RENAMED
|
@@ -19,6 +19,9 @@ The main client `OpenEVSE` combines several mixins:
|
|
|
19
19
|
|
|
20
20
|
## Endpoints & RAPI Commands Reference
|
|
21
21
|
|
|
22
|
+
For a comprehensive matrix of all endpoints across firmware generations (v2.x, v3.x, and active v4.x/v5.x), field variations (`/config`, `/status`), and RAPI fallbacks, see:
|
|
23
|
+
- [OpenEVSE Endpoints Matrix](file:///home/firstof9/github/python-openevse-http/.agents/skills/openevse-api-guide/references/endpoints_matrix.md)
|
|
24
|
+
|
|
22
25
|
| Action | HTTP Endpoint (v4+) | RAPI Command (v2/v3) | Method |
|
|
23
26
|
| :--- | :--- | :--- | :--- |
|
|
24
27
|
| Status | `/status` | N/A | GET |
|
|
@@ -29,6 +32,17 @@ The main client `OpenEVSE` combines several mixins:
|
|
|
29
32
|
| Divert Mode | `/divertmode` or `/config` | N/A | POST |
|
|
30
33
|
| Module Restart | `/restart` (`device: gateway\|evse`) | `$FR` (evse restart) | POST |
|
|
31
34
|
| Firmware Update | `/update` | N/A | POST (multipart or JSON URL) |
|
|
35
|
+
| Relay Stuck Recovery | `/relay/recovery` | `$FK` (controller stuck recovery) | POST |
|
|
36
|
+
| Relay Health Reset | `/relay/reset` | `$FH` (controller health reset) | POST |
|
|
37
|
+
| Cable Temperature | `/cabletemp` | `$GN` / `$SN` (cable temp monitor) | GET / POST |
|
|
38
|
+
| Time Settings | `/time` (v4+) / `/settime` (v3) | `$S1` (RTC set) | GET / POST |
|
|
39
|
+
| Event Logs | `/logs` / `/logs/{index}` | N/A | GET |
|
|
40
|
+
| Certificates | `/certificates` (`/root`, `/{id}`) | N/A | GET / POST / DELETE |
|
|
41
|
+
| RFID Tag Pairing | `/rfid/add` | N/A | POST |
|
|
42
|
+
| RFID Users | `/rfid/users` | N/A | GET / POST / DELETE |
|
|
43
|
+
|
|
44
|
+
> [!NOTE]
|
|
45
|
+
> Firmware development for **v2.x (ESP8266)** and **v3.x (ESP32)** has ended. Active development occurs in **`OpenEVSE/openevse_esp32_firmware`** (v4.x/v5.x). Always check `openevse_esp32_firmware` as the primary reference when evaluating new endpoints, features, or behaviors.
|
|
32
46
|
|
|
33
47
|
## Firmware Version Branching
|
|
34
48
|
|
|
@@ -67,19 +81,20 @@ from .exceptions import CommandFailedError, UnknownStateError, UnsupportedFeatur
|
|
|
67
81
|
|
|
68
82
|
When adding, modifying, or debugging endpoints and RAPI commands, cross-reference against the upstream OpenEVSE firmware sources:
|
|
69
83
|
|
|
70
|
-
- **WiFi Gateway Firmware (
|
|
71
|
-
- **
|
|
84
|
+
- **WiFi Gateway Firmware (Current ESP32 v4/v5)**: [`OpenEVSE/openevse_esp32_firmware`](https://github.com/OpenEVSE/openevse_esp32_firmware) (formerly [`OpenEVSE/ESP32_WiFi_V4.x`](https://github.com/OpenEVSE/ESP32_WiFi_V4.x))
|
|
85
|
+
- **WiFi Gateway Firmware (Legacy ESP32 v3.x)**: [`OpenEVSE/ESP32_WiFi_V3.x`](https://github.com/OpenEVSE/ESP32_WiFi_V3.x)
|
|
86
|
+
- **Legacy WiFi Firmware (ESP8266 v2.x)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)
|
|
72
87
|
- **OpenEVSE Controller Firmware (RAPI)**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse)
|
|
73
88
|
|
|
74
89
|
### What to Verify in Firmware Sources:
|
|
75
90
|
1. **Route & Method Handlers**:
|
|
76
|
-
- Check `src/
|
|
91
|
+
- Check `src/web_server.cpp`, `src/web_server_config.cpp`, `src/web_server_*.cpp` (in `openevse_esp32_firmware` / `ESP32_WiFi_V4.x`) or `src/web_server.cpp` (in `ESP32_WiFi_V3.x` / `ESP8266_WiFi_v2.x`) to confirm HTTP methods (`GET`, `POST`, `PATCH`, `DELETE`).
|
|
77
92
|
- Confirm expected query parameters or JSON body fields (e.g. `divertmode=...`, `{"device": "gateway"}`, `{"charge_current": ...}`).
|
|
78
93
|
2. **Response Formats & Statuses**:
|
|
79
94
|
- Verify success and error response payloads (e.g., `{"msg": "done"}`, `{"result": "OK", "msg": "..."}`, or plain string messages like `"Current Shaper state changed"`).
|
|
80
95
|
- Update `SUCCESS_ANSWERS` in `openevsehttp/const.py` if new success indicators are introduced.
|
|
81
96
|
3. **Firmware Version Thresholds**:
|
|
82
|
-
- Check git history or release tags
|
|
97
|
+
- Check git history or release tags across `openevse_esp32_firmware`, `ESP32_WiFi_V3.x`, and `ESP8266_WiFi_v2.x` to determine when a route or feature was introduced, ensuring accurate `_version_check("x.y.z")` values.
|
|
83
98
|
4. **RAPI Command Specifications**:
|
|
84
99
|
- Check `src/rapi.cpp` or OpenEVSE controller docs for valid RAPI commands (e.g., `$SC`, `$FE`, `$FS`, `$FR`, `$ST`) and return formats (`$OK`, `$NK`).
|
|
85
100
|
5. **Mock Test Fixtures**:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# OpenEVSE HTTP API & Firmware Endpoint Matrix
|
|
2
|
+
|
|
3
|
+
This reference document compiles all known HTTP REST API endpoints, WebSocket paths, and RAPI fallback commands across OpenEVSE WiFi firmware generations:
|
|
4
|
+
- **v4.x / v5.x (Active Development)**: [`OpenEVSE/openevse_esp32_firmware`](https://github.com/OpenEVSE/openevse_esp32_firmware) (formerly `ESP32_WiFi_V4.x`).
|
|
5
|
+
- **v3.x (Legacy ESP32 - EOL)**: [`OpenEVSE/ESP32_WiFi_V3.x`](https://github.com/OpenEVSE/ESP32_WiFi_V3.x) (e.g. v3.3.1).
|
|
6
|
+
- **v2.x (Legacy ESP8266 - EOL)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x) (up to v2.9.1).
|
|
7
|
+
- **Controller Firmware**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse) (RAPI backend).
|
|
8
|
+
|
|
9
|
+
> [!IMPORTANT]
|
|
10
|
+
> **Active Development Warning**: Development on **v2.x** and **v3.x** has ended. Active development occurs exclusively on **v4.x / v5.x** in `OpenEVSE/openevse_esp32_firmware`. Whenever validating new features, routes, or bugfixes, always inspect `OpenEVSE/openevse_esp32_firmware` as the primary source of truth.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 1. Endpoint Availability Matrix
|
|
15
|
+
|
|
16
|
+
| Route | Supported Methods | v2.x (ESP8266) | v3.x (ESP32) | v4.x / v5.x (ESP32) | RAPI Fallback (v2/v3) | python-openevse-http Status |
|
|
17
|
+
| :--- | :--- | :---: | :---: | :---: | :--- | :---: |
|
|
18
|
+
| `/status` | `GET`, `POST` | `GET` only | `GET` only | `GET`, `POST` | N/A | ✅ Fully Supported |
|
|
19
|
+
| `/config` | `GET`, `POST` | `GET`, `POST` | `GET`, `POST` | `GET`, `POST` | N/A | ✅ Fully Supported |
|
|
20
|
+
| `/override` | `GET`, `POST`, `PATCH`, `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | `$FE` (enable) / `$FS` (sleep), `$SC` | ✅ Fully Supported |
|
|
21
|
+
| `/claims` | `GET`, `POST`, `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ✅ Fully Supported |
|
|
22
|
+
| `/limit` | `GET`, `POST`, `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | `$SH` (kWh limit), `$S3` (time limit) | ✅ Fully Supported |
|
|
23
|
+
| `/shaper` | `POST` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ✅ Fully Supported |
|
|
24
|
+
| `/divertmode` | `POST` | ✅ (`/divertmode`) | ✅ (`/divertmode`) | ✅ (`/divertmode`) | N/A (updates config flags) | ✅ Fully Supported |
|
|
25
|
+
| `/restart` | `POST` | ✅ (`/restart`) | ✅ (`/restart`) | ✅ (`/restart`) | `$FR` (EVSE controller reboot) | ✅ Fully Supported |
|
|
26
|
+
| `/r` or `/rapi` | `GET`, `POST` | `GET` (html/json) | `GET`, `POST` | `POST` (Mongoose) | Direct RAPI | ✅ Fully Supported |
|
|
27
|
+
| `/ws` | `WebSocket` | ✅ | ✅ | ✅ | N/A | ✅ Fully Supported |
|
|
28
|
+
| `/schedule` | `GET`, `POST`, `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | `$ST` / `$GD` | ✅ Fully Supported |
|
|
29
|
+
| `/schedule/plan` | `GET` | ❌ | ❌ | ✅ (v4.1.0+) | N/A | ✅ Fully Supported |
|
|
30
|
+
| `/time` | `GET`, `POST` | ❌ | ❌ (`/settime`) | ✅ (v4.0.0+) | `$S1` (RTC set) | ✅ Fully Supported |
|
|
31
|
+
| `/settime` | `GET`, `POST` | ❌ | ✅ | ⚠️ Legacy alias | `$S1` | ✅ Fully Supported |
|
|
32
|
+
| `/emeter` | `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ❌ Not Implemented |
|
|
33
|
+
| `/notifications` | `GET` | ❌ | ❌ | ✅ (v5.1.0+) | N/A | ❌ Not Implemented |
|
|
34
|
+
| `/notifications/ack` | `POST` | ❌ | ❌ | ✅ (v5.1.0+) | N/A | ❌ Not Implemented |
|
|
35
|
+
| `/update` | `GET`, `POST` | `GET`, `POST` | `GET`, `POST` | `GET`, `POST` | N/A | ✅ Fully Supported |
|
|
36
|
+
| `/logs` | `GET` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ✅ Fully Supported |
|
|
37
|
+
| `/logs/export` | `GET` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ❌ Not Implemented |
|
|
38
|
+
| `/certificates` | `GET`, `POST`, `DELETE` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ✅ Fully Supported |
|
|
39
|
+
| `/scan` | `GET` | ✅ | ✅ | ✅ | N/A | ❌ Not Implemented |
|
|
40
|
+
| `/apoff` | `GET`, `POST` | ✅ | ✅ | ✅ | N/A | ❌ Not Implemented |
|
|
41
|
+
| `/reset` | `GET`, `POST` | ✅ | ✅ | ✅ | N/A | ❌ Not Implemented |
|
|
42
|
+
| `/rfid/add` | `POST` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ✅ Fully Supported |
|
|
43
|
+
| `/rfid/users` | `GET`, `POST`, `DELETE` | ❌ | ❌ | ✅ (v5.0.0+) | N/A | ✅ Fully Supported |
|
|
44
|
+
| `/relay/reset` | `POST` | ❌ | ❌ | ✅ (v5.1.0+) | `$FH` | ✅ Fully Supported |
|
|
45
|
+
| `/relay/recovery` | `POST` | ❌ | ❌ | ✅ (v5.1.0+) | `$FK` | ✅ Fully Supported |
|
|
46
|
+
| `/cabletemp` | `GET`, `POST` | ❌ | ❌ | ✅ (v5.1.0+) | `$GN`, `$SN` | ✅ Fully Supported |
|
|
47
|
+
| `/teslaveh` / `/tesla/vehicles` | `GET` | ❌ | ✅ (`/teslaveh`) | ✅ | N/A | ❌ Not Implemented |
|
|
48
|
+
| `/energy/raw`, `/daily`, etc. | `GET` | ❌ | ❌ | ✅ (v4.0.0+) | N/A | ❌ Not Implemented |
|
|
49
|
+
| `/migrate/expand16mb` | `POST` | ❌ | ❌ | ✅ (v5.1.0+) | N/A | ❌ Not Implemented |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Core Endpoint Payload Differences
|
|
54
|
+
|
|
55
|
+
### A. `/config` (Device Configuration & Identification)
|
|
56
|
+
|
|
57
|
+
| Field Key | Type | v2.x (ESP8266) | v3.x (ESP32) | v4.x / v5.x (ESP32) | Notes |
|
|
58
|
+
| :--- | :--- | :---: | :---: | :---: | :--- |
|
|
59
|
+
| `wifi_serial` | `string` | ❌ (None) | ❌ (None) | ✅ | Formatted uppercase hardware MAC (e.g. `1234567890AB`). **Missing on v2/v3!** |
|
|
60
|
+
| `hostname` | `string` | ✅ (`"openevse"`) | ✅ (`"openevse-XXXX"`) | ✅ (`"openevse-XXXX"`) | Configured hostname. |
|
|
61
|
+
| `mqtt_announce_topic` | `string` | ✅ | ✅ | ✅ | Defaults to `"openevse/announce/XXXX"`. |
|
|
62
|
+
| `version` | `string` | ✅ (`"2.9.1"`) | ✅ (`"3.3.1"`) | ✅ (`"4.1.2"`) | WiFi gateway firmware version string. |
|
|
63
|
+
| `firmware` | `string` | ✅ (`"5.0.1"`) | ✅ | ✅ (`"7.1.3"`) | EVSE controller firmware version string. |
|
|
64
|
+
| `protocol` | `string` | ✅ (`"4.0.1"`) | ✅ (`"-"`) | ✅ (`"-"`) | RAPI protocol version. |
|
|
65
|
+
| `buildenv` | `string` | ❌ | ❌ | ✅ (e.g. `"openevse_wifi_v1"`) | PlatformIO build target name. |
|
|
66
|
+
| `d9_support` | `bool` | ❌ | ❌ | ✅ (v5.x) | Signals OpenEVSE D9+ controller hardware support. |
|
|
67
|
+
| `rfid_enabled` | `bool` | ❌ | ❌ | ✅ (v4.1.0+) | Virtual flag mask. |
|
|
68
|
+
| `led_brightness` | `int` | ❌ | ❌ | ✅ (v4.1.0+) | RGB LED brightness (0-255). |
|
|
69
|
+
| `flags` | `int` | ✅ (24-bit) | ✅ (24-bit) | ✅ (32-bit) | Bitmask of enabled services and configurations. |
|
|
70
|
+
|
|
71
|
+
### B. `/status` (Telemetry & State Monitoring)
|
|
72
|
+
|
|
73
|
+
| Field Key | Type | v2.x (ESP8266) | v3.x (ESP32) | v4.x / v5.x (ESP32) | Notes |
|
|
74
|
+
| :--- | :--- | :---: | :---: | :---: | :--- |
|
|
75
|
+
| `state` | `int` | ✅ | ✅ | ✅ | EVSE state: 1=Ready, 2=Connected, 3=Charging, 254=Sleeping, 255=Disabled. |
|
|
76
|
+
| `amp` | `float` | ✅ | ✅ | ✅ | Charging current in Amperes. |
|
|
77
|
+
| `voltage` | `int\|float` | ✅ | ✅ | ✅ | Voltage in Volts (e.g. 240). |
|
|
78
|
+
| `pilot` | `int` | ✅ | ✅ | ✅ | Pilot limit in Amperes. |
|
|
79
|
+
| `macaddress` | `string` | ❌ | ❌ | ✅ (v4.x+) | Hardware MAC address (e.g. `"AA:BB:CC:DD:EE:FF"`). |
|
|
80
|
+
| `ipaddress` | `string` | ✅ | ✅ | ✅ | Local IP address. |
|
|
81
|
+
| `shaper` | `int` | ❌ | ❌ | ✅ (v4.0.0+) | Current Shaper active: 0=Disabled, 1=Enabled. |
|
|
82
|
+
| `shaper_live_pwr` | `int` | ❌ | ❌ | ✅ (v4.0.0+) | Shaper live grid power (W). |
|
|
83
|
+
| `shaper_cur` | `float` | ❌ | ❌ | ✅ (v4.0.0+) | Shaper dynamically calculated max current (A). |
|
|
84
|
+
| `vehicle_soc` | `int` | ❌ | ❌ | ✅ (v4.1.0+) | Vehicle battery SoC (%). Pushed via `POST /status`. |
|
|
85
|
+
| `vehicle_range` | `int` | ❌ | ❌ | ✅ (v4.1.0+) | Vehicle range (km or miles). |
|
|
86
|
+
| `time_to_full_charge` | `int` | ❌ | ❌ | ✅ (v4.1.0+) | Seconds until complete charge. |
|
|
87
|
+
| `notifications` | `object` | ❌ | ❌ | ✅ (v5.1.0+) | Summary notifications object `{"count": N, "severity": "..."}`. |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 3. Actuator Security & CSRF (`actuatorMethodAllowed`)
|
|
92
|
+
|
|
93
|
+
In **v4.x / v5.x** firmware, destructive actuators (`/reset`, `/restart`, `/apoff`, `/divertmode`, `/shaper`, `/settime`, `/rfid/add`, `/relay/reset`, `/relay/recovery`) include CSRF protection:
|
|
94
|
+
- Calls must be sent via **`POST`** (or non-GET HTTP methods).
|
|
95
|
+
- If sent via `GET`, the firmware rejects with `403 Forbidden` unless the header `X-Requested-With: OpenEVSE` is present.
|
|
96
|
+
- `python-openevse-http` always sends actuators via `POST` or `PATCH`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 4. Upstream Repository Reference Links
|
|
101
|
+
|
|
102
|
+
When cross-checking implementation or adding support for new firmware features:
|
|
103
|
+
- **Active WiFi Gateway Codebase**: [`OpenEVSE/openevse_esp32_firmware`](https://github.com/OpenEVSE/openevse_esp32_firmware)
|
|
104
|
+
- HTTP routes & endpoints: `src/web_server.cpp`
|
|
105
|
+
- `/config` serialization: `src/web_server_config.cpp` & `src/app_config.cpp`
|
|
106
|
+
- Shaper & Claims: `src/current_shaper.cpp`, `src/web_server_claims.cpp`
|
|
107
|
+
- Override handler: `src/web_server.cpp` (`handleOverride`)
|
|
108
|
+
- **Legacy ESP32 Firmware (EOL)**: [`OpenEVSE/ESP32_WiFi_V3.x`](https://github.com/OpenEVSE/ESP32_WiFi_V3.x)
|
|
109
|
+
- **Legacy ESP8266 Firmware (EOL)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)
|
|
110
|
+
- **Controller RAPI Source**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse) (specifically `src/rapi.cpp`)
|
|
@@ -82,6 +82,6 @@ jobs:
|
|
|
82
82
|
with:
|
|
83
83
|
name: coverage-data
|
|
84
84
|
- name: Upload coverage report
|
|
85
|
-
uses: codecov/codecov-action@
|
|
85
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
86
86
|
with:
|
|
87
87
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -32,8 +32,9 @@ OpenEVSE chargers run various firmware versions (v2.x, v3.x, v4.x, v5.x) with di
|
|
|
32
32
|
|
|
33
33
|
### Validating Endpoints Against Firmware Sources
|
|
34
34
|
When adding or updating endpoints, payload keys, or RAPI commands, cross-reference against:
|
|
35
|
-
- **WiFi Gateway (
|
|
36
|
-
- **Legacy
|
|
35
|
+
- **WiFi Gateway (Current ESP32 v4/v5)**: [`OpenEVSE/openevse_esp32_firmware`](https://github.com/OpenEVSE/openevse_esp32_firmware) (formerly [`OpenEVSE/ESP32_WiFi_V4.x`](https://github.com/OpenEVSE/ESP32_WiFi_V4.x))
|
|
36
|
+
- **WiFi Gateway (Legacy ESP32 v3.x)**: [`OpenEVSE/ESP32_WiFi_V3.x`](https://github.com/OpenEVSE/ESP32_WiFi_V3.x)
|
|
37
|
+
- **Legacy WiFi (ESP8266 v2.x)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)
|
|
37
38
|
- **Controller / RAPI**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse) (commands in `src/rapi.cpp`)
|
|
38
39
|
Verify HTTP methods, expected JSON fields, success/error payload shapes, and version thresholds.
|
|
39
40
|
|
{python_openevse_http-1.6.0/python_openevse_http.egg-info → python_openevse_http-1.7.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.1
|
|
4
4
|
Summary: Python wrapper for OpenEVSE HTTP API
|
|
5
5
|
Home-page: https://github.com/firstof9/python-openevse-http
|
|
6
6
|
Download-URL: https://github.com/firstof9/python-openevse-http
|
|
@@ -51,6 +51,9 @@ A Python library for communicating with [OpenEVSE](https://www.openevse.com/) ch
|
|
|
51
51
|
- Control charging claims and limits.
|
|
52
52
|
- Handle schedules.
|
|
53
53
|
- **Shaper Toggle**: Enable or disable the grid shaper feature (requires firmware 4.0.0+).
|
|
54
|
+
- **Time Synchronization & RTC**: Manage time, timezone, and NTP sync (requires firmware 4.0.0+).
|
|
55
|
+
- **Event Logs**: Retrieve diagnostic log blocks and log event history (requires firmware 4.0.0+).
|
|
56
|
+
- **Certificates**: Manage SSL/TLS Root CA and client certificates (requires firmware 4.0.0+).
|
|
54
57
|
|
|
55
58
|
## Installation
|
|
56
59
|
|
|
@@ -116,6 +119,25 @@ To bypass certificate verification:
|
|
|
116
119
|
)
|
|
117
120
|
```
|
|
118
121
|
|
|
122
|
+
### GitHub API Token (Optional)
|
|
123
|
+
|
|
124
|
+
When performing firmware update checks (`charger.firmware_check()`) or automatic firmware updates (`charger.update_firmware()`), the library queries GitHub's Releases API. To avoid hitting unauthenticated rate limits (60 requests/hour), you can supply a GitHub personal access token (PAT):
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Provide a GitHub token during client initialization
|
|
128
|
+
charger = OpenEVSE(
|
|
129
|
+
"192.168.1.30",
|
|
130
|
+
session=session,
|
|
131
|
+
github_token="ghp_your_github_token_here",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Or set / update dynamically via property
|
|
135
|
+
charger.github_token = "ghp_your_github_token_here"
|
|
136
|
+
|
|
137
|
+
# Or pass per-call
|
|
138
|
+
latest = await charger.firmware_check(github_token="ghp_your_github_token_here")
|
|
139
|
+
```
|
|
140
|
+
|
|
119
141
|
## API Support Matrix
|
|
120
142
|
|
|
121
143
|
| Endpoint | Methods | Supported | Description |
|
|
@@ -124,22 +146,27 @@ To bypass certificate verification:
|
|
|
124
146
|
| `/config` | GET, POST | ✅ | System and WiFi configuration |
|
|
125
147
|
| `/override` | GET, POST, PATCH, DELETE | ✅ | Manual charging overrides & current limits |
|
|
126
148
|
| `/claims` | GET, POST, DELETE | ✅ | Client-based charging claims |
|
|
127
|
-
| `/schedule` | GET, POST |
|
|
149
|
+
| `/schedule` | GET, POST, DELETE | ✅ | Charging schedule management (v4.0.0+) |
|
|
128
150
|
| `/limit` | GET, POST, DELETE | ✅ | Charge limits (Time, Energy, SoC) |
|
|
129
151
|
| `/shaper` | POST | ✅ | Grid shaper control (v4.0.0+) |
|
|
130
152
|
| `/restart` | POST | ✅ | Reboot WiFi gateway or EVSE module |
|
|
131
153
|
| `/divertmode` | POST | ✅ | Solar divert mode control |
|
|
132
154
|
| `/r` (RAPI) | POST | ✅ | Direct RAPI command interface |
|
|
133
155
|
| `/ws` | GET | ✅ | WebSocket real-time updates |
|
|
134
|
-
| `/time` | GET, POST |
|
|
135
|
-
| `/
|
|
156
|
+
| `/time` | GET, POST | ✅ | RTC and NTP time settings (v4.0.0+) |
|
|
157
|
+
| `/settime` | POST | ✅ | Legacy time setting interface (v3.x / fallback to `$S1` on v2.x) |
|
|
158
|
+
| `/logs` | GET | ✅ | Event log block bounds and block events (v4.0.0+) |
|
|
136
159
|
| `/emeter` | DELETE | ❌ | Energy meter reset |
|
|
137
160
|
| `/wifi` | GET, POST | ❌ | Network scanning and AP configuration |
|
|
138
161
|
| `/tesla` | GET | ❌ | Tesla vehicle integration |
|
|
139
|
-
| `/certificates`| GET, POST, DELETE |
|
|
140
|
-
| `/schedule/plan`| GET |
|
|
162
|
+
| `/certificates`| GET, POST, DELETE | ✅ | SSL/TLS certificate management (v4.0.0+) |
|
|
163
|
+
| `/schedule/plan`| GET | ✅ | Schedule planning and optimization (v4.1.0+) |
|
|
141
164
|
| `/update` | POST | ✅ | Firmware update interface |
|
|
142
|
-
| `/
|
|
165
|
+
| `/relay/recovery` | POST | ✅ | Stuck relay recovery cycle (v5.1.0+ / `$FK`) |
|
|
166
|
+
| `/relay/reset` | POST | ✅ | Relay contact-life health estimation reset (v5.1.0+ / `$FH`) |
|
|
167
|
+
| `/cabletemp` | GET, POST | ✅ | Cable temperature monitor configuration and status (v5.1.0+) |
|
|
168
|
+
| `/rfid/add` | POST | ✅ | RFID tag pairing mode (v4.0.0+) |
|
|
169
|
+
| `/rfid/users` | GET, POST, DELETE | ✅ | RFID tag to user mappings (v5.0.0+) |
|
|
143
170
|
|
|
144
171
|
✅ = Fully Supported \| ⚠️ = Partial Support \| ❌ = Not yet implemented
|
|
145
172
|
|
|
@@ -18,6 +18,9 @@ A Python library for communicating with [OpenEVSE](https://www.openevse.com/) ch
|
|
|
18
18
|
- Control charging claims and limits.
|
|
19
19
|
- Handle schedules.
|
|
20
20
|
- **Shaper Toggle**: Enable or disable the grid shaper feature (requires firmware 4.0.0+).
|
|
21
|
+
- **Time Synchronization & RTC**: Manage time, timezone, and NTP sync (requires firmware 4.0.0+).
|
|
22
|
+
- **Event Logs**: Retrieve diagnostic log blocks and log event history (requires firmware 4.0.0+).
|
|
23
|
+
- **Certificates**: Manage SSL/TLS Root CA and client certificates (requires firmware 4.0.0+).
|
|
21
24
|
|
|
22
25
|
## Installation
|
|
23
26
|
|
|
@@ -83,6 +86,25 @@ To bypass certificate verification:
|
|
|
83
86
|
)
|
|
84
87
|
```
|
|
85
88
|
|
|
89
|
+
### GitHub API Token (Optional)
|
|
90
|
+
|
|
91
|
+
When performing firmware update checks (`charger.firmware_check()`) or automatic firmware updates (`charger.update_firmware()`), the library queries GitHub's Releases API. To avoid hitting unauthenticated rate limits (60 requests/hour), you can supply a GitHub personal access token (PAT):
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Provide a GitHub token during client initialization
|
|
95
|
+
charger = OpenEVSE(
|
|
96
|
+
"192.168.1.30",
|
|
97
|
+
session=session,
|
|
98
|
+
github_token="ghp_your_github_token_here",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Or set / update dynamically via property
|
|
102
|
+
charger.github_token = "ghp_your_github_token_here"
|
|
103
|
+
|
|
104
|
+
# Or pass per-call
|
|
105
|
+
latest = await charger.firmware_check(github_token="ghp_your_github_token_here")
|
|
106
|
+
```
|
|
107
|
+
|
|
86
108
|
## API Support Matrix
|
|
87
109
|
|
|
88
110
|
| Endpoint | Methods | Supported | Description |
|
|
@@ -91,22 +113,27 @@ To bypass certificate verification:
|
|
|
91
113
|
| `/config` | GET, POST | ✅ | System and WiFi configuration |
|
|
92
114
|
| `/override` | GET, POST, PATCH, DELETE | ✅ | Manual charging overrides & current limits |
|
|
93
115
|
| `/claims` | GET, POST, DELETE | ✅ | Client-based charging claims |
|
|
94
|
-
| `/schedule` | GET, POST |
|
|
116
|
+
| `/schedule` | GET, POST, DELETE | ✅ | Charging schedule management (v4.0.0+) |
|
|
95
117
|
| `/limit` | GET, POST, DELETE | ✅ | Charge limits (Time, Energy, SoC) |
|
|
96
118
|
| `/shaper` | POST | ✅ | Grid shaper control (v4.0.0+) |
|
|
97
119
|
| `/restart` | POST | ✅ | Reboot WiFi gateway or EVSE module |
|
|
98
120
|
| `/divertmode` | POST | ✅ | Solar divert mode control |
|
|
99
121
|
| `/r` (RAPI) | POST | ✅ | Direct RAPI command interface |
|
|
100
122
|
| `/ws` | GET | ✅ | WebSocket real-time updates |
|
|
101
|
-
| `/time` | GET, POST |
|
|
102
|
-
| `/
|
|
123
|
+
| `/time` | GET, POST | ✅ | RTC and NTP time settings (v4.0.0+) |
|
|
124
|
+
| `/settime` | POST | ✅ | Legacy time setting interface (v3.x / fallback to `$S1` on v2.x) |
|
|
125
|
+
| `/logs` | GET | ✅ | Event log block bounds and block events (v4.0.0+) |
|
|
103
126
|
| `/emeter` | DELETE | ❌ | Energy meter reset |
|
|
104
127
|
| `/wifi` | GET, POST | ❌ | Network scanning and AP configuration |
|
|
105
128
|
| `/tesla` | GET | ❌ | Tesla vehicle integration |
|
|
106
|
-
| `/certificates`| GET, POST, DELETE |
|
|
107
|
-
| `/schedule/plan`| GET |
|
|
129
|
+
| `/certificates`| GET, POST, DELETE | ✅ | SSL/TLS certificate management (v4.0.0+) |
|
|
130
|
+
| `/schedule/plan`| GET | ✅ | Schedule planning and optimization (v4.1.0+) |
|
|
108
131
|
| `/update` | POST | ✅ | Firmware update interface |
|
|
109
|
-
| `/
|
|
132
|
+
| `/relay/recovery` | POST | ✅ | Stuck relay recovery cycle (v5.1.0+ / `$FK`) |
|
|
133
|
+
| `/relay/reset` | POST | ✅ | Relay contact-life health estimation reset (v5.1.0+ / `$FH`) |
|
|
134
|
+
| `/cabletemp` | GET, POST | ✅ | Cable temperature monitor configuration and status (v5.1.0+) |
|
|
135
|
+
| `/rfid/add` | POST | ✅ | RFID tag pairing mode (v4.0.0+) |
|
|
136
|
+
| `/rfid/users` | GET, POST, DELETE | ✅ | RFID tag to user mappings (v5.0.0+) |
|
|
110
137
|
|
|
111
138
|
✅ = Fully Supported \| ⚠️ = Partial Support \| ❌ = Not yet implemented
|
|
112
139
|
|
|
@@ -21,13 +21,16 @@ from .const import (
|
|
|
21
21
|
ERROR_SESSION_REQUIRED,
|
|
22
22
|
ERROR_TIMEOUT,
|
|
23
23
|
UPDATE_TRIGGERS,
|
|
24
|
+
USER_AGENT,
|
|
24
25
|
)
|
|
25
26
|
from .exceptions import (
|
|
26
27
|
AlreadyListening,
|
|
27
28
|
AuthenticationError,
|
|
29
|
+
CommandFailedError,
|
|
28
30
|
MissingMethod,
|
|
29
31
|
MissingSerial,
|
|
30
32
|
ParseJSONError,
|
|
33
|
+
UnsupportedFeature,
|
|
31
34
|
)
|
|
32
35
|
from .managers import ManagersMixin
|
|
33
36
|
from .properties import PropertiesMixin
|
|
@@ -55,6 +58,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
55
58
|
session: aiohttp.ClientSession | None = None,
|
|
56
59
|
ssl: bool = False,
|
|
57
60
|
ssl_verify: bool = True,
|
|
61
|
+
github_token: str | None = None,
|
|
58
62
|
) -> None:
|
|
59
63
|
"""Connect to an OpenEVSE charger equipped with wifi or ethernet."""
|
|
60
64
|
self._user = user or ""
|
|
@@ -75,6 +79,11 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
75
79
|
self._owns_loop = False
|
|
76
80
|
self._loop_thread: threading.Thread | None = None
|
|
77
81
|
self._session = session
|
|
82
|
+
self._github_token = (
|
|
83
|
+
github_token.strip()
|
|
84
|
+
if isinstance(github_token, str) and github_token.strip()
|
|
85
|
+
else None
|
|
86
|
+
)
|
|
78
87
|
|
|
79
88
|
def _get_session(self) -> aiohttp.ClientSession:
|
|
80
89
|
"""Return the configured HTTP session or fail fast."""
|
|
@@ -93,6 +102,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
93
102
|
method: str = "",
|
|
94
103
|
data: Any = None,
|
|
95
104
|
rapi: Any = None,
|
|
105
|
+
headers: dict[str, str] | None = None,
|
|
96
106
|
) -> Mapping[str, Any] | list[Any] | str | bool:
|
|
97
107
|
"""Return result of processed HTTP request."""
|
|
98
108
|
auth = None
|
|
@@ -105,7 +115,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
105
115
|
|
|
106
116
|
session = self._get_session()
|
|
107
117
|
return await self._process_request_with_session(
|
|
108
|
-
session, url, method, data, rapi, auth
|
|
118
|
+
session, url, method, data, rapi, auth, headers
|
|
109
119
|
)
|
|
110
120
|
|
|
111
121
|
def _normalize_response(self, response: Any) -> dict[str, Any] | list[Any]:
|
|
@@ -123,6 +133,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
123
133
|
data: Any,
|
|
124
134
|
rapi: Any,
|
|
125
135
|
auth: Any,
|
|
136
|
+
headers: dict[str, str] | None = None,
|
|
126
137
|
) -> Mapping[str, Any] | list[Any] | str | bool:
|
|
127
138
|
"""Process a request with a given session."""
|
|
128
139
|
if not hasattr(session, method):
|
|
@@ -136,7 +147,17 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
136
147
|
method,
|
|
137
148
|
)
|
|
138
149
|
try:
|
|
139
|
-
|
|
150
|
+
req_headers: dict[str, str] = {
|
|
151
|
+
"User-Agent": USER_AGENT,
|
|
152
|
+
"X-Requested-With": "OpenEVSE",
|
|
153
|
+
}
|
|
154
|
+
if headers:
|
|
155
|
+
req_headers.update(headers)
|
|
156
|
+
kwargs: dict[str, Any] = {
|
|
157
|
+
"data": rapi,
|
|
158
|
+
"auth": auth,
|
|
159
|
+
"headers": req_headers,
|
|
160
|
+
}
|
|
140
161
|
if data is not None:
|
|
141
162
|
kwargs["json"] = data
|
|
142
163
|
if url.startswith("https://") and not self.ssl_verify:
|
|
@@ -166,15 +187,16 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
166
187
|
|
|
167
188
|
if resp.status == 400:
|
|
168
189
|
if isinstance(response_content, dict) and "msg" in response_content:
|
|
169
|
-
|
|
190
|
+
msg = str(response_content["msg"])
|
|
170
191
|
elif (
|
|
171
192
|
isinstance(response_content, dict)
|
|
172
193
|
and "error" in response_content
|
|
173
194
|
):
|
|
174
|
-
|
|
195
|
+
msg = str(response_content["error"])
|
|
175
196
|
else:
|
|
176
|
-
|
|
177
|
-
|
|
197
|
+
msg = str(response_content)
|
|
198
|
+
_LOGGER.error("Error 400: %s", msg)
|
|
199
|
+
raise CommandFailedError(msg)
|
|
178
200
|
if resp.status == 401:
|
|
179
201
|
_LOGGER.error("Authentication error: %s", response_content)
|
|
180
202
|
raise AuthenticationError
|
|
@@ -517,3 +539,66 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
517
539
|
def version_check(self, min_version: str, max_version: str = "") -> bool:
|
|
518
540
|
"""Unprotected function call for version checking."""
|
|
519
541
|
return self._version_check(min_version=min_version, max_version=max_version)
|
|
542
|
+
|
|
543
|
+
def _controller_version_check(
|
|
544
|
+
self, min_version: str, max_version: str = ""
|
|
545
|
+
) -> bool:
|
|
546
|
+
"""Return bool if minimum controller (open_evse) version is met."""
|
|
547
|
+
if "firmware" not in self._config:
|
|
548
|
+
_LOGGER.debug("Unable to find controller firmware version.")
|
|
549
|
+
return False
|
|
550
|
+
cutoff = AwesomeVersion(min_version)
|
|
551
|
+
limit = ""
|
|
552
|
+
if max_version != "":
|
|
553
|
+
limit = AwesomeVersion(max_version)
|
|
554
|
+
|
|
555
|
+
current = get_awesome_version(self._config["firmware"])
|
|
556
|
+
if current.strategy == "unknown":
|
|
557
|
+
_LOGGER.debug(
|
|
558
|
+
"Non-semver controller firmware version detected: %s",
|
|
559
|
+
self._config["firmware"],
|
|
560
|
+
)
|
|
561
|
+
return False
|
|
562
|
+
|
|
563
|
+
if limit:
|
|
564
|
+
try:
|
|
565
|
+
if cutoff <= current < limit:
|
|
566
|
+
return True
|
|
567
|
+
except AwesomeVersionCompareException:
|
|
568
|
+
_LOGGER.debug("Non-semver controller firmware version detected.")
|
|
569
|
+
return False
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
if current >= cutoff:
|
|
573
|
+
return True
|
|
574
|
+
except AwesomeVersionCompareException:
|
|
575
|
+
_LOGGER.debug("Non-semver controller firmware version detected.")
|
|
576
|
+
return False
|
|
577
|
+
|
|
578
|
+
def controller_version_check(self, min_version: str, max_version: str = "") -> bool:
|
|
579
|
+
"""Unprotected function call for controller version checking."""
|
|
580
|
+
return self._controller_version_check(
|
|
581
|
+
min_version=min_version, max_version=max_version
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def _require_firmware(
|
|
585
|
+
self, min_version: str, feature: str, max_version: str = ""
|
|
586
|
+
) -> None:
|
|
587
|
+
"""Verify minimum gateway firmware version or log and raise UnsupportedFeature."""
|
|
588
|
+
if not self._version_check(min_version, max_version):
|
|
589
|
+
err = UnsupportedFeature(
|
|
590
|
+
feature, min_version=min_version, component="gateway"
|
|
591
|
+
)
|
|
592
|
+
_LOGGER.debug("%s", err)
|
|
593
|
+
raise err
|
|
594
|
+
|
|
595
|
+
def _require_controller_firmware(
|
|
596
|
+
self, min_version: str, feature: str, max_version: str = ""
|
|
597
|
+
) -> None:
|
|
598
|
+
"""Verify minimum OpenEVSE controller firmware version or log and raise UnsupportedFeature."""
|
|
599
|
+
if not self._controller_version_check(min_version, max_version):
|
|
600
|
+
err = UnsupportedFeature(
|
|
601
|
+
feature, min_version=min_version, component="OpenEVSE controller"
|
|
602
|
+
)
|
|
603
|
+
_LOGGER.debug("%s", err)
|
|
604
|
+
raise err
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Commands mixin for OpenEVSE HTTP client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .commands_base import BaseCommandMixin
|
|
6
|
+
from .commands_core import CoreCommandsMixin
|
|
7
|
+
from .commands_diagnostics import DiagnosticsCommandsMixin
|
|
8
|
+
from .commands_firmware import FirmwareCommandsMixin
|
|
9
|
+
from .commands_schedule import ScheduleCommandsMixin
|
|
10
|
+
from .commands_security import SecurityCommandsMixin
|
|
11
|
+
from .commands_time import TimeCommandsMixin
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BaseCommandMixin",
|
|
15
|
+
"CommandsMixin",
|
|
16
|
+
"CoreCommandsMixin",
|
|
17
|
+
"DiagnosticsCommandsMixin",
|
|
18
|
+
"FirmwareCommandsMixin",
|
|
19
|
+
"ScheduleCommandsMixin",
|
|
20
|
+
"SecurityCommandsMixin",
|
|
21
|
+
"TimeCommandsMixin",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CommandsMixin(
|
|
26
|
+
CoreCommandsMixin,
|
|
27
|
+
FirmwareCommandsMixin,
|
|
28
|
+
ScheduleCommandsMixin,
|
|
29
|
+
SecurityCommandsMixin,
|
|
30
|
+
DiagnosticsCommandsMixin,
|
|
31
|
+
TimeCommandsMixin,
|
|
32
|
+
):
|
|
33
|
+
"""Composite mixin providing all command methods for OpenEVSE."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Base mixin providing protocol stubs and common helpers for command mixins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseCommandMixin:
|
|
15
|
+
"""Base mixin defining internal attributes and stub methods required by command mixins."""
|
|
16
|
+
|
|
17
|
+
url: str
|
|
18
|
+
ssl: bool
|
|
19
|
+
ssl_verify: bool
|
|
20
|
+
_status: dict[str, Any]
|
|
21
|
+
_config: dict[str, Any]
|
|
22
|
+
_session: aiohttp.ClientSession | None
|
|
23
|
+
_github_token: str | None
|
|
24
|
+
|
|
25
|
+
# These are implemented in OpenEVSE (client.py)
|
|
26
|
+
def _version_check(self, min_version: str, max_version: str = "") -> bool:
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
def _controller_version_check(
|
|
30
|
+
self, min_version: str, max_version: str = ""
|
|
31
|
+
) -> bool:
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
def _require_firmware(
|
|
35
|
+
self, min_version: str, feature: str, max_version: str = ""
|
|
36
|
+
) -> None:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def _require_controller_firmware(
|
|
40
|
+
self, min_version: str, feature: str, max_version: str = ""
|
|
41
|
+
) -> None:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
async def process_request(
|
|
45
|
+
self,
|
|
46
|
+
url: str,
|
|
47
|
+
method: str = "",
|
|
48
|
+
data: Any = None,
|
|
49
|
+
rapi: Any = None,
|
|
50
|
+
headers: dict[str, str] | None = None,
|
|
51
|
+
) -> Mapping[str, Any] | list[Any] | str | bool:
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
async def send_command(self, command: str) -> tuple[Any, Any]:
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
async def update(self, force_status: bool = False) -> None:
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
|
|
60
|
+
def _normalize_response(self, response: Any) -> dict[str, Any] | list[Any]:
|
|
61
|
+
"""Normalize response to a dict or list."""
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
|
|
64
|
+
def _get_session(self) -> aiohttp.ClientSession:
|
|
65
|
+
"""Return the configured HTTP session."""
|
|
66
|
+
raise NotImplementedError
|