python-openevse-http 1.5.0__tar.gz → 1.6.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.
- python_openevse_http-1.6.0/.agents/skills/openevse-api-guide/SKILL.md +100 -0
- python_openevse_http-1.6.0/.agents/skills/openevse-dev-workflow/SKILL.md +87 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/release-drafter.yml +1 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/autolabeler.yml +2 -2
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/links.yml +3 -3
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/publish-to-pypi.yml +4 -4
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/release-drafter.yml +2 -2
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/test.yml +10 -10
- python_openevse_http-1.6.0/AGENTS.md +128 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/EXTERNAL_SESSION.md +3 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/PKG-INFO +3 -1
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/README.md +2 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/__init__.py +8 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/commands.py +50 -19
- python_openevse_http-1.6.0/openevsehttp/exceptions.py +49 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/properties.py +8 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/pyproject.toml +1 -1
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/PKG-INFO +3 -1
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/SOURCES.txt +3 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/scm_file_list.json +47 -44
- python_openevse_http-1.6.0/python_openevse_http.egg-info/scm_version.json +8 -0
- python_openevse_http-1.6.0/requirements_lint.txt +4 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/requirements_test.txt +2 -2
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_client.py +38 -3
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_commands.py +70 -22
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_managers.py +2 -1
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_properties.py +23 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_shaper.py +7 -3
- python_openevse_http-1.5.0/openevsehttp/exceptions.py +0 -33
- python_openevse_http-1.5.0/python_openevse_http.egg-info/scm_version.json +0 -8
- python_openevse_http-1.5.0/requirements_lint.txt +0 -4
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/dependabot.yml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/pull_request_template.md +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.gitignore +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.pre-commit-config.yaml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.yamllint +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/LICENSE +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/codecov.yml +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/example_external_session.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/__main__.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/client.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/const.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/managers.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/py.typed +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/sensors.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/utils.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/requirements.txt +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/setup.cfg +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/setup.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/__init__.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/common.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/conftest.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/github_v2.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/github_v4.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v2_json/config.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v2_json/status.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-broken.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-dev.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-extra-version.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-new.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/schedule.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status-broken.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status-new.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/websocket.json +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_external_session.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_main_edge_cases.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_mixins.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_sensors.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_websocket.py +0 -0
- {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tox.ini +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
| Action | HTTP Endpoint (v4+) | RAPI Command (v2/v3) | Method |
|
|
23
|
+
| :--- | :--- | :--- | :--- |
|
|
24
|
+
| Status | `/status` | N/A | GET |
|
|
25
|
+
| Config | `/config` | N/A | GET / POST |
|
|
26
|
+
| Manual Override | `/override` | `$FE` (enable) / `$FS` (sleep) | GET / POST / PATCH / DELETE |
|
|
27
|
+
| Soft Current Limit | `/override` (charge_current) | `$SC <amps> [N\|V]` | POST |
|
|
28
|
+
| Shaper Mode | `/shaper` | N/A | POST |
|
|
29
|
+
| Divert Mode | `/divertmode` or `/config` | N/A | POST |
|
|
30
|
+
| Module Restart | `/restart` (`device: gateway\|evse`) | `$FR` (evse restart) | POST |
|
|
31
|
+
| Firmware Update | `/update` | N/A | POST (multipart or JSON URL) |
|
|
32
|
+
|
|
33
|
+
## Firmware Version Branching
|
|
34
|
+
|
|
35
|
+
Always check firmware compatibility using `self._version_check(min_version)`:
|
|
36
|
+
```python
|
|
37
|
+
if self._version_check("4.0.1"):
|
|
38
|
+
# Use HTTP REST endpoint
|
|
39
|
+
response = await self.process_request(url=f"{self.url}override", method="patch")
|
|
40
|
+
else:
|
|
41
|
+
# Fallback to RAPI command for older firmware
|
|
42
|
+
response, msg = await self.send_command("$FE" if state == 254 else "$FS")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If a feature is not supported on older firmware:
|
|
46
|
+
```python
|
|
47
|
+
if not self._version_check("4.1.0"):
|
|
48
|
+
_LOGGER.debug("Feature not supported for older firmware.")
|
|
49
|
+
raise UnsupportedFeature
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Exception Handling Conventions
|
|
53
|
+
|
|
54
|
+
All custom exceptions inherit from `OpenEVSEError(Exception)`.
|
|
55
|
+
|
|
56
|
+
- **`CommandFailedError`**: Raise when a command returns an error response, fails HTTP verification, or returns `$NK` / `RAPI_ERRORS`.
|
|
57
|
+
- **`UnknownStateError`**: Raise when prior charger state or configuration is required to determine the command payload (e.g. toggling) but is missing or `None`.
|
|
58
|
+
- **`FirmwareResolutionError`**: Raise when GitHub release download URL cannot be determined from the release metadata.
|
|
59
|
+
- **`UnsupportedFeature`**: Raise when charger firmware is below the minimum supported version for a feature.
|
|
60
|
+
- **`AuthenticationError`**: Raise on 401 unauthorized.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from .exceptions import CommandFailedError, UnknownStateError, UnsupportedFeature
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Validating Endpoints Against Firmware Repositories
|
|
67
|
+
|
|
68
|
+
When adding, modifying, or debugging endpoints and RAPI commands, cross-reference against the upstream OpenEVSE firmware sources:
|
|
69
|
+
|
|
70
|
+
- **WiFi Gateway Firmware (v3/v4/v5)**: [`OpenEVSE/ESP32_WiFi_V4.x`](https://github.com/OpenEVSE/ESP32_WiFi_V4.x)
|
|
71
|
+
- **Legacy WiFi Firmware (v2)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)
|
|
72
|
+
- **OpenEVSE Controller Firmware (RAPI)**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse)
|
|
73
|
+
|
|
74
|
+
### What to Verify in Firmware Sources:
|
|
75
|
+
1. **Route & Method Handlers**:
|
|
76
|
+
- Check `src/http.cpp`, `src/web_server.cpp`, or `src/web_server.h` in `ESP32_WiFi_V4.x` to confirm HTTP methods (`GET`, `POST`, `PATCH`, `DELETE`).
|
|
77
|
+
- Confirm expected query parameters or JSON body fields (e.g. `divertmode=...`, `{"device": "gateway"}`, `{"charge_current": ...}`).
|
|
78
|
+
2. **Response Formats & Statuses**:
|
|
79
|
+
- Verify success and error response payloads (e.g., `{"msg": "done"}`, `{"result": "OK", "msg": "..."}`, or plain string messages like `"Current Shaper state changed"`).
|
|
80
|
+
- Update `SUCCESS_ANSWERS` in `openevsehttp/const.py` if new success indicators are introduced.
|
|
81
|
+
3. **Firmware Version Thresholds**:
|
|
82
|
+
- Check git history or release tags in `ESP32_WiFi_V4.x` to determine when a route or feature was introduced, ensuring accurate `_version_check("x.y.z")` values.
|
|
83
|
+
4. **RAPI Command Specifications**:
|
|
84
|
+
- 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
|
+
5. **Mock Test Fixtures**:
|
|
86
|
+
- Update or add mock JSON payloads under `tests/fixtures/v4_json/` and `tests/fixtures/v2_json/` to mirror real firmware response shapes.
|
|
87
|
+
|
|
88
|
+
## Writing Tests for Commands
|
|
89
|
+
|
|
90
|
+
When testing command methods:
|
|
91
|
+
1. Use fixtures from `tests/conftest.py` (`test_charger`, `test_charger_v2`, `test_charger_new`).
|
|
92
|
+
2. Mock responses using `mock_aioclient`:
|
|
93
|
+
```python
|
|
94
|
+
mock_aioclient.post(
|
|
95
|
+
TEST_URL_CONFIG,
|
|
96
|
+
status=200,
|
|
97
|
+
body='{"msg": "done"}',
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
3. Test success paths, failure responses (`CommandFailedError`), missing state paths (`UnknownStateError`), and older firmware version behavior (`UnsupportedFeature` / RAPI commands).
|
|
@@ -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>`).
|
|
@@ -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@
|
|
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@
|
|
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@
|
|
18
|
+
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
|
19
19
|
with:
|
|
20
20
|
egress-policy: audit
|
|
21
21
|
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
23
|
|
|
24
24
|
- name: Link Checker
|
|
25
|
-
uses: lycheeverse/lychee-action@
|
|
25
|
+
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
|
|
26
26
|
with:
|
|
27
27
|
args: --verbose --no-progress './**/*.md'
|
{python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/publish-to-pypi.yml
RENAMED
|
@@ -21,17 +21,17 @@ jobs:
|
|
|
21
21
|
contents: read
|
|
22
22
|
id-token: write
|
|
23
23
|
steps:
|
|
24
|
-
- uses: step-security/harden-runner@
|
|
24
|
+
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
|
25
25
|
with:
|
|
26
26
|
egress-policy: audit
|
|
27
27
|
|
|
28
|
-
- uses: actions/checkout@
|
|
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@
|
|
34
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
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@
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
{python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/release-drafter.yml
RENAMED
|
@@ -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@
|
|
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@
|
|
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@
|
|
21
|
+
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
|
22
22
|
with:
|
|
23
23
|
egress-policy: audit
|
|
24
24
|
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
26
26
|
- name: Set up Python
|
|
27
|
-
uses: actions/setup-python@
|
|
27
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
28
28
|
with:
|
|
29
29
|
python-version: "3.14"
|
|
30
|
-
- name: Install
|
|
31
|
-
run: pip install
|
|
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@
|
|
44
|
+
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
|
45
45
|
with:
|
|
46
46
|
egress-policy: audit
|
|
47
47
|
|
|
48
|
-
- uses: actions/checkout@
|
|
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@
|
|
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@
|
|
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@
|
|
77
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
78
78
|
with:
|
|
79
79
|
fetch-depth: 2
|
|
80
80
|
- name: Download coverage data
|
|
@@ -0,0 +1,128 @@
|
|
|
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 (v3/v4/v5)**: [`OpenEVSE/ESP32_WiFi_V4.x`](https://github.com/OpenEVSE/ESP32_WiFi_V4.x) (routes in `src/http.cpp`, `src/web_server.cpp`)
|
|
36
|
+
- **Legacy WiFi (v2)**: [`OpenEVSE/ESP8266_WiFi_v2.x`](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)
|
|
37
|
+
- **Controller / RAPI**: [`OpenEVSE/open_evse`](https://github.com/OpenEVSE/open_evse) (commands in `src/rapi.cpp`)
|
|
38
|
+
Verify HTTP methods, expected JSON fields, success/error payload shapes, and version thresholds.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Exception Handling
|
|
43
|
+
|
|
44
|
+
All custom exceptions inherit from `OpenEVSEError(Exception)`:
|
|
45
|
+
- `CommandFailedError`: Command execution failure, RAPI rejection (`$NK`), or error HTTP response.
|
|
46
|
+
- `UnknownStateError`: Required state or configuration missing before command execution (e.g. toggle state).
|
|
47
|
+
- `FirmwareResolutionError`: GitHub release asset resolution failure.
|
|
48
|
+
- `AuthenticationError`: HTTP 401 / auth failures.
|
|
49
|
+
- `UnsupportedFeature`: Feature not available for current firmware version.
|
|
50
|
+
- `ParseJSONError`, `InvalidType`, `MissingMethod`, `MissingSerial`, `AlreadyListening`.
|
|
51
|
+
|
|
52
|
+
Export all public exception classes in `openevsehttp/__init__.py`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 4. Development & Testing Workflow
|
|
57
|
+
|
|
58
|
+
### Running Tests
|
|
59
|
+
Use `tox` for isolated environments:
|
|
60
|
+
```bash
|
|
61
|
+
# Run unit tests on Python 3.14 / active environment
|
|
62
|
+
tox -e py314
|
|
63
|
+
|
|
64
|
+
# Or run pytest directly within the tox environment
|
|
65
|
+
.tox/py314/bin/pytest
|
|
66
|
+
|
|
67
|
+
# Target specific test files
|
|
68
|
+
.tox/py314/bin/pytest tests/test_commands.py -k "test_toggle_override"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Linting & Formatting
|
|
72
|
+
```bash
|
|
73
|
+
# Run ruff formatting check & linter
|
|
74
|
+
tox -e lint
|
|
75
|
+
|
|
76
|
+
# Format code automatically
|
|
77
|
+
.tox/lint/bin/ruff format ./
|
|
78
|
+
|
|
79
|
+
# Run linter with auto-fixes
|
|
80
|
+
.tox/lint/bin/ruff check --fix openevsehttp tests
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Type Checking
|
|
84
|
+
```bash
|
|
85
|
+
tox -e mypy
|
|
86
|
+
# Or directly:
|
|
87
|
+
.tox/mypy/bin/mypy openevsehttp
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 5. Testing & Mocking Guidelines
|
|
93
|
+
|
|
94
|
+
- Tests use `pytest` with `pytest-asyncio` (`asyncio_default_fixture_loop_scope = "function"`).
|
|
95
|
+
- Test fixtures in `tests/conftest.py`:
|
|
96
|
+
- `test_charger`: Standard v4 charger client with mocked endpoints.
|
|
97
|
+
- `test_charger_v2`: Legacy v2 firmware mock.
|
|
98
|
+
- `test_charger_new`: Newer v4 fixture with shaper and modern endpoints.
|
|
99
|
+
- `test_charger_auth`: Authenticated charger mock.
|
|
100
|
+
- `mock_aioclient`: `AiohttpClientMocker` instance for intercepting HTTP requests (`get`, `post`, `patch`, `delete`).
|
|
101
|
+
- Fixture data files are located in `tests/fixtures/` (`v4_json/`, `v2_json/`).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 6. Commit, Pull Request & Issue Guidelines
|
|
106
|
+
|
|
107
|
+
### Creating Pull Requests
|
|
108
|
+
- **Use the PR Template**: Always structure PR descriptions according to [`.github/pull_request_template.md`](.github/pull_request_template.md):
|
|
109
|
+
- **Description**: Provide a clear summary of changes, motivation, and link related issues (`Fixes #<number>`).
|
|
110
|
+
- **Type of change**: Check the relevant boxes (`Bug fix`, `New feature`, `Breaking change`, `Code quality / Refactoring`, `Documentation update`).
|
|
111
|
+
- **Checklist**: Complete all checklist items before opening or marking ready for review.
|
|
112
|
+
- **Semantic PR Titles**: Use conventional commit titles matching [`.github/release-drafter.yml`](.github/release-drafter.yml):
|
|
113
|
+
- `feat:` New features / enhancements
|
|
114
|
+
- `fix:` Bug fixes
|
|
115
|
+
- `refactor:` Refactoring / code quality
|
|
116
|
+
- `test:` Test additions / updates
|
|
117
|
+
- `docs:` Documentation changes
|
|
118
|
+
- `chore:` Maintenance / dependency updates
|
|
119
|
+
- Ensure all tests (`tox -e py314`), linting (`tox -e lint`), and type checks (`tox -e mypy`) pass before submitting PRs.
|
|
120
|
+
|
|
121
|
+
### Creating Issues & Feature Requests
|
|
122
|
+
Always follow the templates in [`.github/ISSUE_TEMPLATE/`](.github/ISSUE_TEMPLATE/):
|
|
123
|
+
- **Bug Reports** ([`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml)):
|
|
124
|
+
- Prefix title with `[Bug]: <summary>`.
|
|
125
|
+
- Include: Description, Steps to Reproduce, Expected Behavior, Environment Info (Library version, Python version, OpenEVSE WiFi Firmware version), and Debug Logs / Stack Trace.
|
|
126
|
+
- **Feature Requests** ([`feature_request.yml`](.github/ISSUE_TEMPLATE/feature_request.yml)):
|
|
127
|
+
- Prefix title with `[Feature Request]: <summary>`.
|
|
128
|
+
- 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)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
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
|
|
@@ -65,6 +65,7 @@ import asyncio
|
|
|
65
65
|
import aiohttp
|
|
66
66
|
from openevsehttp import OpenEVSE
|
|
67
67
|
|
|
68
|
+
|
|
68
69
|
async def main():
|
|
69
70
|
async with aiohttp.ClientSession() as session:
|
|
70
71
|
charger = OpenEVSE("192.168.1.30", session=session)
|
|
@@ -81,6 +82,7 @@ async def main():
|
|
|
81
82
|
await charger.toggle_shaper()
|
|
82
83
|
await charger.ws_disconnect()
|
|
83
84
|
|
|
85
|
+
|
|
84
86
|
if __name__ == "__main__":
|
|
85
87
|
asyncio.run(main())
|
|
86
88
|
```
|
|
@@ -32,6 +32,7 @@ import asyncio
|
|
|
32
32
|
import aiohttp
|
|
33
33
|
from openevsehttp import OpenEVSE
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
async def main():
|
|
36
37
|
async with aiohttp.ClientSession() as session:
|
|
37
38
|
charger = OpenEVSE("192.168.1.30", session=session)
|
|
@@ -48,6 +49,7 @@ async def main():
|
|
|
48
49
|
await charger.toggle_shaper()
|
|
49
50
|
await charger.ws_disconnect()
|
|
50
51
|
|
|
52
|
+
|
|
51
53
|
if __name__ == "__main__":
|
|
52
54
|
asyncio.run(main())
|
|
53
55
|
```
|
|
@@ -16,11 +16,15 @@ from .const import (
|
|
|
16
16
|
from .exceptions import (
|
|
17
17
|
AlreadyListening,
|
|
18
18
|
AuthenticationError,
|
|
19
|
+
CommandFailedError,
|
|
20
|
+
FirmwareResolutionError,
|
|
19
21
|
InvalidType,
|
|
20
22
|
MissingMethod,
|
|
21
23
|
MissingSerial,
|
|
24
|
+
OpenEVSEError,
|
|
22
25
|
ParseJSONError,
|
|
23
26
|
UnknownError,
|
|
27
|
+
UnknownStateError,
|
|
24
28
|
UnsupportedFeature,
|
|
25
29
|
)
|
|
26
30
|
from .websocket import (
|
|
@@ -43,15 +47,19 @@ __all__ = [
|
|
|
43
47
|
"UPDATE_TRIGGERS",
|
|
44
48
|
"AlreadyListening",
|
|
45
49
|
"AuthenticationError",
|
|
50
|
+
"CommandFailedError",
|
|
46
51
|
"ContentTypeError",
|
|
52
|
+
"FirmwareResolutionError",
|
|
47
53
|
"InvalidType",
|
|
48
54
|
"MissingMethod",
|
|
49
55
|
"MissingSerial",
|
|
50
56
|
"OpenEVSE",
|
|
57
|
+
"OpenEVSEError",
|
|
51
58
|
"OpenEVSEWebsocket",
|
|
52
59
|
"ParseJSONError",
|
|
53
60
|
"ServerTimeoutError",
|
|
54
61
|
"UnknownError",
|
|
62
|
+
"UnknownStateError",
|
|
55
63
|
"UnsupportedFeature",
|
|
56
64
|
"divert_mode",
|
|
57
65
|
"states",
|