python-openevse-http 0.3.5__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_openevse_http-0.4.1/.github/workflows/publish-to-pypi.yml +46 -0
- {python_openevse_http-0.3.5/python_openevse_http.egg-info → python_openevse_http-0.4.1}/PKG-INFO +1 -1
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/client.py +10 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/commands.py +71 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/properties.py +11 -1
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1/python_openevse_http.egg-info}/PKG-INFO +1 -1
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_client.py +23 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_commands.py +143 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_properties.py +16 -2
- python_openevse_http-0.3.5/.github/workflows/publish-to-pypi.yml +0 -35
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/dependabot.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/release-drafter.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/workflows/autolabeler.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/workflows/links.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/workflows/release-drafter.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/workflows/test.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.gitignore +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.pre-commit-config.yaml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.yamllint +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/EXTERNAL_SESSION.md +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/LICENSE +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/README.md +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/codecov.yml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/example_external_session.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/__main__.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/const.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/managers.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/sensors.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/utils.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/pyproject.toml +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/SOURCES.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/requirements.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/requirements_lint.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/requirements_test.txt +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/setup.cfg +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/setup.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/__init__.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/common.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/conftest.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/github_v2.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/github_v4.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v2_json/config.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v2_json/status.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-broken.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-dev.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-extra-version.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-new.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/schedule.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/status-broken.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/status-new.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/status.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/websocket.json +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_external_session.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_main_edge_cases.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_managers.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_mixins.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_sensors.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_shaper.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/test_websocket.py +0 -0
- {python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tox.ini +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish releases to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published, prereleased]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
tag:
|
|
9
|
+
description: 'Tag/Ref to build and publish'
|
|
10
|
+
required: false
|
|
11
|
+
default: ''
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build-and-publish:
|
|
18
|
+
name: Builds and publishes releases to PyPI
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
id-token: write
|
|
23
|
+
steps:
|
|
24
|
+
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
|
|
25
|
+
with:
|
|
26
|
+
egress-policy: audit
|
|
27
|
+
|
|
28
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
29
|
+
with:
|
|
30
|
+
ref: ${{ inputs.tag || github.ref }}
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- name: Install uv
|
|
34
|
+
uses: astral-sh/setup-uv@1edb52594c857e2b5b13128931090f0640537287 # v5.3.0
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: true
|
|
37
|
+
version: "0.10.9"
|
|
38
|
+
|
|
39
|
+
- name: Set up Python
|
|
40
|
+
run: uv python install 3.14
|
|
41
|
+
|
|
42
|
+
- name: Build package
|
|
43
|
+
run: uv build
|
|
44
|
+
|
|
45
|
+
- name: Publish release to PyPI
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
|
|
@@ -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:
|
|
@@ -414,12 +414,83 @@ class CommandsMixin:
|
|
|
414
414
|
|
|
415
415
|
if not isinstance(message, dict):
|
|
416
416
|
return None
|
|
417
|
+
|
|
418
|
+
# Match browser_download_url based on buildenv
|
|
419
|
+
download_url = None
|
|
420
|
+
buildenv = self._config.get("buildenv")
|
|
421
|
+
assets = message.get("assets", [])
|
|
422
|
+
|
|
423
|
+
if buildenv and assets:
|
|
424
|
+
target_filename = f"{buildenv}.bin"
|
|
425
|
+
for asset in assets:
|
|
426
|
+
if asset.get("name") == target_filename:
|
|
427
|
+
download_url = asset.get("browser_download_url")
|
|
428
|
+
break
|
|
429
|
+
|
|
417
430
|
return {
|
|
418
431
|
"latest_version": message.get("tag_name"),
|
|
419
432
|
"release_notes": message.get("body"),
|
|
420
433
|
"release_url": message.get("html_url"),
|
|
434
|
+
"browser_download_url": download_url,
|
|
421
435
|
}
|
|
422
436
|
|
|
437
|
+
async def update_firmware(
|
|
438
|
+
self,
|
|
439
|
+
firmware_url: str | None = None,
|
|
440
|
+
firmware_bytes: bytes | None = None,
|
|
441
|
+
filename: str = "firmware.bin",
|
|
442
|
+
) -> Mapping[str, Any] | list[Any] | str:
|
|
443
|
+
"""Instruct the device to update its firmware.
|
|
444
|
+
|
|
445
|
+
You can either:
|
|
446
|
+
1. Pass firmware_bytes to perform a multipart upload of a local file.
|
|
447
|
+
2. Pass firmware_url to tell the device to download the file directly.
|
|
448
|
+
3. Pass neither to automatically resolve the latest matching binary URL from GitHub.
|
|
449
|
+
"""
|
|
450
|
+
if not self._version_check("4.1.7"):
|
|
451
|
+
_LOGGER.debug("Feature not supported for older firmware.")
|
|
452
|
+
raise UnsupportedFeature
|
|
453
|
+
|
|
454
|
+
if firmware_bytes is not None and firmware_url is not None:
|
|
455
|
+
raise ValueError("Cannot specify both firmware_bytes and firmware_url")
|
|
456
|
+
|
|
457
|
+
if firmware_url is not None:
|
|
458
|
+
if not isinstance(firmware_url, str) or not firmware_url.strip():
|
|
459
|
+
raise ValueError("Invalid firmware_url")
|
|
460
|
+
|
|
461
|
+
url = f"{self.url}update"
|
|
462
|
+
|
|
463
|
+
# 1. Handle multipart binary upload
|
|
464
|
+
if firmware_bytes is not None:
|
|
465
|
+
form_data = aiohttp.FormData()
|
|
466
|
+
form_data.add_field(
|
|
467
|
+
name="file",
|
|
468
|
+
value=firmware_bytes,
|
|
469
|
+
filename=filename,
|
|
470
|
+
content_type="application/octet-stream",
|
|
471
|
+
)
|
|
472
|
+
_LOGGER.debug(
|
|
473
|
+
"Uploading firmware binary to %s (%d bytes)", url, len(firmware_bytes)
|
|
474
|
+
)
|
|
475
|
+
# Rapi is mapped to http request's data kwarg in process_request
|
|
476
|
+
return await self.process_request(url=url, method="post", rapi=form_data)
|
|
477
|
+
|
|
478
|
+
# 2. Resolve URL from GitHub if not specified
|
|
479
|
+
if firmware_url is None:
|
|
480
|
+
check_result = await self.firmware_check()
|
|
481
|
+
if not check_result or not check_result.get("browser_download_url"):
|
|
482
|
+
raise RuntimeError(
|
|
483
|
+
"Could not resolve latest firmware download URL from GitHub."
|
|
484
|
+
)
|
|
485
|
+
firmware_url = check_result["browser_download_url"]
|
|
486
|
+
|
|
487
|
+
# 3. Post JSON URL payload
|
|
488
|
+
data = {"url": firmware_url}
|
|
489
|
+
_LOGGER.debug(
|
|
490
|
+
"Requesting OpenEVSE to download and update from: %s", firmware_url
|
|
491
|
+
)
|
|
492
|
+
return await self.process_request(url=url, method="post", data=data)
|
|
493
|
+
|
|
423
494
|
async def set_led_brightness(self, level: int) -> None:
|
|
424
495
|
"""Set LED brightness level."""
|
|
425
496
|
if isinstance(level, bool) or not isinstance(level, int):
|
|
@@ -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:
|
|
@@ -1651,3 +1651,26 @@ async def test_update_status_non_mapping_data(caplog):
|
|
|
1651
1651
|
with caplog.at_level(logging.WARNING):
|
|
1652
1652
|
await charger._update_status("data", "not a dict", None)
|
|
1653
1653
|
assert "Received non-Mapping websocket data: not a dict" in caplog.text
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
async def test_update_status_ota():
|
|
1657
|
+
"""Test _update_status with ota websocket events."""
|
|
1658
|
+
charger = OpenEVSE(SERVER_URL)
|
|
1659
|
+
charger._status = {"ota_update": 0}
|
|
1660
|
+
|
|
1661
|
+
# 1. Started event
|
|
1662
|
+
await charger._update_status("data", {"ota": "started"}, None)
|
|
1663
|
+
assert charger.ota_update is True
|
|
1664
|
+
assert charger.ota_state == "started"
|
|
1665
|
+
|
|
1666
|
+
# 2. Progress event
|
|
1667
|
+
await charger._update_status("data", {"ota_progress": 25}, None)
|
|
1668
|
+
assert charger.ota_progress == 25
|
|
1669
|
+
|
|
1670
|
+
# 3. Completed event (verifying ota_progress is cleared even if present in the data dict)
|
|
1671
|
+
await charger._update_status(
|
|
1672
|
+
"data", {"ota": "completed", "ota_progress": 100}, None
|
|
1673
|
+
)
|
|
1674
|
+
assert charger.ota_update is False
|
|
1675
|
+
assert charger.ota_progress is None
|
|
1676
|
+
assert charger.ota_state == "completed"
|
|
@@ -1040,3 +1040,146 @@ async def test_normalize_response(test_charger):
|
|
|
1040
1040
|
assert test_charger._normalize_response({"msg": "OK"}) == {"msg": "OK"}
|
|
1041
1041
|
# Test with string
|
|
1042
1042
|
assert test_charger._normalize_response("OK") == {"msg": "OK"}
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
# ── update_firmware ──────────────────────────────────────────────────
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
async def test_update_firmware_bytes(test_charger, mock_aioclient, caplog):
|
|
1049
|
+
"""Test update_firmware with bytes upload."""
|
|
1050
|
+
test_charger._config["version"] = "4.1.7"
|
|
1051
|
+
mock_aioclient.post(
|
|
1052
|
+
"http://openevse.test.tld/update",
|
|
1053
|
+
status=200,
|
|
1054
|
+
body="OK",
|
|
1055
|
+
)
|
|
1056
|
+
with caplog.at_level(logging.DEBUG):
|
|
1057
|
+
response = await test_charger.update_firmware(firmware_bytes=b"fakebinarydata")
|
|
1058
|
+
assert response == "OK"
|
|
1059
|
+
assert (
|
|
1060
|
+
"Uploading firmware binary to http://openevse.test.tld/update (14 bytes)"
|
|
1061
|
+
in caplog.text
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
async def test_update_firmware_url(test_charger, mock_aioclient, caplog):
|
|
1066
|
+
"""Test update_firmware with a direct URL."""
|
|
1067
|
+
test_charger._config["version"] = "4.1.7"
|
|
1068
|
+
mock_aioclient.post(
|
|
1069
|
+
"http://openevse.test.tld/update",
|
|
1070
|
+
status=200,
|
|
1071
|
+
body='{"msg":"started"}',
|
|
1072
|
+
)
|
|
1073
|
+
with caplog.at_level(logging.DEBUG):
|
|
1074
|
+
response = await test_charger.update_firmware(
|
|
1075
|
+
firmware_url="http://github.com/release.bin"
|
|
1076
|
+
)
|
|
1077
|
+
assert response == {"msg": "started"}
|
|
1078
|
+
assert (
|
|
1079
|
+
"Requesting OpenEVSE to download and update from: http://github.com/release.bin"
|
|
1080
|
+
in caplog.text
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
async def test_update_firmware_auto(test_charger, mock_aioclient, caplog):
|
|
1085
|
+
"""Test update_firmware with auto-resolved URL from GitHub."""
|
|
1086
|
+
# Setup config with a buildenv and version >= 4.1.7
|
|
1087
|
+
test_charger._config = {"version": "4.1.7", "buildenv": "openevse_esp32-gateway"}
|
|
1088
|
+
|
|
1089
|
+
# Mock GitHub Releases API to return assets matching buildenv
|
|
1090
|
+
github_response = {
|
|
1091
|
+
"tag_name": "v4.1.2",
|
|
1092
|
+
"body": "release notes",
|
|
1093
|
+
"html_url": "https://github.com/OpenEVSE/releases/v4.1.2",
|
|
1094
|
+
"assets": [
|
|
1095
|
+
{
|
|
1096
|
+
"name": "openevse_esp32-gateway.bin",
|
|
1097
|
+
"browser_download_url": "https://github.com/OpenEVSE/releases/download/v4.1.2/openevse_esp32-gateway.bin",
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
"name": "other_env.bin",
|
|
1101
|
+
"browser_download_url": "https://github.com/OpenEVSE/releases/download/v4.1.2/other_env.bin",
|
|
1102
|
+
},
|
|
1103
|
+
],
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
mock_aioclient.get(
|
|
1107
|
+
"https://api.github.com/repos/OpenEVSE/ESP32_WiFi_V4.x/releases/latest",
|
|
1108
|
+
status=200,
|
|
1109
|
+
body=json.dumps(github_response),
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
mock_aioclient.post(
|
|
1113
|
+
"http://openevse.test.tld/update",
|
|
1114
|
+
status=200,
|
|
1115
|
+
body='{"msg":"started"}',
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
with caplog.at_level(logging.DEBUG):
|
|
1119
|
+
response = await test_charger.update_firmware()
|
|
1120
|
+
assert response == {"msg": "started"}
|
|
1121
|
+
assert (
|
|
1122
|
+
"Requesting OpenEVSE to download and update from: https://github.com/OpenEVSE/releases/download/v4.1.2/openevse_esp32-gateway.bin"
|
|
1123
|
+
in caplog.text
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
async def test_update_firmware_auto_missing_buildenv(test_charger, mock_aioclient):
|
|
1128
|
+
"""Test update_firmware raises RuntimeError when buildenv asset is missing."""
|
|
1129
|
+
test_charger._config = {"version": "4.1.7", "buildenv": "openevse_esp32-gateway"}
|
|
1130
|
+
|
|
1131
|
+
# Mock GitHub releases but without the matching gateway asset
|
|
1132
|
+
github_response = {
|
|
1133
|
+
"tag_name": "v4.1.2",
|
|
1134
|
+
"body": "release notes",
|
|
1135
|
+
"html_url": "https://github.com/OpenEVSE/releases/v4.1.2",
|
|
1136
|
+
"assets": [
|
|
1137
|
+
{
|
|
1138
|
+
"name": "other_env.bin",
|
|
1139
|
+
"browser_download_url": "https://github.com/OpenEVSE/releases/download/v4.1.2/other_env.bin",
|
|
1140
|
+
}
|
|
1141
|
+
],
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
mock_aioclient.get(
|
|
1145
|
+
"https://api.github.com/repos/OpenEVSE/ESP32_WiFi_V4.x/releases/latest",
|
|
1146
|
+
status=200,
|
|
1147
|
+
body=json.dumps(github_response),
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
with pytest.raises(
|
|
1151
|
+
RuntimeError,
|
|
1152
|
+
match="Could not resolve latest firmware download URL from GitHub.",
|
|
1153
|
+
):
|
|
1154
|
+
await test_charger.update_firmware()
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
async def test_update_firmware_both_provided(test_charger):
|
|
1158
|
+
"""Test update_firmware raises ValueError when both bytes and URL are provided."""
|
|
1159
|
+
test_charger._config["version"] = "4.1.7"
|
|
1160
|
+
with pytest.raises(
|
|
1161
|
+
ValueError, match="Cannot specify both firmware_bytes and firmware_url"
|
|
1162
|
+
):
|
|
1163
|
+
await test_charger.update_firmware(
|
|
1164
|
+
firmware_url="http://url", firmware_bytes=b"bytes"
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
async def test_update_firmware_url_invalid(test_charger):
|
|
1169
|
+
"""Test update_firmware raises ValueError when firmware_url is empty or invalid type."""
|
|
1170
|
+
test_charger._config["version"] = "4.1.7"
|
|
1171
|
+
with pytest.raises(ValueError, match="Invalid firmware_url"):
|
|
1172
|
+
await test_charger.update_firmware(firmware_url="")
|
|
1173
|
+
|
|
1174
|
+
with pytest.raises(ValueError, match="Invalid firmware_url"):
|
|
1175
|
+
await test_charger.update_firmware(firmware_url=" ")
|
|
1176
|
+
|
|
1177
|
+
with pytest.raises(ValueError, match="Invalid firmware_url"):
|
|
1178
|
+
await test_charger.update_firmware(firmware_url=123) # type: ignore
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
async def test_update_firmware_unsupported(test_charger):
|
|
1182
|
+
"""Test update_firmware raises UnsupportedFeature on older firmware."""
|
|
1183
|
+
test_charger._config["version"] = "4.1.2"
|
|
1184
|
+
with pytest.raises(UnsupportedFeature):
|
|
1185
|
+
await test_charger.update_firmware(firmware_url="http://url")
|
|
@@ -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
|
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: Publish releases to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published, prereleased]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build-and-publish:
|
|
13
|
-
name: Builds and publishes releases to PyPI
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
|
|
17
|
-
with:
|
|
18
|
-
egress-policy: audit
|
|
19
|
-
|
|
20
|
-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
21
|
-
- name: Set up Python 3.9
|
|
22
|
-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
|
23
|
-
with:
|
|
24
|
-
python-version: 3.9
|
|
25
|
-
- name: Install wheel
|
|
26
|
-
run: >-
|
|
27
|
-
pip install wheel
|
|
28
|
-
- name: Build
|
|
29
|
-
run: >-
|
|
30
|
-
python3 setup.py sdist bdist_wheel
|
|
31
|
-
- name: Publish release to PyPI
|
|
32
|
-
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
33
|
-
with:
|
|
34
|
-
user: __token__
|
|
35
|
-
password: ${{ secrets.PYPI_TOKEN }}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/.github/workflows/release-drafter.yml
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
|
|
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.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/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
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v2_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v2_json/status.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-dev.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config-new.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/schedule.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/status-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/tests/fixtures/v4_json/status-new.json
RENAMED
|
File without changes
|
{python_openevse_http-0.3.5 → python_openevse_http-0.4.1}/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
|