python-openevse-http 0.4.0__tar.gz → 0.4.2__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-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/autolabeler.yml +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/links.yml +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/publish-to-pypi.yml +3 -3
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/release-drafter.yml +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/test.yml +3 -3
- {python_openevse_http-0.4.0/python_openevse_http.egg-info → python_openevse_http-0.4.2}/PKG-INFO +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/client.py +13 -3
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/commands.py +22 -3
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/properties.py +11 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/pyproject.toml +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2/python_openevse_http.egg-info}/PKG-INFO +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/requirements_test.txt +1 -1
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_client.py +72 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_commands.py +33 -3
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_properties.py +16 -2
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/dependabot.yml +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/release-drafter.yml +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.gitignore +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.pre-commit-config.yaml +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.yamllint +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/EXTERNAL_SESSION.md +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/LICENSE +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/README.md +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/codecov.yml +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/example_external_session.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/__main__.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/const.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/managers.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/sensors.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/utils.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/SOURCES.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/requirements.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/requirements_lint.txt +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/setup.cfg +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/setup.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/__init__.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/common.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/conftest.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/github_v2.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/github_v4.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/config.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/status.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-broken.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-dev.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-extra-version.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-new.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/schedule.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-broken.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-new.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/websocket.json +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_external_session.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_main_edge_cases.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_managers.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_mixins.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_sensors.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_shaper.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/test_websocket.py +0 -0
- {python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tox.ini +0 -0
|
@@ -32,7 +32,7 @@ jobs:
|
|
|
32
32
|
runs-on: ubuntu-latest
|
|
33
33
|
timeout-minutes: 3
|
|
34
34
|
steps:
|
|
35
|
-
- uses: step-security/harden-runner@
|
|
35
|
+
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
36
36
|
with:
|
|
37
37
|
egress-policy: audit
|
|
38
38
|
|
|
@@ -15,7 +15,7 @@ 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
19
19
|
with:
|
|
20
20
|
egress-policy: audit
|
|
21
21
|
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/publish-to-pypi.yml
RENAMED
|
@@ -21,7 +21,7 @@ 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
25
25
|
with:
|
|
26
26
|
egress-policy: audit
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
fetch-depth: 0
|
|
32
32
|
|
|
33
33
|
- name: Install uv
|
|
34
|
-
uses: astral-sh/setup-uv@
|
|
34
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.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@
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/.github/workflows/release-drafter.yml
RENAMED
|
@@ -13,7 +13,7 @@ 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
17
17
|
with:
|
|
18
18
|
egress-policy: audit
|
|
19
19
|
|
|
@@ -18,7 +18,7 @@ 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
22
22
|
with:
|
|
23
23
|
egress-policy: audit
|
|
24
24
|
|
|
@@ -41,7 +41,7 @@ jobs:
|
|
|
41
41
|
- "3.14"
|
|
42
42
|
|
|
43
43
|
steps:
|
|
44
|
-
- uses: step-security/harden-runner@
|
|
44
|
+
- uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
45
45
|
with:
|
|
46
46
|
egress-policy: audit
|
|
47
47
|
|
|
@@ -69,7 +69,7 @@ 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
|
|
73
73
|
with:
|
|
74
74
|
egress-policy: audit
|
|
75
75
|
|
|
@@ -183,12 +183,12 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
183
183
|
return (False, "")
|
|
184
184
|
return (value["cmd"], value["ret"])
|
|
185
185
|
|
|
186
|
-
async def update(self) -> None:
|
|
186
|
+
async def update(self, force_status: bool = False) -> None:
|
|
187
187
|
"""Update the values."""
|
|
188
188
|
# TODO: add addiontal endpoints to update
|
|
189
189
|
urls = [f"{self.url}config"]
|
|
190
190
|
|
|
191
|
-
if not self._ws_listening:
|
|
191
|
+
if not self._ws_listening or force_status or self.ota_update:
|
|
192
192
|
urls = [f"{self.url}status", f"{self.url}config"]
|
|
193
193
|
|
|
194
194
|
for url in urls:
|
|
@@ -196,7 +196,7 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
196
196
|
response = await self.process_request(url, method="get")
|
|
197
197
|
if "/status" in url:
|
|
198
198
|
if isinstance(response, Mapping) and "error" not in response:
|
|
199
|
-
self._status
|
|
199
|
+
self._status.update(dict(response))
|
|
200
200
|
_LOGGER.debug("Status update: %s", self._status)
|
|
201
201
|
elif isinstance(response, Mapping):
|
|
202
202
|
_LOGGER.warning(
|
|
@@ -337,6 +337,16 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
337
337
|
# TODO: update specific endpoints based on _version prefix
|
|
338
338
|
if any(key in keys for key in UPDATE_TRIGGERS):
|
|
339
339
|
await self.update()
|
|
340
|
+
|
|
341
|
+
if "ota" in keys:
|
|
342
|
+
ota_val = data["ota"]
|
|
343
|
+
if ota_val == "started":
|
|
344
|
+
self._status["ota_update"] = 1
|
|
345
|
+
elif ota_val in ("completed", "failed"):
|
|
346
|
+
self._status["ota_update"] = 0
|
|
347
|
+
data.pop("ota_progress", None)
|
|
348
|
+
self._status.pop("ota_progress", None)
|
|
349
|
+
|
|
340
350
|
self._status.update(data)
|
|
341
351
|
|
|
342
352
|
if self.callback is not None:
|
|
@@ -39,13 +39,22 @@ class CommandsMixin:
|
|
|
39
39
|
async def send_command(self, command: str) -> tuple:
|
|
40
40
|
raise NotImplementedError
|
|
41
41
|
|
|
42
|
-
async def update(self) -> None:
|
|
42
|
+
async def update(self, force_status: bool = False) -> None:
|
|
43
43
|
raise NotImplementedError
|
|
44
44
|
|
|
45
45
|
def _normalize_response(self, response: Any) -> dict[str, Any] | list[Any]:
|
|
46
46
|
"""Normalize response to a dict or list."""
|
|
47
47
|
raise NotImplementedError
|
|
48
48
|
|
|
49
|
+
def _flag_ota_if_started(self, response: Any) -> None:
|
|
50
|
+
"""Flag OTA as active if response indicates firmware update has started."""
|
|
51
|
+
normalized = self._normalize_response(response)
|
|
52
|
+
if isinstance(normalized, dict) and (
|
|
53
|
+
normalized.get("msg") == "started"
|
|
54
|
+
or normalized.get("msg") in SUCCESS_ANSWERS
|
|
55
|
+
):
|
|
56
|
+
self._status["ota_update"] = 1
|
|
57
|
+
|
|
49
58
|
async def get_schedule(self) -> Mapping[str, Any] | list[Any]:
|
|
50
59
|
"""Return the current schedule."""
|
|
51
60
|
url = f"{self.url}schedule"
|
|
@@ -447,6 +456,10 @@ class CommandsMixin:
|
|
|
447
456
|
2. Pass firmware_url to tell the device to download the file directly.
|
|
448
457
|
3. Pass neither to automatically resolve the latest matching binary URL from GitHub.
|
|
449
458
|
"""
|
|
459
|
+
if not self._version_check("4.1.7"):
|
|
460
|
+
_LOGGER.debug("Feature not supported for older firmware.")
|
|
461
|
+
raise UnsupportedFeature
|
|
462
|
+
|
|
450
463
|
if firmware_bytes is not None and firmware_url is not None:
|
|
451
464
|
raise ValueError("Cannot specify both firmware_bytes and firmware_url")
|
|
452
465
|
|
|
@@ -469,7 +482,11 @@ class CommandsMixin:
|
|
|
469
482
|
"Uploading firmware binary to %s (%d bytes)", url, len(firmware_bytes)
|
|
470
483
|
)
|
|
471
484
|
# Rapi is mapped to http request's data kwarg in process_request
|
|
472
|
-
|
|
485
|
+
response = await self.process_request(
|
|
486
|
+
url=url, method="post", rapi=form_data
|
|
487
|
+
)
|
|
488
|
+
self._flag_ota_if_started(response)
|
|
489
|
+
return response
|
|
473
490
|
|
|
474
491
|
# 2. Resolve URL from GitHub if not specified
|
|
475
492
|
if firmware_url is None:
|
|
@@ -485,7 +502,9 @@ class CommandsMixin:
|
|
|
485
502
|
_LOGGER.debug(
|
|
486
503
|
"Requesting OpenEVSE to download and update from: %s", firmware_url
|
|
487
504
|
)
|
|
488
|
-
|
|
505
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
506
|
+
self._flag_ota_if_started(response)
|
|
507
|
+
return response
|
|
489
508
|
|
|
490
509
|
async def set_led_brightness(self, level: int) -> None:
|
|
491
510
|
"""Set LED brightness level."""
|
|
@@ -341,7 +341,17 @@ class PropertiesMixin:
|
|
|
341
341
|
@property
|
|
342
342
|
def ota_update(self) -> bool:
|
|
343
343
|
"""Return if an OTA update is active."""
|
|
344
|
-
return self._status.get("ota_update", False)
|
|
344
|
+
return bool(self._status.get("ota_update", False))
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def ota_progress(self) -> int | None:
|
|
348
|
+
"""Return the progress of the current OTA update."""
|
|
349
|
+
return self._status.get("ota_progress")
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def ota_state(self) -> str | None:
|
|
353
|
+
"""Return the state of the current OTA update."""
|
|
354
|
+
return self._status.get("ota")
|
|
345
355
|
|
|
346
356
|
@property
|
|
347
357
|
def manual_override(self) -> bool:
|
|
@@ -117,6 +117,55 @@ async def test_get_status_auth_err(test_charger_auth_err):
|
|
|
117
117
|
await test_charger_auth_err.update()
|
|
118
118
|
|
|
119
119
|
|
|
120
|
+
async def test_update_force_status(mock_aioclient):
|
|
121
|
+
"""Test force_status parameter when ws is listening."""
|
|
122
|
+
unique_host = "force-status.test.tld"
|
|
123
|
+
charger = OpenEVSE(unique_host)
|
|
124
|
+
url_status = f"http://{unique_host}/status"
|
|
125
|
+
url_config = f"http://{unique_host}/config"
|
|
126
|
+
|
|
127
|
+
mock_aioclient.get(
|
|
128
|
+
url_status,
|
|
129
|
+
status=200,
|
|
130
|
+
body='{"state": 2, "wifi_serial": "123"}',
|
|
131
|
+
)
|
|
132
|
+
mock_aioclient.get(
|
|
133
|
+
url_config,
|
|
134
|
+
status=200,
|
|
135
|
+
body='{"wifi_serial": "123", "version": "4.0.1"}',
|
|
136
|
+
)
|
|
137
|
+
charger._ws_listening = True
|
|
138
|
+
charger._status = {"transient_key": "preserved"}
|
|
139
|
+
await charger.update(force_status=True)
|
|
140
|
+
assert charger._status["state"] == 2
|
|
141
|
+
assert charger._status["transient_key"] == "preserved"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def test_update_ota_active(mock_aioclient):
|
|
145
|
+
"""Test automatic status polling when ota_update is active."""
|
|
146
|
+
unique_host = "ota-active.test.tld"
|
|
147
|
+
charger = OpenEVSE(unique_host)
|
|
148
|
+
url_status = f"http://{unique_host}/status"
|
|
149
|
+
url_config = f"http://{unique_host}/config"
|
|
150
|
+
|
|
151
|
+
mock_aioclient.get(
|
|
152
|
+
url_status,
|
|
153
|
+
status=200,
|
|
154
|
+
body='{"state": 2, "ota_update": 1}',
|
|
155
|
+
)
|
|
156
|
+
mock_aioclient.get(
|
|
157
|
+
url_config,
|
|
158
|
+
status=200,
|
|
159
|
+
body='{"wifi_serial": "123", "version": "4.0.1"}',
|
|
160
|
+
)
|
|
161
|
+
charger._ws_listening = True
|
|
162
|
+
charger._status = {"ota_update": 1, "ota_progress": 50}
|
|
163
|
+
await charger.update()
|
|
164
|
+
assert charger._status["state"] == 2
|
|
165
|
+
assert charger._status["ota_progress"] == 50
|
|
166
|
+
assert charger.ota_update is True
|
|
167
|
+
|
|
168
|
+
|
|
120
169
|
# ── send_command ──────────────────────────────────────────────────────
|
|
121
170
|
|
|
122
171
|
|
|
@@ -1651,3 +1700,26 @@ async def test_update_status_non_mapping_data(caplog):
|
|
|
1651
1700
|
with caplog.at_level(logging.WARNING):
|
|
1652
1701
|
await charger._update_status("data", "not a dict", None)
|
|
1653
1702
|
assert "Received non-Mapping websocket data: not a dict" in caplog.text
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
async def test_update_status_ota():
|
|
1706
|
+
"""Test _update_status with ota websocket events."""
|
|
1707
|
+
charger = OpenEVSE(SERVER_URL)
|
|
1708
|
+
charger._status = {"ota_update": 0}
|
|
1709
|
+
|
|
1710
|
+
# 1. Started event
|
|
1711
|
+
await charger._update_status("data", {"ota": "started"}, None)
|
|
1712
|
+
assert charger.ota_update is True
|
|
1713
|
+
assert charger.ota_state == "started"
|
|
1714
|
+
|
|
1715
|
+
# 2. Progress event
|
|
1716
|
+
await charger._update_status("data", {"ota_progress": 25}, None)
|
|
1717
|
+
assert charger.ota_progress == 25
|
|
1718
|
+
|
|
1719
|
+
# 3. Completed event (verifying ota_progress is cleared even if present in the data dict)
|
|
1720
|
+
await charger._update_status(
|
|
1721
|
+
"data", {"ota": "completed", "ota_progress": 100}, None
|
|
1722
|
+
)
|
|
1723
|
+
assert charger.ota_update is False
|
|
1724
|
+
assert charger.ota_progress is None
|
|
1725
|
+
assert charger.ota_state == "completed"
|
|
@@ -1047,6 +1047,7 @@ async def test_normalize_response(test_charger):
|
|
|
1047
1047
|
|
|
1048
1048
|
async def test_update_firmware_bytes(test_charger, mock_aioclient, caplog):
|
|
1049
1049
|
"""Test update_firmware with bytes upload."""
|
|
1050
|
+
test_charger._config["version"] = "4.1.7"
|
|
1050
1051
|
mock_aioclient.post(
|
|
1051
1052
|
"http://openevse.test.tld/update",
|
|
1052
1053
|
status=200,
|
|
@@ -1059,10 +1060,12 @@ async def test_update_firmware_bytes(test_charger, mock_aioclient, caplog):
|
|
|
1059
1060
|
"Uploading firmware binary to http://openevse.test.tld/update (14 bytes)"
|
|
1060
1061
|
in caplog.text
|
|
1061
1062
|
)
|
|
1063
|
+
assert test_charger.ota_update is True
|
|
1062
1064
|
|
|
1063
1065
|
|
|
1064
1066
|
async def test_update_firmware_url(test_charger, mock_aioclient, caplog):
|
|
1065
1067
|
"""Test update_firmware with a direct URL."""
|
|
1068
|
+
test_charger._config["version"] = "4.1.7"
|
|
1066
1069
|
mock_aioclient.post(
|
|
1067
1070
|
"http://openevse.test.tld/update",
|
|
1068
1071
|
status=200,
|
|
@@ -1077,12 +1080,13 @@ async def test_update_firmware_url(test_charger, mock_aioclient, caplog):
|
|
|
1077
1080
|
"Requesting OpenEVSE to download and update from: http://github.com/release.bin"
|
|
1078
1081
|
in caplog.text
|
|
1079
1082
|
)
|
|
1083
|
+
assert test_charger.ota_update is True
|
|
1080
1084
|
|
|
1081
1085
|
|
|
1082
1086
|
async def test_update_firmware_auto(test_charger, mock_aioclient, caplog):
|
|
1083
1087
|
"""Test update_firmware with auto-resolved URL from GitHub."""
|
|
1084
|
-
# Setup config with a buildenv
|
|
1085
|
-
test_charger._config = {"version": "4.
|
|
1088
|
+
# Setup config with a buildenv and version >= 4.1.7
|
|
1089
|
+
test_charger._config = {"version": "4.1.7", "buildenv": "openevse_esp32-gateway"}
|
|
1086
1090
|
|
|
1087
1091
|
# Mock GitHub Releases API to return assets matching buildenv
|
|
1088
1092
|
github_response = {
|
|
@@ -1120,11 +1124,12 @@ async def test_update_firmware_auto(test_charger, mock_aioclient, caplog):
|
|
|
1120
1124
|
"Requesting OpenEVSE to download and update from: https://github.com/OpenEVSE/releases/download/v4.1.2/openevse_esp32-gateway.bin"
|
|
1121
1125
|
in caplog.text
|
|
1122
1126
|
)
|
|
1127
|
+
assert test_charger.ota_update is True
|
|
1123
1128
|
|
|
1124
1129
|
|
|
1125
1130
|
async def test_update_firmware_auto_missing_buildenv(test_charger, mock_aioclient):
|
|
1126
1131
|
"""Test update_firmware raises RuntimeError when buildenv asset is missing."""
|
|
1127
|
-
test_charger._config = {"version": "4.
|
|
1132
|
+
test_charger._config = {"version": "4.1.7", "buildenv": "openevse_esp32-gateway"}
|
|
1128
1133
|
|
|
1129
1134
|
# Mock GitHub releases but without the matching gateway asset
|
|
1130
1135
|
github_response = {
|
|
@@ -1154,6 +1159,7 @@ async def test_update_firmware_auto_missing_buildenv(test_charger, mock_aioclien
|
|
|
1154
1159
|
|
|
1155
1160
|
async def test_update_firmware_both_provided(test_charger):
|
|
1156
1161
|
"""Test update_firmware raises ValueError when both bytes and URL are provided."""
|
|
1162
|
+
test_charger._config["version"] = "4.1.7"
|
|
1157
1163
|
with pytest.raises(
|
|
1158
1164
|
ValueError, match="Cannot specify both firmware_bytes and firmware_url"
|
|
1159
1165
|
):
|
|
@@ -1164,6 +1170,7 @@ async def test_update_firmware_both_provided(test_charger):
|
|
|
1164
1170
|
|
|
1165
1171
|
async def test_update_firmware_url_invalid(test_charger):
|
|
1166
1172
|
"""Test update_firmware raises ValueError when firmware_url is empty or invalid type."""
|
|
1173
|
+
test_charger._config["version"] = "4.1.7"
|
|
1167
1174
|
with pytest.raises(ValueError, match="Invalid firmware_url"):
|
|
1168
1175
|
await test_charger.update_firmware(firmware_url="")
|
|
1169
1176
|
|
|
@@ -1172,3 +1179,26 @@ async def test_update_firmware_url_invalid(test_charger):
|
|
|
1172
1179
|
|
|
1173
1180
|
with pytest.raises(ValueError, match="Invalid firmware_url"):
|
|
1174
1181
|
await test_charger.update_firmware(firmware_url=123) # type: ignore
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
async def test_update_firmware_unsupported(test_charger):
|
|
1185
|
+
"""Test update_firmware raises UnsupportedFeature on older firmware."""
|
|
1186
|
+
test_charger._config["version"] = "4.1.2"
|
|
1187
|
+
with pytest.raises(UnsupportedFeature):
|
|
1188
|
+
await test_charger.update_firmware(firmware_url="http://url")
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
async def test_update_firmware_error_response(test_charger, mock_aioclient):
|
|
1192
|
+
"""Test update_firmware doesn't set ota_update on error responses."""
|
|
1193
|
+
test_charger._config["version"] = "4.1.7"
|
|
1194
|
+
test_charger._status = {}
|
|
1195
|
+
mock_aioclient.post(
|
|
1196
|
+
"http://openevse.test.tld/update",
|
|
1197
|
+
status=200,
|
|
1198
|
+
body='{"msg":"error"}',
|
|
1199
|
+
)
|
|
1200
|
+
response = await test_charger.update_firmware(
|
|
1201
|
+
firmware_url="http://github.com/release.bin"
|
|
1202
|
+
)
|
|
1203
|
+
assert response == {"msg": "error"}
|
|
1204
|
+
assert test_charger.ota_update is False
|
|
@@ -1040,17 +1040,31 @@ async def test_get_has_limit(fixture, expected, request):
|
|
|
1040
1040
|
|
|
1041
1041
|
|
|
1042
1042
|
@pytest.mark.parametrize(
|
|
1043
|
-
"fixture, expected", [("test_charger",
|
|
1043
|
+
"fixture, expected", [("test_charger", False), ("test_charger_v2", False)]
|
|
1044
1044
|
)
|
|
1045
1045
|
async def test_get_ota_update(fixture, expected, request):
|
|
1046
1046
|
"""Test ota_update property."""
|
|
1047
1047
|
charger = request.getfixturevalue(fixture)
|
|
1048
1048
|
await charger.update()
|
|
1049
1049
|
status = charger.ota_update
|
|
1050
|
-
assert status
|
|
1050
|
+
assert status is expected
|
|
1051
1051
|
await charger.ws_disconnect()
|
|
1052
1052
|
|
|
1053
1053
|
|
|
1054
|
+
async def test_ota_properties():
|
|
1055
|
+
"""Test ota_progress and ota_state properties."""
|
|
1056
|
+
charger = OpenEVSE(SERVER_URL)
|
|
1057
|
+
charger._status = {"ota_update": 1, "ota_progress": 45, "ota": "started"}
|
|
1058
|
+
assert charger.ota_update is True
|
|
1059
|
+
assert charger.ota_progress == 45
|
|
1060
|
+
assert charger.ota_state == "started"
|
|
1061
|
+
|
|
1062
|
+
charger._status = {"ota_update": 0}
|
|
1063
|
+
assert charger.ota_update is False
|
|
1064
|
+
assert charger.ota_progress is None
|
|
1065
|
+
assert charger.ota_state is None
|
|
1066
|
+
|
|
1067
|
+
|
|
1054
1068
|
# ── MQTT ────────────────────────────────────────────────────────────
|
|
1055
1069
|
|
|
1056
1070
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/status.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-dev.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-new.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/schedule.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-new.json
RENAMED
|
File without changes
|
{python_openevse_http-0.4.0 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|