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.
Files changed (82) hide show
  1. python_openevse_http-1.6.0/.agents/skills/openevse-api-guide/SKILL.md +100 -0
  2. python_openevse_http-1.6.0/.agents/skills/openevse-dev-workflow/SKILL.md +87 -0
  3. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/release-drafter.yml +1 -0
  4. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/autolabeler.yml +2 -2
  5. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/links.yml +3 -3
  6. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/publish-to-pypi.yml +4 -4
  7. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/release-drafter.yml +2 -2
  8. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/workflows/test.yml +10 -10
  9. python_openevse_http-1.6.0/AGENTS.md +128 -0
  10. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/EXTERNAL_SESSION.md +3 -0
  11. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/PKG-INFO +3 -1
  12. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/README.md +2 -0
  13. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/__init__.py +8 -0
  14. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/commands.py +50 -19
  15. python_openevse_http-1.6.0/openevsehttp/exceptions.py +49 -0
  16. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/properties.py +8 -0
  17. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/pyproject.toml +1 -1
  18. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/PKG-INFO +3 -1
  19. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/SOURCES.txt +3 -0
  20. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/scm_file_list.json +47 -44
  21. python_openevse_http-1.6.0/python_openevse_http.egg-info/scm_version.json +8 -0
  22. python_openevse_http-1.6.0/requirements_lint.txt +4 -0
  23. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/requirements_test.txt +2 -2
  24. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_client.py +38 -3
  25. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_commands.py +70 -22
  26. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_managers.py +2 -1
  27. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_properties.py +23 -0
  28. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_shaper.py +7 -3
  29. python_openevse_http-1.5.0/openevsehttp/exceptions.py +0 -33
  30. python_openevse_http-1.5.0/python_openevse_http.egg-info/scm_version.json +0 -8
  31. python_openevse_http-1.5.0/requirements_lint.txt +0 -4
  32. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  33. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  34. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  35. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/dependabot.yml +0 -0
  36. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.github/pull_request_template.md +0 -0
  37. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.gitignore +0 -0
  38. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.pre-commit-config.yaml +0 -0
  39. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/.yamllint +0 -0
  40. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/LICENSE +0 -0
  41. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/codecov.yml +0 -0
  42. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/example_external_session.py +0 -0
  43. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/__main__.py +0 -0
  44. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/client.py +0 -0
  45. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/const.py +0 -0
  46. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/managers.py +0 -0
  47. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/py.typed +0 -0
  48. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/sensors.py +0 -0
  49. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/utils.py +0 -0
  50. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/openevsehttp/websocket.py +0 -0
  51. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  52. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/not-zip-safe +0 -0
  53. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/requires.txt +0 -0
  54. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/python_openevse_http.egg-info/top_level.txt +0 -0
  55. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/requirements.txt +0 -0
  56. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/setup.cfg +0 -0
  57. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/setup.py +0 -0
  58. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/__init__.py +0 -0
  59. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/common.py +0 -0
  60. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/conftest.py +0 -0
  61. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/github_v2.json +0 -0
  62. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/github_v4.json +0 -0
  63. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v2_json/config.json +0 -0
  64. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v2_json/status.json +0 -0
  65. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  66. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-broken.json +0 -0
  67. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-dev.json +0 -0
  68. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  69. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-new.json +0 -0
  70. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
  71. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/config.json +0 -0
  72. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/schedule.json +0 -0
  73. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status-broken.json +0 -0
  74. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status-new.json +0 -0
  75. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/v4_json/status.json +0 -0
  76. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/fixtures/websocket.json +0 -0
  77. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_external_session.py +0 -0
  78. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_main_edge_cases.py +0 -0
  79. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_mixins.py +0 -0
  80. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_sensors.py +0 -0
  81. {python_openevse_http-1.5.0 → python_openevse_http-1.6.0}/tests/test_websocket.py +0 -0
  82. {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>`).
@@ -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@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@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
@@ -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.5.0
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",