python-openevse-http 0.4.1__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.
Files changed (69) hide show
  1. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/workflows/autolabeler.yml +1 -1
  2. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/workflows/links.yml +1 -1
  3. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/workflows/publish-to-pypi.yml +3 -3
  4. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/workflows/release-drafter.yml +1 -1
  5. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/workflows/test.yml +3 -3
  6. {python_openevse_http-0.4.1/python_openevse_http.egg-info → python_openevse_http-0.4.2}/PKG-INFO +1 -1
  7. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/client.py +3 -3
  8. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/commands.py +18 -3
  9. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/pyproject.toml +1 -1
  10. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2/python_openevse_http.egg-info}/PKG-INFO +1 -1
  11. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/requirements_test.txt +1 -1
  12. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_client.py +49 -0
  13. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_commands.py +19 -0
  14. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/dependabot.yml +0 -0
  15. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.github/release-drafter.yml +0 -0
  16. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.gitignore +0 -0
  17. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.pre-commit-config.yaml +0 -0
  18. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/.yamllint +0 -0
  19. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/EXTERNAL_SESSION.md +0 -0
  20. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/LICENSE +0 -0
  21. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/README.md +0 -0
  22. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/codecov.yml +0 -0
  23. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/example_external_session.py +0 -0
  24. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/__init__.py +0 -0
  25. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/__main__.py +0 -0
  26. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/const.py +0 -0
  27. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/exceptions.py +0 -0
  28. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/managers.py +0 -0
  29. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/properties.py +0 -0
  30. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/sensors.py +0 -0
  31. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/utils.py +0 -0
  32. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/openevsehttp/websocket.py +0 -0
  33. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/SOURCES.txt +0 -0
  34. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  35. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/not-zip-safe +0 -0
  36. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/requires.txt +0 -0
  37. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/python_openevse_http.egg-info/top_level.txt +0 -0
  38. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/requirements.txt +0 -0
  39. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/requirements_lint.txt +0 -0
  40. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/setup.cfg +0 -0
  41. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/setup.py +0 -0
  42. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/__init__.py +0 -0
  43. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/common.py +0 -0
  44. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/conftest.py +0 -0
  45. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/github_v2.json +0 -0
  46. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/github_v4.json +0 -0
  47. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/config.json +0 -0
  48. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v2_json/status.json +0 -0
  49. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  50. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-broken.json +0 -0
  51. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-dev.json +0 -0
  52. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  53. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-new.json +0 -0
  54. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
  55. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/config.json +0 -0
  56. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/schedule.json +0 -0
  57. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-broken.json +0 -0
  58. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status-new.json +0 -0
  59. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/v4_json/status.json +0 -0
  60. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/fixtures/websocket.json +0 -0
  61. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_external_session.py +0 -0
  62. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_main_edge_cases.py +0 -0
  63. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_managers.py +0 -0
  64. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_mixins.py +0 -0
  65. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_properties.py +0 -0
  66. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_sensors.py +0 -0
  67. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_shaper.py +0 -0
  68. {python_openevse_http-0.4.1 → python_openevse_http-0.4.2}/tests/test_websocket.py +0 -0
  69. {python_openevse_http-0.4.1 → 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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
18
+ - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
19
19
  with:
20
20
  egress-policy: audit
21
21
 
@@ -21,7 +21,7 @@ jobs:
21
21
  contents: read
22
22
  id-token: write
23
23
  steps:
24
- - uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
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@1edb52594c857e2b5b13128931090f0640537287 # v5.3.0
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@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
46
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
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@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1
72
+ - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
73
73
  with:
74
74
  egress-policy: audit
75
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.4.1
3
+ Version: 0.4.2
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
@@ -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 = dict(response)
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(
@@ -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"
@@ -473,7 +482,11 @@ class CommandsMixin:
473
482
  "Uploading firmware binary to %s (%d bytes)", url, len(firmware_bytes)
474
483
  )
475
484
  # 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)
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
477
490
 
478
491
  # 2. Resolve URL from GitHub if not specified
479
492
  if firmware_url is None:
@@ -489,7 +502,9 @@ class CommandsMixin:
489
502
  _LOGGER.debug(
490
503
  "Requesting OpenEVSE to download and update from: %s", firmware_url
491
504
  )
492
- return await self.process_request(url=url, method="post", data=data)
505
+ response = await self.process_request(url=url, method="post", data=data)
506
+ self._flag_ota_if_started(response)
507
+ return response
493
508
 
494
509
  async def set_led_brightness(self, level: int) -> None:
495
510
  """Set LED brightness level."""
@@ -32,7 +32,7 @@ ignore = [
32
32
  max-complexity = 18
33
33
 
34
34
  [build-system]
35
- requires = ["setuptools>=61.0.0", "setuptools-scm>=8.0"]
35
+ requires = ["setuptools>=82.0.1", "setuptools-scm>=10.0.5"]
36
36
  build-backend = "setuptools.build_meta"
37
37
 
38
38
  [tool.setuptools_scm]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.4.1
3
+ Version: 0.4.2
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
@@ -5,7 +5,7 @@ pytest-cov==7.1.0
5
5
  pytest-timeout==2.4.0
6
6
  pytest-asyncio
7
7
  requests_mock
8
- aiohttp
8
+ aiohttp<3.11
9
9
  aioresponses
10
10
  tox==4.55.0
11
11
  freezegun
@@ -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
 
@@ -1060,6 +1060,7 @@ async def test_update_firmware_bytes(test_charger, mock_aioclient, caplog):
1060
1060
  "Uploading firmware binary to http://openevse.test.tld/update (14 bytes)"
1061
1061
  in caplog.text
1062
1062
  )
1063
+ assert test_charger.ota_update is True
1063
1064
 
1064
1065
 
1065
1066
  async def test_update_firmware_url(test_charger, mock_aioclient, caplog):
@@ -1079,6 +1080,7 @@ async def test_update_firmware_url(test_charger, mock_aioclient, caplog):
1079
1080
  "Requesting OpenEVSE to download and update from: http://github.com/release.bin"
1080
1081
  in caplog.text
1081
1082
  )
1083
+ assert test_charger.ota_update is True
1082
1084
 
1083
1085
 
1084
1086
  async def test_update_firmware_auto(test_charger, mock_aioclient, caplog):
@@ -1122,6 +1124,7 @@ async def test_update_firmware_auto(test_charger, mock_aioclient, caplog):
1122
1124
  "Requesting OpenEVSE to download and update from: https://github.com/OpenEVSE/releases/download/v4.1.2/openevse_esp32-gateway.bin"
1123
1125
  in caplog.text
1124
1126
  )
1127
+ assert test_charger.ota_update is True
1125
1128
 
1126
1129
 
1127
1130
  async def test_update_firmware_auto_missing_buildenv(test_charger, mock_aioclient):
@@ -1183,3 +1186,19 @@ async def test_update_firmware_unsupported(test_charger):
1183
1186
  test_charger._config["version"] = "4.1.2"
1184
1187
  with pytest.raises(UnsupportedFeature):
1185
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