python-openevse-http 0.4.4__tar.gz → 1.0.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 (72) hide show
  1. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/workflows/autolabeler.yml +2 -0
  2. python_openevse_http-1.0.0/EXTERNAL_SESSION.md +91 -0
  3. {python_openevse_http-0.4.4/python_openevse_http.egg-info → python_openevse_http-1.0.0}/PKG-INFO +13 -17
  4. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/README.md +12 -16
  5. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/example_external_session.py +0 -13
  6. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/client.py +39 -32
  7. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/commands.py +7 -7
  8. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/const.py +7 -0
  9. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/websocket.py +15 -8
  10. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0/python_openevse_http.egg-info}/PKG-INFO +13 -17
  11. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/conftest.py +140 -143
  12. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_client.py +197 -143
  13. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_commands.py +4 -1
  14. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_external_session.py +12 -26
  15. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_main_edge_cases.py +6 -3
  16. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_mixins.py +2 -0
  17. python_openevse_http-1.0.0/tests/test_properties.py +492 -0
  18. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_shaper.py +2 -1
  19. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_websocket.py +36 -24
  20. python_openevse_http-0.4.4/EXTERNAL_SESSION.md +0 -141
  21. python_openevse_http-0.4.4/tests/test_properties.py +0 -1236
  22. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/dependabot.yml +0 -0
  23. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/release-drafter.yml +0 -0
  24. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/workflows/links.yml +0 -0
  25. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/workflows/publish-to-pypi.yml +0 -0
  26. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/workflows/release-drafter.yml +0 -0
  27. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.github/workflows/test.yml +0 -0
  28. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.gitignore +0 -0
  29. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.pre-commit-config.yaml +0 -0
  30. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/.yamllint +0 -0
  31. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/LICENSE +0 -0
  32. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/codecov.yml +0 -0
  33. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/__init__.py +0 -0
  34. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/__main__.py +0 -0
  35. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/exceptions.py +0 -0
  36. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/managers.py +0 -0
  37. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/properties.py +0 -0
  38. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/py.typed +0 -0
  39. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/sensors.py +0 -0
  40. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/openevsehttp/utils.py +0 -0
  41. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/pyproject.toml +0 -0
  42. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/python_openevse_http.egg-info/SOURCES.txt +0 -0
  43. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  44. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/python_openevse_http.egg-info/not-zip-safe +0 -0
  45. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/python_openevse_http.egg-info/requires.txt +0 -0
  46. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/python_openevse_http.egg-info/top_level.txt +0 -0
  47. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/requirements.txt +0 -0
  48. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/requirements_lint.txt +0 -0
  49. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/requirements_test.txt +0 -0
  50. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/setup.cfg +0 -0
  51. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/setup.py +0 -0
  52. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/__init__.py +0 -0
  53. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/common.py +0 -0
  54. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/github_v2.json +0 -0
  55. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/github_v4.json +0 -0
  56. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v2_json/config.json +0 -0
  57. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v2_json/status.json +0 -0
  58. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  59. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-broken.json +0 -0
  60. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-dev.json +0 -0
  61. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  62. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-new.json +0 -0
  63. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
  64. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/config.json +0 -0
  65. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/schedule.json +0 -0
  66. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/status-broken.json +0 -0
  67. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/status-new.json +0 -0
  68. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/v4_json/status.json +0 -0
  69. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/fixtures/websocket.json +0 -0
  70. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_managers.py +0 -0
  71. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tests/test_sensors.py +0 -0
  72. {python_openevse_http-0.4.4 → python_openevse_http-1.0.0}/tox.ini +0 -0
@@ -6,11 +6,13 @@ on:
6
6
  - opened
7
7
  - synchronize
8
8
  - reopened
9
+ - edited
9
10
  pull_request_target:
10
11
  types:
11
12
  - opened
12
13
  - synchronize
13
14
  - reopened
15
+ - edited
14
16
 
15
17
  permissions: {}
16
18
 
@@ -0,0 +1,91 @@
1
+ # HTTP Session Management
2
+
3
+ ## Overview
4
+
5
+ The `python-openevse-http` library requires you to pass an external `aiohttp.ClientSession` to `OpenEVSE`. Session ownership stays with the caller, so the library no longer constructs temporary HTTP clients internally.
6
+
7
+ ## Benefits
8
+
9
+ - **Session Reuse**: Share a single session across multiple OpenEVSE instances or other `aiohttp` clients
10
+ - **Custom Configuration**: Configure timeouts, connectors, proxies, and SSL behavior yourself
11
+ - **Resource Management**: Keep connection pooling and cleanup in one place
12
+ - **Predictable Lifecycle**: Avoid hidden session creation inside request and websocket code paths
13
+
14
+ ## Usage
15
+
16
+ ### Basic Usage
17
+
18
+ ```python
19
+ import aiohttp
20
+ from openevsehttp import OpenEVSE
21
+
22
+ async def main():
23
+ timeout = aiohttp.ClientTimeout(total=30)
24
+ async with aiohttp.ClientSession(timeout=timeout) as session:
25
+ charger = OpenEVSE("openevse.local", session=session)
26
+ await charger.update()
27
+ print(f"Status: {charger.status}")
28
+ await charger.ws_disconnect()
29
+ ```
30
+
31
+ ### Sharing a Session
32
+
33
+ ```python
34
+ import aiohttp
35
+ from openevsehttp import OpenEVSE
36
+
37
+ async def main():
38
+ async with aiohttp.ClientSession() as session:
39
+ charger1 = OpenEVSE("charger1.local", session=session)
40
+ charger2 = OpenEVSE("charger2.local", session=session)
41
+
42
+ await charger1.update()
43
+ await charger2.update()
44
+ ```
45
+
46
+ ### Websocket Startup
47
+
48
+ Start websocket listening from the same event loop that owns the
49
+ `aiohttp.ClientSession`:
50
+
51
+ ```python
52
+ import aiohttp
53
+ from openevsehttp import OpenEVSE
54
+
55
+ async def main():
56
+ async with aiohttp.ClientSession() as session:
57
+ charger = OpenEVSE("openevse.local", session=session)
58
+ await charger.ws_start()
59
+ await charger.ws_disconnect()
60
+ ```
61
+
62
+ `ws_start()` is async so websocket tasks are created on the event loop that owns
63
+ the configured `aiohttp.ClientSession`. This prevents using a session from a
64
+ private background loop it was not created on.
65
+
66
+ ## API Notes
67
+
68
+ - `OpenEVSE(..., session=session)` uses the provided session for HTTP requests.
69
+ - `OpenEVSEWebsocket(..., session=session)` uses the provided session for websocket connections.
70
+ - If no session is configured, HTTP requests and websocket startup raise `RuntimeError`.
71
+ - Call `await charger.ws_start()` from the event loop that owns the session.
72
+ - Externally provided sessions are never closed by the library.
73
+
74
+ ## Migration
75
+
76
+ Before:
77
+
78
+ ```python
79
+ charger = OpenEVSE("openevse.local")
80
+ await charger.update()
81
+ ```
82
+
83
+ After:
84
+
85
+ ```python
86
+ import aiohttp
87
+
88
+ async with aiohttp.ClientSession() as session:
89
+ charger = OpenEVSE("openevse.local", session=session)
90
+ await charger.update()
91
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.4.4
3
+ Version: 1.0.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
@@ -62,28 +62,24 @@ pip install python_openevse_http
62
62
 
63
63
  ```python
64
64
  import asyncio
65
+ import aiohttp
65
66
  from openevsehttp import OpenEVSE
66
67
 
67
68
  async def main():
68
- # Initialize the charger
69
- charger = OpenEVSE("192.168.1.30")
69
+ async with aiohttp.ClientSession() as session:
70
+ charger = OpenEVSE("192.168.1.30", session=session)
71
+ await charger.update()
70
72
 
71
- # Update state
72
- await charger.update()
73
+ print(f"Charger State: {charger.status}")
74
+ print(f"Current Charge: {charger.charge_current}A")
73
75
 
74
- print(f"Charger State: {charger.status}")
75
- print(f"Current Charge: {charger.charge_current}A")
76
+ if charger.shaper_active:
77
+ print("Shaper is active, disabling...")
78
+ else:
79
+ print("Shaper is inactive, enabling...")
76
80
 
77
- # Toggle the Shaper feature
78
- if charger.shaper_active:
79
- print("Shaper is active, disabling...")
80
- else:
81
- print("Shaper is inactive, enabling...")
82
-
83
- await charger.toggle_shaper()
84
-
85
- # Clean up
86
- await charger.close()
81
+ await charger.toggle_shaper()
82
+ await charger.ws_disconnect()
87
83
 
88
84
  if __name__ == "__main__":
89
85
  asyncio.run(main())
@@ -29,28 +29,24 @@ pip install python_openevse_http
29
29
 
30
30
  ```python
31
31
  import asyncio
32
+ import aiohttp
32
33
  from openevsehttp import OpenEVSE
33
34
 
34
35
  async def main():
35
- # Initialize the charger
36
- charger = OpenEVSE("192.168.1.30")
36
+ async with aiohttp.ClientSession() as session:
37
+ charger = OpenEVSE("192.168.1.30", session=session)
38
+ await charger.update()
37
39
 
38
- # Update state
39
- await charger.update()
40
+ print(f"Charger State: {charger.status}")
41
+ print(f"Current Charge: {charger.charge_current}A")
40
42
 
41
- print(f"Charger State: {charger.status}")
42
- print(f"Current Charge: {charger.charge_current}A")
43
+ if charger.shaper_active:
44
+ print("Shaper is active, disabling...")
45
+ else:
46
+ print("Shaper is inactive, enabling...")
43
47
 
44
- # Toggle the Shaper feature
45
- if charger.shaper_active:
46
- print("Shaper is active, disabling...")
47
- else:
48
- print("Shaper is inactive, enabling...")
49
-
50
- await charger.toggle_shaper()
51
-
52
- # Clean up
53
- await charger.close()
48
+ await charger.toggle_shaper()
49
+ await charger.ws_disconnect()
54
50
 
55
51
  if __name__ == "__main__":
56
52
  asyncio.run(main())
@@ -31,19 +31,6 @@ async def example_with_external_session():
31
31
  await charger.ws_disconnect()
32
32
 
33
33
 
34
- async def example_without_external_session():
35
- """Demonstrate without external session (backward compatible)."""
36
- # The library will create and manage its own sessions
37
- charger = OpenEVSE("openevse.local")
38
-
39
- # Use the charger normally
40
- await charger.update()
41
- print(f"Status: {charger.status}")
42
- print(f"Current: {charger.charging_current}A")
43
-
44
- await charger.ws_disconnect()
45
-
46
-
47
34
  async def example_shared_session():
48
35
  """Demonstrate sharing a session between multiple clients."""
49
36
  async with aiohttp.ClientSession() as session:
@@ -17,6 +17,8 @@ from awesomeversion.exceptions import AwesomeVersionCompareException
17
17
 
18
18
  from .commands import CommandsMixin
19
19
  from .const import (
20
+ ERROR_SESSION_LOOP_MISMATCH,
21
+ ERROR_SESSION_REQUIRED,
20
22
  ERROR_TIMEOUT,
21
23
  UPDATE_TRIGGERS,
22
24
  )
@@ -68,7 +70,17 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
68
70
  self._owns_loop = False
69
71
  self._loop_thread: threading.Thread | None = None
70
72
  self._session = session
71
- self._session_external = session is not None
73
+
74
+ def _get_session(self) -> aiohttp.ClientSession:
75
+ """Return the configured HTTP session or fail fast."""
76
+ if self._session is None:
77
+ raise RuntimeError(ERROR_SESSION_REQUIRED)
78
+ try:
79
+ loop = asyncio.get_running_loop()
80
+ except RuntimeError:
81
+ return self._session
82
+ self._validate_session_loop(loop)
83
+ return self._session
72
84
 
73
85
  async def process_request(
74
86
  self,
@@ -86,16 +98,10 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
86
98
  if self._user and self._pwd:
87
99
  auth = aiohttp.BasicAuth(self._user, self._pwd)
88
100
 
89
- # Use provided session or create a temporary one
90
- if (session := self._session) is None:
91
- async with aiohttp.ClientSession() as session:
92
- return await self._process_request_with_session(
93
- session, url, method, data, rapi, auth
94
- )
95
- else:
96
- return await self._process_request_with_session(
97
- session, url, method, data, rapi, auth
98
- )
101
+ session = self._get_session()
102
+ return await self._process_request_with_session(
103
+ session, url, method, data, rapi, auth
104
+ )
99
105
 
100
106
  def _normalize_response(self, response: Any) -> dict[str, Any] | list[Any]:
101
107
  """Normalize response to a dict or list."""
@@ -259,36 +265,37 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
259
265
  data = {"serial": serial, "model": model}
260
266
  return data
261
267
 
262
- def ws_start(self) -> None:
268
+ async def ws_start(self) -> None:
263
269
  """Start the websocket listener."""
264
270
  if self.websocket and self.websocket.state != STATE_STOPPED:
265
271
  raise AlreadyListening
266
272
 
267
- # Detect loop mismatch
268
- use_session = self._session
269
- try:
270
- asyncio.get_running_loop()
271
- except RuntimeError:
272
- # We are about to create a private loop in _start_listening
273
- # If we have a session, it's likely bound to another loop
274
- if self._session:
275
- _LOGGER.warning(
276
- "Caller-provided session may not work on private event loop. "
277
- "Creating a loop-local session."
278
- )
279
- use_session = None
280
- # Clear self._session so subsequent await self.update() uses
281
- # a loop-local session as well.
282
- self._session = None
283
- self._session_external = False
273
+ self._get_session()
284
274
 
285
275
  if not self.websocket or self.websocket.state == STATE_STOPPED:
286
- self.websocket = OpenEVSEWebsocket(
287
- self.url, self._update_status, self._user, self._pwd, use_session
288
- )
276
+ self._create_websocket()
289
277
 
290
278
  self._start_listening()
291
279
 
280
+ def _create_websocket(self) -> None:
281
+ """Create a websocket using the configured session."""
282
+ self.websocket = OpenEVSEWebsocket(
283
+ self.url,
284
+ self._update_status,
285
+ self._user,
286
+ self._pwd,
287
+ self._session,
288
+ )
289
+
290
+ def _validate_session_loop(self, loop: asyncio.AbstractEventLoop) -> None:
291
+ """Ensure the configured session belongs to the active event loop."""
292
+ session_loop = getattr(self._session, "_loop", None)
293
+ if (
294
+ isinstance(session_loop, asyncio.AbstractEventLoop)
295
+ and session_loop is not loop
296
+ ):
297
+ raise RuntimeError(ERROR_SESSION_LOOP_MISMATCH)
298
+
292
299
  def _start_listening(self) -> None:
293
300
  """Start the websocket listener."""
294
301
  if not self._loop:
@@ -25,7 +25,7 @@ class CommandsMixin:
25
25
  url: str
26
26
  _status: dict[str, Any]
27
27
  _config: dict[str, Any]
28
- _session: Any
28
+ _session: aiohttp.ClientSession | None
29
29
 
30
30
  # These are defined in client.py
31
31
  def _version_check(self, min_version: str, max_version: str = "") -> bool:
@@ -46,6 +46,10 @@ class CommandsMixin:
46
46
  """Normalize response to a dict or list."""
47
47
  raise NotImplementedError
48
48
 
49
+ def _get_session(self) -> aiohttp.ClientSession:
50
+ """Return the configured HTTP session."""
51
+ raise NotImplementedError
52
+
49
53
  def _flag_ota_if_started(self, response: Any) -> None:
50
54
  """Flag OTA as active if response indicates firmware update has started."""
51
55
  normalized = self._normalize_response(response)
@@ -409,12 +413,8 @@ class CommandsMixin:
409
413
  return None
410
414
 
411
415
  try:
412
- if (session := self._session) is None:
413
- async with aiohttp.ClientSession() as session:
414
- return await self._firmware_check_with_session(session, url, method)
415
- else:
416
- return await self._firmware_check_with_session(session, url, method)
417
-
416
+ session = self._get_session()
417
+ return await self._firmware_check_with_session(session, url, method)
418
418
  except (TimeoutError, ServerTimeoutError):
419
419
  _LOGGER.error("%s: %s", "Timeout while updating", url)
420
420
  except ContentTypeError as err:
@@ -62,3 +62,10 @@ RAPI_ERRORS = [
62
62
  ]
63
63
 
64
64
  SUCCESS_ANSWERS = ["OK", "done", "no change", "Created", "Updated", "Deleted"]
65
+
66
+ ERROR_SESSION_REQUIRED = (
67
+ "An aiohttp.ClientSession must be provided via the session argument."
68
+ )
69
+ ERROR_SESSION_LOOP_MISMATCH = (
70
+ "The aiohttp.ClientSession is bound to a different event loop."
71
+ )
@@ -11,6 +11,11 @@ from typing import Any
11
11
 
12
12
  import aiohttp
13
13
 
14
+ from .const import (
15
+ ERROR_SESSION_LOOP_MISMATCH,
16
+ ERROR_SESSION_REQUIRED,
17
+ )
18
+
14
19
  _LOGGER = logging.getLogger(__name__)
15
20
 
16
21
  MAX_FAILED_ATTEMPTS = 5
@@ -40,7 +45,6 @@ class OpenEVSEWebsocket:
40
45
  ) -> None:
41
46
  """Initialize a OpenEVSEWebsocket instance."""
42
47
  self.session = session
43
- self._session_external = session is not None
44
48
  self.uri = self._get_uri(server)
45
49
  self._user = user
46
50
  self._password = password
@@ -224,10 +228,17 @@ class OpenEVSEWebsocket:
224
228
  self._listener_loop = None
225
229
 
226
230
  async def _ensure_session(self) -> None:
227
- """Ensure aiohttp.ClientSession exists."""
231
+ """Ensure an external aiohttp.ClientSession exists."""
228
232
  if self.session is None:
229
- self.session = aiohttp.ClientSession()
230
- self._session_external = False
233
+ raise RuntimeError(ERROR_SESSION_REQUIRED)
234
+
235
+ loop = asyncio.get_running_loop()
236
+ session_loop = getattr(self.session, "_loop", None)
237
+ if (
238
+ isinstance(session_loop, asyncio.AbstractEventLoop)
239
+ and session_loop is not loop
240
+ ):
241
+ raise RuntimeError(ERROR_SESSION_LOOP_MISMATCH)
231
242
 
232
243
  async def close(self) -> None:
233
244
  """Close the listening websocket."""
@@ -242,10 +253,6 @@ class OpenEVSEWebsocket:
242
253
  if self._client is not None:
243
254
  await self._client.close()
244
255
  self._client = None
245
- # Only close the session if we created it
246
- if not self._session_external and self.session is not None:
247
- await self.session.close()
248
- self.session = None
249
256
 
250
257
  async def keepalive(self) -> None:
251
258
  """Send ping requests to websocket."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 0.4.4
3
+ Version: 1.0.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
@@ -62,28 +62,24 @@ pip install python_openevse_http
62
62
 
63
63
  ```python
64
64
  import asyncio
65
+ import aiohttp
65
66
  from openevsehttp import OpenEVSE
66
67
 
67
68
  async def main():
68
- # Initialize the charger
69
- charger = OpenEVSE("192.168.1.30")
69
+ async with aiohttp.ClientSession() as session:
70
+ charger = OpenEVSE("192.168.1.30", session=session)
71
+ await charger.update()
70
72
 
71
- # Update state
72
- await charger.update()
73
+ print(f"Charger State: {charger.status}")
74
+ print(f"Current Charge: {charger.charge_current}A")
73
75
 
74
- print(f"Charger State: {charger.status}")
75
- print(f"Current Charge: {charger.charge_current}A")
76
+ if charger.shaper_active:
77
+ print("Shaper is active, disabling...")
78
+ else:
79
+ print("Shaper is inactive, enabling...")
76
80
 
77
- # Toggle the Shaper feature
78
- if charger.shaper_active:
79
- print("Shaper is active, disabling...")
80
- else:
81
- print("Shaper is inactive, enabling...")
82
-
83
- await charger.toggle_shaper()
84
-
85
- # Clean up
86
- await charger.close()
81
+ await charger.toggle_shaper()
82
+ await charger.ws_disconnect()
87
83
 
88
84
  if __name__ == "__main__":
89
85
  asyncio.run(main())