python-openevse-http 1.5.0__tar.gz → 1.7.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 (91) hide show
  1. python_openevse_http-1.7.0/.agents/skills/openevse-api-guide/SKILL.md +115 -0
  2. python_openevse_http-1.7.0/.agents/skills/openevse-api-guide/references/endpoints_matrix.md +110 -0
  3. python_openevse_http-1.7.0/.agents/skills/openevse-dev-workflow/SKILL.md +87 -0
  4. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/release-drafter.yml +1 -0
  5. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/workflows/autolabeler.yml +2 -2
  6. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/workflows/links.yml +3 -3
  7. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/workflows/publish-to-pypi.yml +4 -4
  8. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/workflows/release-drafter.yml +2 -2
  9. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/workflows/test.yml +11 -11
  10. python_openevse_http-1.7.0/AGENTS.md +129 -0
  11. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/EXTERNAL_SESSION.md +3 -0
  12. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/PKG-INFO +36 -7
  13. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/README.md +35 -6
  14. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/__init__.py +8 -0
  15. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/client.py +85 -2
  16. python_openevse_http-1.7.0/openevsehttp/commands.py +33 -0
  17. python_openevse_http-1.7.0/openevsehttp/commands_base.py +66 -0
  18. python_openevse_http-1.5.0/openevsehttp/commands.py → python_openevse_http-1.7.0/openevsehttp/commands_core.py +44 -292
  19. python_openevse_http-1.7.0/openevsehttp/commands_diagnostics.py +229 -0
  20. python_openevse_http-1.7.0/openevsehttp/commands_firmware.py +260 -0
  21. python_openevse_http-1.7.0/openevsehttp/commands_schedule.py +147 -0
  22. python_openevse_http-1.7.0/openevsehttp/commands_security.py +265 -0
  23. python_openevse_http-1.7.0/openevsehttp/commands_time.py +186 -0
  24. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/const.py +10 -1
  25. python_openevse_http-1.7.0/openevsehttp/exceptions.py +62 -0
  26. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/managers.py +6 -1
  27. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/properties.py +103 -0
  28. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/sensors.py +6 -1
  29. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/pyproject.toml +1 -1
  30. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/PKG-INFO +36 -7
  31. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/SOURCES.txt +11 -0
  32. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/scm_file_list.json +55 -44
  33. python_openevse_http-1.7.0/python_openevse_http.egg-info/scm_version.json +8 -0
  34. python_openevse_http-1.7.0/requirements_lint.txt +4 -0
  35. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/requirements_test.txt +2 -2
  36. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/conftest.py +6 -2
  37. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_client.py +221 -4
  38. python_openevse_http-1.7.0/tests/test_commands.py +2764 -0
  39. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_managers.py +6 -5
  40. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_mixins.py +31 -1
  41. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_properties.py +95 -0
  42. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_shaper.py +7 -3
  43. python_openevse_http-1.5.0/openevsehttp/exceptions.py +0 -33
  44. python_openevse_http-1.5.0/python_openevse_http.egg-info/scm_version.json +0 -8
  45. python_openevse_http-1.5.0/requirements_lint.txt +0 -4
  46. python_openevse_http-1.5.0/tests/test_commands.py +0 -1369
  47. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  48. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  49. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  50. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/dependabot.yml +0 -0
  51. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.github/pull_request_template.md +0 -0
  52. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.gitignore +0 -0
  53. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.pre-commit-config.yaml +0 -0
  54. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/.yamllint +0 -0
  55. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/LICENSE +0 -0
  56. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/codecov.yml +0 -0
  57. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/example_external_session.py +0 -0
  58. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/__main__.py +0 -0
  59. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/py.typed +0 -0
  60. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/utils.py +0 -0
  61. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/openevsehttp/websocket.py +0 -0
  62. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  63. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/not-zip-safe +0 -0
  64. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/requires.txt +0 -0
  65. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/python_openevse_http.egg-info/top_level.txt +0 -0
  66. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/requirements.txt +0 -0
  67. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/setup.cfg +0 -0
  68. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/setup.py +0 -0
  69. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/__init__.py +0 -0
  70. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/common.py +0 -0
  71. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/github_v2.json +0 -0
  72. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/github_v4.json +0 -0
  73. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v2_json/config.json +0 -0
  74. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v2_json/status.json +0 -0
  75. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  76. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-broken.json +0 -0
  77. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-dev.json +0 -0
  78. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  79. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-new.json +0 -0
  80. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
  81. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/config.json +0 -0
  82. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/schedule.json +0 -0
  83. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/status-broken.json +0 -0
  84. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/status-new.json +0 -0
  85. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/v4_json/status.json +0 -0
  86. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/fixtures/websocket.json +0 -0
  87. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_external_session.py +0 -0
  88. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_main_edge_cases.py +0 -0
  89. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_sensors.py +0 -0
  90. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tests/test_websocket.py +0 -0
  91. {python_openevse_http-1.5.0 → python_openevse_http-1.7.0}/tox.ini +0 -0
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: openevse-api-guide
3
+ description: >-
4
+ Use this skill when implementing, refactoring, or testing OpenEVSE charger
5
+ commands, REST API endpoints, RAPI commands, properties, or exception handling in python-openevse-http.
6
+ ---
7
+
8
+ # OpenEVSE API & Command Implementation Guide
9
+
10
+ This skill provides architectural guidelines, endpoint conventions, and error handling patterns for developing `python-openevse-http`.
11
+
12
+ ## Architecture
13
+
14
+ The main client `OpenEVSE` combines several mixins:
15
+ - `CommandsMixin` (`openevsehttp/commands.py`): Command execution methods.
16
+ - `PropertiesMixin` (`openevsehttp/properties.py`): Configuration & state properties.
17
+ - `SensorsMixin` (`openevsehttp/sensors.py`): Energy, current, voltage, temperature telemetry.
18
+ - `WebsocketMixin` (`openevsehttp/websocket.py`): Real-time event streams.
19
+
20
+ ## Endpoints & RAPI Commands Reference
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
+
25
+ | Action | HTTP Endpoint (v4+) | RAPI Command (v2/v3) | Method |
26
+ | :--- | :--- | :--- | :--- |
27
+ | Status | `/status` | N/A | GET |
28
+ | Config | `/config` | N/A | GET / POST |
29
+ | Manual Override | `/override` | `$FE` (enable) / `$FS` (sleep) | GET / POST / PATCH / DELETE |
30
+ | Soft Current Limit | `/override` (charge_current) | `$SC <amps> [N\|V]` | POST |
31
+ | Shaper Mode | `/shaper` | N/A | POST |
32
+ | Divert Mode | `/divertmode` or `/config` | N/A | POST |
33
+ | Module Restart | `/restart` (`device: gateway\|evse`) | `$FR` (evse restart) | POST |
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.
46
+
47
+ ## Firmware Version Branching
48
+
49
+ Always check firmware compatibility using `self._version_check(min_version)`:
50
+ ```python
51
+ if self._version_check("4.0.1"):
52
+ # Use HTTP REST endpoint
53
+ response = await self.process_request(url=f"{self.url}override", method="patch")
54
+ else:
55
+ # Fallback to RAPI command for older firmware
56
+ response, msg = await self.send_command("$FE" if state == 254 else "$FS")
57
+ ```
58
+
59
+ If a feature is not supported on older firmware:
60
+ ```python
61
+ if not self._version_check("4.1.0"):
62
+ _LOGGER.debug("Feature not supported for older firmware.")
63
+ raise UnsupportedFeature
64
+ ```
65
+
66
+ ## Exception Handling Conventions
67
+
68
+ All custom exceptions inherit from `OpenEVSEError(Exception)`.
69
+
70
+ - **`CommandFailedError`**: Raise when a command returns an error response, fails HTTP verification, or returns `$NK` / `RAPI_ERRORS`.
71
+ - **`UnknownStateError`**: Raise when prior charger state or configuration is required to determine the command payload (e.g. toggling) but is missing or `None`.
72
+ - **`FirmwareResolutionError`**: Raise when GitHub release download URL cannot be determined from the release metadata.
73
+ - **`UnsupportedFeature`**: Raise when charger firmware is below the minimum supported version for a feature.
74
+ - **`AuthenticationError`**: Raise on 401 unauthorized.
75
+
76
+ ```python
77
+ from .exceptions import CommandFailedError, UnknownStateError, UnsupportedFeature
78
+ ```
79
+
80
+ ## Validating Endpoints Against Firmware Repositories
81
+
82
+ When adding, modifying, or debugging endpoints and RAPI commands, cross-reference against the upstream OpenEVSE firmware sources:
83
+
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)
87
+ - **OpenEVSE Controller Firmware (RAPI)**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse)
88
+
89
+ ### What to Verify in Firmware Sources:
90
+ 1. **Route & Method Handlers**:
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`).
92
+ - Confirm expected query parameters or JSON body fields (e.g. `divertmode=...`, `{"device": "gateway"}`, `{"charge_current": ...}`).
93
+ 2. **Response Formats & Statuses**:
94
+ - Verify success and error response payloads (e.g., `{"msg": "done"}`, `{"result": "OK", "msg": "..."}`, or plain string messages like `"Current Shaper state changed"`).
95
+ - Update `SUCCESS_ANSWERS` in `openevsehttp/const.py` if new success indicators are introduced.
96
+ 3. **Firmware Version Thresholds**:
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.
98
+ 4. **RAPI Command Specifications**:
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`).
100
+ 5. **Mock Test Fixtures**:
101
+ - Update or add mock JSON payloads under `tests/fixtures/v4_json/` and `tests/fixtures/v2_json/` to mirror real firmware response shapes.
102
+
103
+ ## Writing Tests for Commands
104
+
105
+ When testing command methods:
106
+ 1. Use fixtures from `tests/conftest.py` (`test_charger`, `test_charger_v2`, `test_charger_new`).
107
+ 2. Mock responses using `mock_aioclient`:
108
+ ```python
109
+ mock_aioclient.post(
110
+ TEST_URL_CONFIG,
111
+ status=200,
112
+ body='{"msg": "done"}',
113
+ )
114
+ ```
115
+ 3. Test success paths, failure responses (`CommandFailedError`), missing state paths (`UnknownStateError`), and older firmware version behavior (`UnsupportedFeature` / RAPI commands).
@@ -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`)
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: openevse-dev-workflow
3
+ description: >-
4
+ Use this skill when running tests, formatting code, checking linters,
5
+ running type checks, or managing tox environments in python-openevse-http.
6
+ ---
7
+
8
+ # OpenEVSE Development & Testing Workflow
9
+
10
+ This skill guides you through executing tests, linting, formatting, and type checks within the `python-openevse-http` repository.
11
+
12
+ ## Environment & Tooling
13
+
14
+ The project uses `tox` for managing isolated virtual environments and running test tools (`pytest`, `ruff`, `mypy`).
15
+
16
+ ### 1. Running Unit Tests
17
+
18
+ Run full test suite via tox:
19
+ ```bash
20
+ tox -e py314
21
+ ```
22
+
23
+ To run fast targeted test runs with the existing tox environment:
24
+ ```bash
25
+ # Run all tests
26
+ .tox/py314/bin/pytest
27
+
28
+ # Run a specific test file
29
+ .tox/py314/bin/pytest tests/test_commands.py
30
+
31
+ # Run a single test function
32
+ .tox/py314/bin/pytest tests/test_commands.py -k "test_toggle_override"
33
+
34
+ # Run with verbose output and stdout
35
+ .tox/py314/bin/pytest -v -s tests/test_client.py
36
+ ```
37
+
38
+ ### 2. Formatting & Linting (Ruff)
39
+
40
+ Check formatting and linting:
41
+ ```bash
42
+ tox -e lint
43
+ ```
44
+
45
+ To auto-format or auto-fix lint errors:
46
+ ```bash
47
+ # Format code
48
+ .tox/lint/bin/ruff format ./
49
+
50
+ # Auto-fix linting issues
51
+ .tox/lint/bin/ruff check --fix openevsehttp tests
52
+ ```
53
+
54
+ ### 3. Type Checking (Mypy)
55
+
56
+ Run static type checks:
57
+ ```bash
58
+ tox -e mypy
59
+ ```
60
+ Or directly:
61
+ ```bash
62
+ .tox/mypy/bin/mypy openevsehttp
63
+ ```
64
+
65
+ ### 4. Running All CI Checks Together
66
+
67
+ Before submitting PRs or finalizing tasks, verify everything in one step:
68
+ ```bash
69
+ tox -e py314,lint,mypy
70
+ ```
71
+
72
+ ### 5. Pre-commit Hooks
73
+
74
+ Pre-commit hooks are configured via `.pre-commit-config.yaml`. They run automatically on `git commit`, or you can trigger them manually:
75
+ ```bash
76
+ pre-commit run --all-files
77
+ ```
78
+
79
+ ### 6. Pull Requests & Issue Creation
80
+
81
+ - **Pull Requests**:
82
+ - Always use the template in [`.github/pull_request_template.md`](../../.github/pull_request_template.md).
83
+ - Include a summary, issue link (`Fixes #<number>`), type of change, and completed checklist.
84
+ - Follow conventional commits in PR titles (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`).
85
+ - **Issues & Feature Requests**:
86
+ - Use [`.github/ISSUE_TEMPLATE/bug_report.yml`](../../.github/ISSUE_TEMPLATE/bug_report.yml) for bugs (`[Bug]: <summary>`).
87
+ - Use [`.github/ISSUE_TEMPLATE/feature_request.yml`](../../.github/ISSUE_TEMPLATE/feature_request.yml) for feature requests (`[Feature Request]: <summary>`).
@@ -20,6 +20,7 @@ categories:
20
20
  labels:
21
21
  - "performance"
22
22
  - title: "🔧 Maintenance 🔧"
23
+ collapse-after: 2
23
24
  labels:
24
25
  - "chore"
25
26
  - "documentation"
@@ -34,10 +34,10 @@ jobs:
34
34
  runs-on: ubuntu-latest
35
35
  timeout-minutes: 3
36
36
  steps:
37
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
37
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
38
38
  with:
39
39
  egress-policy: audit
40
40
 
41
- - uses: release-drafter/release-drafter/autolabeler@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7
41
+ - uses: release-drafter/release-drafter/autolabeler@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7
42
42
  env:
43
43
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -15,13 +15,13 @@ jobs:
15
15
  linkChecker:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
18
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
19
19
  with:
20
20
  egress-policy: audit
21
21
 
22
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
23
 
24
24
  - name: Link Checker
25
- uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
25
+ uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
26
26
  with:
27
27
  args: --verbose --no-progress './**/*.md'
@@ -21,17 +21,17 @@ jobs:
21
21
  contents: read
22
22
  id-token: write
23
23
  steps:
24
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
24
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
25
25
  with:
26
26
  egress-policy: audit
27
27
 
28
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
29
  with:
30
30
  ref: ${{ inputs.tag || github.ref }}
31
31
  fetch-depth: 0
32
32
 
33
33
  - name: Install uv
34
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
34
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
35
35
  with:
36
36
  enable-cache: true
37
37
  version: "0.10.9"
@@ -43,4 +43,4 @@ jobs:
43
43
  run: uv build
44
44
 
45
45
  - name: Publish release to PyPI
46
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
46
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -13,11 +13,11 @@ jobs:
13
13
  update_release_draft:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
16
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
17
17
  with:
18
18
  egress-policy: audit
19
19
 
20
20
  # Drafts your next Release notes as Pull Requests are merged into "main"
21
- - uses: release-drafter/release-drafter@ed4bc48ec97379be2258e7b7ac2624a3e26ab809 # v7
21
+ - uses: release-drafter/release-drafter@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7
22
22
  env:
23
23
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -18,17 +18,17 @@ jobs:
18
18
  prek:
19
19
  runs-on: ubuntu-latest
20
20
  steps:
21
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
21
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
22
22
  with:
23
23
  egress-policy: audit
24
24
 
25
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
26
  - name: Set up Python
27
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
27
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
28
28
  with:
29
29
  python-version: "3.14"
30
- - name: Install prek
31
- run: pip install prek==0.4.0
30
+ - name: Install dependencies
31
+ run: pip install -r requirements_lint.txt
32
32
  - name: Run prek
33
33
  run: prek run --all-files --skip no-commit-to-branch
34
34
  build:
@@ -41,15 +41,15 @@ jobs:
41
41
  - "3.14"
42
42
 
43
43
  steps:
44
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
44
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
45
45
  with:
46
46
  egress-policy: audit
47
47
 
48
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
48
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
49
49
  with:
50
50
  fetch-depth: 2
51
51
  - name: Set up Python ${{ matrix.python-version }}
52
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
52
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
53
53
  with:
54
54
  python-version: ${{ matrix.python-version }}
55
55
  - name: Install dependencies
@@ -69,12 +69,12 @@ jobs:
69
69
  runs-on: ubuntu-latest
70
70
  needs: build
71
71
  steps:
72
- - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
72
+ - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
73
73
  with:
74
74
  egress-policy: audit
75
75
 
76
76
  - name: Check out the repository
77
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
77
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
78
78
  with:
79
79
  fetch-depth: 2
80
80
  - name: Download coverage data
@@ -82,6 +82,6 @@ jobs:
82
82
  with:
83
83
  name: coverage-data
84
84
  - name: Upload coverage report
85
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
85
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
86
86
  with:
87
87
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,129 @@
1
+ # Agent Guidelines for python-openevse-http
2
+
3
+ This document outlines key architecture, conventions, workflows, and testing practices for agentic assistants operating in this repository.
4
+
5
+ ---
6
+
7
+ ## 1. Project Overview & Architecture
8
+
9
+ `python-openevse-http` is an asynchronous Python library for interacting with OpenEVSE electric vehicle chargers via their HTTP REST API, WebSocket streams, and RAPI commands.
10
+
11
+ ### Core Modules & Mixins
12
+ The main client class `OpenEVSE` in `openevsehttp/client.py` inherits from multiple mixins:
13
+ - **`openevsehttp/client.py`**: Core client lifecycle, authentication, request processing (`process_request`, `send_command`), status updates (`update`), and session management.
14
+ - **`openevsehttp/commands.py` (`CommandsMixin`)**: Charger commands (e.g. `set_override`, `toggle_override`, `clear_override`, `set_current`, `set_charge_mode`, `divert_mode`, `set_shaper`, `toggle_shaper`, `restart_wifi`, `restart_evse`, `update_firmware`).
15
+ - **`openevsehttp/properties.py` (`PropertiesMixin`)**: Charger properties, configuration parsing, state decoding (`states`, `divert_mode`), firmware version parsing.
16
+ - **`openevsehttp/sensors.py` (`SensorsMixin`)**: Sensor values, telemetry, power/voltage calculations.
17
+ - **`openevsehttp/websocket.py` (`WebsocketMixin`, `OpenEVSEWebsocket`)**: Real-time websocket communication and state change listeners.
18
+ - **`openevsehttp/exceptions.py`**: Typed library exceptions inheriting from `OpenEVSEError`.
19
+
20
+ ### Client Session Requirement
21
+ - `OpenEVSE` uses caller-provided `aiohttp.ClientSession` (via `session=...`). If not provided, accessing network operations raises `RuntimeError(ERROR_SESSION_REQUIRED)`.
22
+ - The session must run on the active event loop.
23
+
24
+ ---
25
+
26
+ ## 2. Firmware Version Handling & Upstream Validation
27
+
28
+ OpenEVSE chargers run various firmware versions (v2.x, v3.x, v4.x, v5.x) with different capabilities:
29
+ - **`self._version_check(min_version, max_version="")`**: Use this helper to conditionally execute HTTP API endpoints (v4+) versus RAPI command fallbacks (v2/v3, e.g. `$FE`, `$FS`, `$SC`, `$FR`).
30
+ - Always handle version edge cases (e.g. non-semver development strings like `4.1.2.dev`).
31
+ - Raise `UnsupportedFeature` if a feature is not supported on older firmware.
32
+
33
+ ### Validating Endpoints Against Firmware Sources
34
+ When adding or updating endpoints, payload keys, or RAPI commands, cross-reference against:
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)
38
+ - **Controller / RAPI**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse) (commands in `src/rapi.cpp`)
39
+ Verify HTTP methods, expected JSON fields, success/error payload shapes, and version thresholds.
40
+
41
+ ---
42
+
43
+ ## 3. Exception Handling
44
+
45
+ All custom exceptions inherit from `OpenEVSEError(Exception)`:
46
+ - `CommandFailedError`: Command execution failure, RAPI rejection (`$NK`), or error HTTP response.
47
+ - `UnknownStateError`: Required state or configuration missing before command execution (e.g. toggle state).
48
+ - `FirmwareResolutionError`: GitHub release asset resolution failure.
49
+ - `AuthenticationError`: HTTP 401 / auth failures.
50
+ - `UnsupportedFeature`: Feature not available for current firmware version.
51
+ - `ParseJSONError`, `InvalidType`, `MissingMethod`, `MissingSerial`, `AlreadyListening`.
52
+
53
+ Export all public exception classes in `openevsehttp/__init__.py`.
54
+
55
+ ---
56
+
57
+ ## 4. Development & Testing Workflow
58
+
59
+ ### Running Tests
60
+ Use `tox` for isolated environments:
61
+ ```bash
62
+ # Run unit tests on Python 3.14 / active environment
63
+ tox -e py314
64
+
65
+ # Or run pytest directly within the tox environment
66
+ .tox/py314/bin/pytest
67
+
68
+ # Target specific test files
69
+ .tox/py314/bin/pytest tests/test_commands.py -k "test_toggle_override"
70
+ ```
71
+
72
+ ### Linting & Formatting
73
+ ```bash
74
+ # Run ruff formatting check & linter
75
+ tox -e lint
76
+
77
+ # Format code automatically
78
+ .tox/lint/bin/ruff format ./
79
+
80
+ # Run linter with auto-fixes
81
+ .tox/lint/bin/ruff check --fix openevsehttp tests
82
+ ```
83
+
84
+ ### Type Checking
85
+ ```bash
86
+ tox -e mypy
87
+ # Or directly:
88
+ .tox/mypy/bin/mypy openevsehttp
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 5. Testing & Mocking Guidelines
94
+
95
+ - Tests use `pytest` with `pytest-asyncio` (`asyncio_default_fixture_loop_scope = "function"`).
96
+ - Test fixtures in `tests/conftest.py`:
97
+ - `test_charger`: Standard v4 charger client with mocked endpoints.
98
+ - `test_charger_v2`: Legacy v2 firmware mock.
99
+ - `test_charger_new`: Newer v4 fixture with shaper and modern endpoints.
100
+ - `test_charger_auth`: Authenticated charger mock.
101
+ - `mock_aioclient`: `AiohttpClientMocker` instance for intercepting HTTP requests (`get`, `post`, `patch`, `delete`).
102
+ - Fixture data files are located in `tests/fixtures/` (`v4_json/`, `v2_json/`).
103
+
104
+ ---
105
+
106
+ ## 6. Commit, Pull Request & Issue Guidelines
107
+
108
+ ### Creating Pull Requests
109
+ - **Use the PR Template**: Always structure PR descriptions according to [`.github/pull_request_template.md`](.github/pull_request_template.md):
110
+ - **Description**: Provide a clear summary of changes, motivation, and link related issues (`Fixes #<number>`).
111
+ - **Type of change**: Check the relevant boxes (`Bug fix`, `New feature`, `Breaking change`, `Code quality / Refactoring`, `Documentation update`).
112
+ - **Checklist**: Complete all checklist items before opening or marking ready for review.
113
+ - **Semantic PR Titles**: Use conventional commit titles matching [`.github/release-drafter.yml`](.github/release-drafter.yml):
114
+ - `feat:` New features / enhancements
115
+ - `fix:` Bug fixes
116
+ - `refactor:` Refactoring / code quality
117
+ - `test:` Test additions / updates
118
+ - `docs:` Documentation changes
119
+ - `chore:` Maintenance / dependency updates
120
+ - Ensure all tests (`tox -e py314`), linting (`tox -e lint`), and type checks (`tox -e mypy`) pass before submitting PRs.
121
+
122
+ ### Creating Issues & Feature Requests
123
+ Always follow the templates in [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/):
124
+ - **Bug Reports** ([`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml)):
125
+ - Prefix title with `[Bug]: <summary>`.
126
+ - Include: Description, Steps to Reproduce, Expected Behavior, Environment Info (Library version, Python version, OpenEVSE WiFi Firmware version), and Debug Logs / Stack Trace.
127
+ - **Feature Requests** ([`feature_request.yml`](.github/ISSUE_TEMPLATE/feature_request.yml)):
128
+ - Prefix title with `[Feature Request]: <summary>`.
129
+ - Include: Problem statement, Desired solution, Alternatives considered, and Context.
@@ -19,6 +19,7 @@ The `python-openevse-http` library requires you to pass an external `aiohttp.Cli
19
19
  import aiohttp
20
20
  from openevsehttp import OpenEVSE
21
21
 
22
+
22
23
  async def main():
23
24
  timeout = aiohttp.ClientTimeout(total=30)
24
25
  async with aiohttp.ClientSession(timeout=timeout) as session:
@@ -34,6 +35,7 @@ async def main():
34
35
  import aiohttp
35
36
  from openevsehttp import OpenEVSE
36
37
 
38
+
37
39
  async def main():
38
40
  async with aiohttp.ClientSession() as session:
39
41
  charger1 = OpenEVSE("charger1.local", session=session)
@@ -52,6 +54,7 @@ Start websocket listening from the same event loop that owns the
52
54
  import aiohttp
53
55
  from openevsehttp import OpenEVSE
54
56
 
57
+
55
58
  async def main():
56
59
  async with aiohttp.ClientSession() as session:
57
60
  charger = OpenEVSE("openevse.local", session=session)