python-openevse-http 0.2.5__tar.gz → 0.2.6__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.2.5/python_openevse_http.egg-info → python_openevse_http-0.2.6}/PKG-INFO +3 -5
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/openevsehttp/__main__.py +118 -172
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/openevsehttp/websocket.py +1 -1
- python_openevse_http-0.2.6/pyproject.toml +32 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6/python_openevse_http.egg-info}/PKG-INFO +3 -5
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/SOURCES.txt +1 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/setup.py +3 -5
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/tests/test_main.py +21 -3
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/tests/test_websocket.py +0 -1
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/LICENSE +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/README.md +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/openevsehttp/const.py +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/setup.cfg +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/tests/test_external_session.py +0 -0
- {python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/tests/test_main_edge_cases.py +0 -0
{python_openevse_http-0.2.5/python_openevse_http.egg-info → python_openevse_http-0.2.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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
|
|
@@ -11,12 +11,10 @@ Classifier: Development Status :: 4 - Beta
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
16
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.13
|
|
20
18
|
Description-Content-Type: text/markdown
|
|
21
19
|
License-File: LICENSE
|
|
22
20
|
Requires-Dist: aiohttp
|
|
@@ -6,8 +6,9 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import re
|
|
9
|
+
from collections.abc import Callable
|
|
9
10
|
from datetime import datetime, timedelta, timezone
|
|
10
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
import aiohttp # type: ignore
|
|
13
14
|
from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError
|
|
@@ -122,112 +123,79 @@ class OpenEVSE:
|
|
|
122
123
|
auth = aiohttp.BasicAuth(self._user, self._pwd)
|
|
123
124
|
|
|
124
125
|
# Use provided session or create a temporary one
|
|
125
|
-
if self._session is
|
|
126
|
-
session = self._session
|
|
127
|
-
http_method = getattr(session, method)
|
|
128
|
-
_LOGGER.debug(
|
|
129
|
-
"Connecting to %s with data: %s rapi: %s using method %s",
|
|
130
|
-
url,
|
|
131
|
-
data,
|
|
132
|
-
rapi,
|
|
133
|
-
method,
|
|
134
|
-
)
|
|
135
|
-
try:
|
|
136
|
-
async with http_method(
|
|
137
|
-
url,
|
|
138
|
-
data=rapi,
|
|
139
|
-
json=data,
|
|
140
|
-
auth=auth,
|
|
141
|
-
) as resp:
|
|
142
|
-
try:
|
|
143
|
-
message = await resp.text()
|
|
144
|
-
except UnicodeDecodeError:
|
|
145
|
-
_LOGGER.debug("Decoding error")
|
|
146
|
-
message = await resp.read()
|
|
147
|
-
message = message.decode(errors="replace")
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
message = json.loads(message)
|
|
151
|
-
except ValueError:
|
|
152
|
-
_LOGGER.warning("Non JSON response: %s", message)
|
|
153
|
-
|
|
154
|
-
if resp.status == 400:
|
|
155
|
-
index = ""
|
|
156
|
-
if "msg" in message.keys():
|
|
157
|
-
index = "msg"
|
|
158
|
-
elif "error" in message.keys():
|
|
159
|
-
index = "error"
|
|
160
|
-
_LOGGER.error("Error 400: %s", message[index])
|
|
161
|
-
raise ParseJSONError
|
|
162
|
-
if resp.status == 401:
|
|
163
|
-
_LOGGER.error("Authentication error: %s", message)
|
|
164
|
-
raise AuthenticationError
|
|
165
|
-
if resp.status in [404, 405, 500]:
|
|
166
|
-
_LOGGER.warning("%s", message)
|
|
167
|
-
|
|
168
|
-
if method == "post" and "config_version" in message:
|
|
169
|
-
await self.update()
|
|
170
|
-
return message
|
|
171
|
-
|
|
172
|
-
except (TimeoutError, ServerTimeoutError) as err:
|
|
173
|
-
_LOGGER.error("%s: %s", ERROR_TIMEOUT, url)
|
|
174
|
-
raise err
|
|
175
|
-
except ContentTypeError as err:
|
|
176
|
-
_LOGGER.error("Content error: %s", err.message)
|
|
177
|
-
raise err
|
|
178
|
-
else:
|
|
126
|
+
if (session := self._session) is None:
|
|
179
127
|
async with aiohttp.ClientSession() as session:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"Connecting to %s with data: %s rapi: %s using method %s",
|
|
183
|
-
url,
|
|
184
|
-
data,
|
|
185
|
-
rapi,
|
|
186
|
-
method,
|
|
128
|
+
return await self._process_request_with_session(
|
|
129
|
+
session, url, method, data, rapi, auth
|
|
187
130
|
)
|
|
131
|
+
else:
|
|
132
|
+
return await self._process_request_with_session(
|
|
133
|
+
session, url, method, data, rapi, auth
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
async def _process_request_with_session(
|
|
137
|
+
self,
|
|
138
|
+
session: aiohttp.ClientSession,
|
|
139
|
+
url: str,
|
|
140
|
+
method: str,
|
|
141
|
+
data: Any,
|
|
142
|
+
rapi: Any,
|
|
143
|
+
auth: Any,
|
|
144
|
+
) -> dict[str, str] | dict[str, Any]:
|
|
145
|
+
"""Process a request with a given session."""
|
|
146
|
+
http_method = getattr(session, method)
|
|
147
|
+
_LOGGER.debug(
|
|
148
|
+
"Connecting to %s with data: %s rapi: %s using method %s",
|
|
149
|
+
url,
|
|
150
|
+
data,
|
|
151
|
+
rapi,
|
|
152
|
+
method,
|
|
153
|
+
)
|
|
154
|
+
try:
|
|
155
|
+
kwargs = {"data": rapi, "auth": auth}
|
|
156
|
+
if data is not None:
|
|
157
|
+
kwargs["json"] = data
|
|
158
|
+
async with http_method(url, **kwargs) as resp:
|
|
188
159
|
try:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
except ContentTypeError as err:
|
|
229
|
-
_LOGGER.error("Content error: %s", err.message)
|
|
230
|
-
raise err
|
|
160
|
+
message = await resp.text()
|
|
161
|
+
except UnicodeDecodeError:
|
|
162
|
+
_LOGGER.debug("Decoding error")
|
|
163
|
+
message = await resp.read()
|
|
164
|
+
message = message.decode(errors="replace")
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
message = json.loads(message)
|
|
168
|
+
except ValueError:
|
|
169
|
+
_LOGGER.warning("Non JSON response: %s", message)
|
|
170
|
+
|
|
171
|
+
if resp.status == 400:
|
|
172
|
+
if isinstance(message, dict) and "msg" in message:
|
|
173
|
+
_LOGGER.error("Error 400: %s", message["msg"])
|
|
174
|
+
elif isinstance(message, dict) and "error" in message:
|
|
175
|
+
_LOGGER.error("Error 400: %s", message["error"])
|
|
176
|
+
else:
|
|
177
|
+
_LOGGER.error("Error 400: %s", message)
|
|
178
|
+
raise ParseJSONError
|
|
179
|
+
if resp.status == 401:
|
|
180
|
+
_LOGGER.error("Authentication error: %s", message)
|
|
181
|
+
raise AuthenticationError
|
|
182
|
+
if resp.status in [404, 405, 500]:
|
|
183
|
+
_LOGGER.warning("%s", message)
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
method == "post"
|
|
187
|
+
and isinstance(message, dict)
|
|
188
|
+
and "config_version" in message
|
|
189
|
+
):
|
|
190
|
+
await self.update()
|
|
191
|
+
return message
|
|
192
|
+
|
|
193
|
+
except (TimeoutError, ServerTimeoutError):
|
|
194
|
+
_LOGGER.error("%s: %s", ERROR_TIMEOUT, url)
|
|
195
|
+
raise
|
|
196
|
+
except ContentTypeError as err:
|
|
197
|
+
_LOGGER.error("Content error: %s", err.message)
|
|
198
|
+
raise
|
|
231
199
|
|
|
232
200
|
async def send_command(self, command: str) -> tuple:
|
|
233
201
|
"""Send a RAPI command to the charger and parses the response."""
|
|
@@ -387,7 +355,7 @@ class OpenEVSE:
|
|
|
387
355
|
await asyncio.sleep(interval)
|
|
388
356
|
await func(*args, **kwargs)
|
|
389
357
|
|
|
390
|
-
async def get_schedule(self) ->
|
|
358
|
+
async def get_schedule(self) -> dict[str, str] | dict[str, Any]:
|
|
391
359
|
"""Return the current schedule."""
|
|
392
360
|
url = f"{self.url}schedule"
|
|
393
361
|
|
|
@@ -406,9 +374,7 @@ class OpenEVSE:
|
|
|
406
374
|
data = {"charge_mode": mode}
|
|
407
375
|
|
|
408
376
|
_LOGGER.debug("Setting charge mode to %s", mode)
|
|
409
|
-
response = await self.process_request(
|
|
410
|
-
url=url, method="post", data=data
|
|
411
|
-
) # noqa: E501
|
|
377
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
412
378
|
result = response["msg"]
|
|
413
379
|
if result not in ["done", "no change"]:
|
|
414
380
|
_LOGGER.error("Problem issuing command: %s", response["msg"])
|
|
@@ -433,13 +399,11 @@ class OpenEVSE:
|
|
|
433
399
|
data = {"divert_enabled": mode}
|
|
434
400
|
|
|
435
401
|
_LOGGER.debug("Toggling divert: %s", mode)
|
|
436
|
-
response = await self.process_request(
|
|
437
|
-
url=url, method="post", data=data
|
|
438
|
-
) # noqa: E501
|
|
402
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
439
403
|
_LOGGER.debug("divert_mode response: %s", response)
|
|
440
404
|
return response
|
|
441
405
|
|
|
442
|
-
async def get_override(self) ->
|
|
406
|
+
async def get_override(self) -> dict[str, str] | dict[str, Any]:
|
|
443
407
|
"""Get the manual override status."""
|
|
444
408
|
if not self._version_check("4.0.1"):
|
|
445
409
|
_LOGGER.debug("Feature not supported for older firmware.")
|
|
@@ -486,9 +450,7 @@ class OpenEVSE:
|
|
|
486
450
|
|
|
487
451
|
_LOGGER.debug("Override data: %s", data)
|
|
488
452
|
_LOGGER.debug("Setting override config on %s", url)
|
|
489
|
-
response = await self.process_request(
|
|
490
|
-
url=url, method="post", data=data
|
|
491
|
-
) # noqa: E501
|
|
453
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
492
454
|
return response
|
|
493
455
|
|
|
494
456
|
async def toggle_override(self) -> None:
|
|
@@ -559,9 +521,7 @@ class OpenEVSE:
|
|
|
559
521
|
data = {"service": level}
|
|
560
522
|
|
|
561
523
|
_LOGGER.debug("Set service level to: %s", level)
|
|
562
|
-
response = await self.process_request(
|
|
563
|
-
url=url, method="post", data=data
|
|
564
|
-
) # noqa: E501
|
|
524
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
565
525
|
_LOGGER.debug("service response: %s", response)
|
|
566
526
|
result = response["msg"]
|
|
567
527
|
if result not in ["done", "no change"]:
|
|
@@ -633,42 +593,11 @@ class OpenEVSE:
|
|
|
633
593
|
return None
|
|
634
594
|
|
|
635
595
|
try:
|
|
636
|
-
if self._session:
|
|
637
|
-
session = self._session
|
|
638
|
-
http_method = getattr(session, method)
|
|
639
|
-
_LOGGER.debug(
|
|
640
|
-
"Connecting to %s using method %s",
|
|
641
|
-
url,
|
|
642
|
-
method,
|
|
643
|
-
)
|
|
644
|
-
async with http_method(url) as resp:
|
|
645
|
-
if resp.status != 200:
|
|
646
|
-
return None
|
|
647
|
-
message = await resp.text()
|
|
648
|
-
message = json.loads(message)
|
|
649
|
-
response = {}
|
|
650
|
-
response["latest_version"] = message["tag_name"]
|
|
651
|
-
response["release_notes"] = message["body"]
|
|
652
|
-
response["release_url"] = message["html_url"]
|
|
653
|
-
return response
|
|
654
|
-
else:
|
|
596
|
+
if (session := self._session) is None:
|
|
655
597
|
async with aiohttp.ClientSession() as session:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
url,
|
|
660
|
-
method,
|
|
661
|
-
)
|
|
662
|
-
async with http_method(url) as resp:
|
|
663
|
-
if resp.status != 200:
|
|
664
|
-
return None
|
|
665
|
-
message = await resp.text()
|
|
666
|
-
message = json.loads(message)
|
|
667
|
-
response = {}
|
|
668
|
-
response["latest_version"] = message["tag_name"]
|
|
669
|
-
response["release_notes"] = message["body"]
|
|
670
|
-
response["release_url"] = message["html_url"]
|
|
671
|
-
return response
|
|
598
|
+
return await self._firmware_check_with_session(session, url, method)
|
|
599
|
+
else:
|
|
600
|
+
return await self._firmware_check_with_session(session, url, method)
|
|
672
601
|
|
|
673
602
|
except (TimeoutError, ServerTimeoutError):
|
|
674
603
|
_LOGGER.error("%s: %s", ERROR_TIMEOUT, url)
|
|
@@ -679,6 +608,33 @@ class OpenEVSE:
|
|
|
679
608
|
|
|
680
609
|
return None
|
|
681
610
|
|
|
611
|
+
async def _firmware_check_with_session(
|
|
612
|
+
self, session: aiohttp.ClientSession, url: str, method: str
|
|
613
|
+
) -> dict | None:
|
|
614
|
+
"""Process a firmware check request with a given session."""
|
|
615
|
+
http_method = getattr(session, method)
|
|
616
|
+
_LOGGER.debug(
|
|
617
|
+
"Connecting to %s using method %s",
|
|
618
|
+
url,
|
|
619
|
+
method,
|
|
620
|
+
)
|
|
621
|
+
async with http_method(url) as resp:
|
|
622
|
+
if resp.status != 200:
|
|
623
|
+
return None
|
|
624
|
+
message = await resp.text()
|
|
625
|
+
try:
|
|
626
|
+
message = json.loads(message)
|
|
627
|
+
except json.JSONDecodeError:
|
|
628
|
+
_LOGGER.error("Failed to parse JSON response: %s", message)
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
response = {}
|
|
632
|
+
if isinstance(message, dict):
|
|
633
|
+
response["latest_version"] = message.get("tag_name")
|
|
634
|
+
response["release_notes"] = message.get("body")
|
|
635
|
+
response["release_url"] = message.get("html_url")
|
|
636
|
+
return response
|
|
637
|
+
|
|
682
638
|
def _version_check(self, min_version: str, max_version: str = "") -> bool:
|
|
683
639
|
"""Return bool if minimum version is met."""
|
|
684
640
|
if "version" not in self._config:
|
|
@@ -831,7 +787,7 @@ class OpenEVSE:
|
|
|
831
787
|
raise UnsupportedFeature
|
|
832
788
|
|
|
833
789
|
url = f"{self.url}limit"
|
|
834
|
-
data:
|
|
790
|
+
data: dict[str, Any] = await self.get_limit()
|
|
835
791
|
valid_types = ["time", "energy", "soc", "range"]
|
|
836
792
|
|
|
837
793
|
if limit_type not in valid_types:
|
|
@@ -844,9 +800,7 @@ class OpenEVSE:
|
|
|
844
800
|
|
|
845
801
|
_LOGGER.debug("Limit data: %s", data)
|
|
846
802
|
_LOGGER.debug("Setting limit config on %s", url)
|
|
847
|
-
response = await self.process_request(
|
|
848
|
-
url=url, method="post", data=data
|
|
849
|
-
) # noqa: E501
|
|
803
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
850
804
|
return response
|
|
851
805
|
|
|
852
806
|
async def clear_limit(self) -> Any:
|
|
@@ -856,12 +810,9 @@ class OpenEVSE:
|
|
|
856
810
|
raise UnsupportedFeature
|
|
857
811
|
|
|
858
812
|
url = f"{self.url}limit"
|
|
859
|
-
data: Dict[str, Any] = {}
|
|
860
813
|
|
|
861
814
|
_LOGGER.debug("Clearing limit config on %s", url)
|
|
862
|
-
response = await self.process_request(
|
|
863
|
-
url=url, method="delete", data=data
|
|
864
|
-
) # noqa: E501
|
|
815
|
+
response = await self.process_request(url=url, method="delete")
|
|
865
816
|
return response
|
|
866
817
|
|
|
867
818
|
async def get_limit(self) -> Any:
|
|
@@ -871,12 +822,9 @@ class OpenEVSE:
|
|
|
871
822
|
raise UnsupportedFeature
|
|
872
823
|
|
|
873
824
|
url = f"{self.url}limit"
|
|
874
|
-
data: Dict[str, Any] = {}
|
|
875
825
|
|
|
876
826
|
_LOGGER.debug("Getting limit config on %s", url)
|
|
877
|
-
response = await self.process_request(
|
|
878
|
-
url=url, method="get", data=data
|
|
879
|
-
) # noqa: E501
|
|
827
|
+
response = await self.process_request(url=url, method="get")
|
|
880
828
|
return response
|
|
881
829
|
|
|
882
830
|
async def make_claim(
|
|
@@ -911,9 +859,7 @@ class OpenEVSE:
|
|
|
911
859
|
|
|
912
860
|
_LOGGER.debug("Claim data: %s", data)
|
|
913
861
|
_LOGGER.debug("Setting up claim on %s", url)
|
|
914
|
-
response = await self.process_request(
|
|
915
|
-
url=url, method="post", data=data
|
|
916
|
-
) # noqa: E501
|
|
862
|
+
response = await self.process_request(url=url, method="post", data=data)
|
|
917
863
|
return response
|
|
918
864
|
|
|
919
865
|
async def release_claim(self, client: int = CLIENT) -> Any:
|
|
@@ -925,7 +871,7 @@ class OpenEVSE:
|
|
|
925
871
|
url = f"{self.url}claims/{client}"
|
|
926
872
|
|
|
927
873
|
_LOGGER.debug("Releasing claim on %s", url)
|
|
928
|
-
response = await self.process_request(url=url, method="delete")
|
|
874
|
+
response = await self.process_request(url=url, method="delete")
|
|
929
875
|
return response
|
|
930
876
|
|
|
931
877
|
async def list_claims(self, target: bool | None = None) -> Any:
|
|
@@ -941,7 +887,7 @@ class OpenEVSE:
|
|
|
941
887
|
url = f"{self.url}claims{target_check}"
|
|
942
888
|
|
|
943
889
|
_LOGGER.debug("Getting claims on %s", url)
|
|
944
|
-
response = await self.process_request(url=url, method="get")
|
|
890
|
+
response = await self.process_request(url=url, method="get")
|
|
945
891
|
return response
|
|
946
892
|
|
|
947
893
|
async def set_led_brightness(self, level: int) -> None:
|
|
@@ -955,7 +901,7 @@ class OpenEVSE:
|
|
|
955
901
|
|
|
956
902
|
data["led_brightness"] = level
|
|
957
903
|
_LOGGER.debug("Setting LED brightness to %s", level)
|
|
958
|
-
await self.process_request(url=url, method="post", data=data)
|
|
904
|
+
await self.process_request(url=url, method="post", data=data)
|
|
959
905
|
|
|
960
906
|
async def set_divert_mode(self, mode: str = "fast") -> None:
|
|
961
907
|
"""Set the divert mode."""
|
|
@@ -1418,7 +1364,7 @@ class OpenEVSE:
|
|
|
1418
1364
|
# Safety counts
|
|
1419
1365
|
@property
|
|
1420
1366
|
def checks_count(self) -> dict:
|
|
1421
|
-
"""Return the
|
|
1367
|
+
"""Return the safety checks counts."""
|
|
1422
1368
|
attributes = ("gfcicount", "nogndcount", "stuckcount")
|
|
1423
1369
|
counts = {}
|
|
1424
1370
|
if self._status is not None and set(attributes).issubset(self._status.keys()):
|
|
@@ -170,7 +170,7 @@ class OpenEVSEWebsocket:
|
|
|
170
170
|
if self._ping and self._pong:
|
|
171
171
|
time_delta = self._pong - self._ping
|
|
172
172
|
if time_delta < datetime.timedelta(0):
|
|
173
|
-
#
|
|
173
|
+
# Negative time should indicate no pong reply so consider the
|
|
174
174
|
# websocket disconnected.
|
|
175
175
|
self._error_reason = ERROR_PING_TIMEOUT
|
|
176
176
|
await self._set_state(STATE_DISCONNECTED)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[tool.ruff]
|
|
2
|
+
target-version = "py310"
|
|
3
|
+
line-length = 88
|
|
4
|
+
|
|
5
|
+
[tool.ruff.lint]
|
|
6
|
+
select = [
|
|
7
|
+
"E", # pycodestyle errors
|
|
8
|
+
"W", # pycodestyle warnings
|
|
9
|
+
"F", # pyflakes
|
|
10
|
+
"I", # isort
|
|
11
|
+
"C", # flake8-comprehensions
|
|
12
|
+
"B", # flake8-bugbear
|
|
13
|
+
"UP", # pyupgrade
|
|
14
|
+
"D", # pydocstyle
|
|
15
|
+
]
|
|
16
|
+
ignore = [
|
|
17
|
+
"D100", # missing docstring in public module
|
|
18
|
+
"D101", # missing docstring in public class
|
|
19
|
+
"D102", # missing docstring in public method
|
|
20
|
+
"D103", # missing docstring in public function
|
|
21
|
+
"D104", # missing docstring in public package
|
|
22
|
+
"D105", # missing docstring in magic method
|
|
23
|
+
"D106", # missing docstring in public nested class
|
|
24
|
+
"D107", # missing docstring in __init__
|
|
25
|
+
"D202", # No blank lines allowed after function docstring
|
|
26
|
+
"D203", # 1 blank line required before class docstring
|
|
27
|
+
"D213", # Multi-line docstring summary should start at the second line
|
|
28
|
+
"E501", # line too long
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.ruff.lint.mccabe]
|
|
32
|
+
max-complexity = 18
|
{python_openevse_http-0.2.5 → python_openevse_http-0.2.6/python_openevse_http.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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
|
|
@@ -11,12 +11,10 @@ Classifier: Development Status :: 4 - Beta
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
16
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
19
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.13
|
|
20
18
|
Description-Content-Type: text/markdown
|
|
21
19
|
License-File: LICENSE
|
|
22
20
|
Requires-Dist: aiohttp
|
|
@@ -6,7 +6,7 @@ from setuptools import find_packages, setup
|
|
|
6
6
|
|
|
7
7
|
PROJECT_DIR = Path(__file__).parent.resolve()
|
|
8
8
|
README_FILE = PROJECT_DIR / "README.md"
|
|
9
|
-
VERSION = "0.2.
|
|
9
|
+
VERSION = "0.2.6"
|
|
10
10
|
|
|
11
11
|
setup(
|
|
12
12
|
name="python_openevse_http",
|
|
@@ -19,7 +19,7 @@ setup(
|
|
|
19
19
|
long_description=README_FILE.read_text(encoding="utf-8"),
|
|
20
20
|
long_description_content_type="text/markdown",
|
|
21
21
|
packages=find_packages(exclude=["test.*", "tests"]),
|
|
22
|
-
python_requires=">=3.
|
|
22
|
+
python_requires=">=3.13",
|
|
23
23
|
install_requires=["aiohttp"],
|
|
24
24
|
license="Apache-2.0",
|
|
25
25
|
entry_points={},
|
|
@@ -30,10 +30,8 @@ setup(
|
|
|
30
30
|
"Intended Audience :: Developers",
|
|
31
31
|
"Natural Language :: English",
|
|
32
32
|
"Programming Language :: Python :: 3",
|
|
33
|
-
"Programming Language :: Python :: 3.10",
|
|
34
|
-
"Programming Language :: Python :: 3.11",
|
|
35
|
-
"Programming Language :: Python :: 3.12",
|
|
36
33
|
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Programming Language :: Python :: 3.14",
|
|
37
35
|
"License :: OSI Approved :: Apache Software License",
|
|
38
36
|
],
|
|
39
37
|
)
|
|
@@ -150,6 +150,16 @@ async def test_send_command_parse_err(test_charger_auth, mock_aioclient):
|
|
|
150
150
|
status = await test_charger_auth.send_command("test")
|
|
151
151
|
assert status is None
|
|
152
152
|
|
|
153
|
+
mock_aioclient.post(TEST_URL_RAPI, status=400, body='{"other": "Something else"}')
|
|
154
|
+
with pytest.raises(main.ParseJSONError):
|
|
155
|
+
status = await test_charger_auth.send_command("test")
|
|
156
|
+
assert status is None
|
|
157
|
+
|
|
158
|
+
mock_aioclient.post(TEST_URL_RAPI, status=400, body='"Just a string response"')
|
|
159
|
+
with pytest.raises(main.ParseJSONError):
|
|
160
|
+
status = await test_charger_auth.send_command("test")
|
|
161
|
+
assert status is None
|
|
162
|
+
|
|
153
163
|
|
|
154
164
|
async def test_send_command_auth_err(test_charger_auth, mock_aioclient):
|
|
155
165
|
"""Test v4 Status reply."""
|
|
@@ -854,7 +864,7 @@ async def test_toggle_override_v2_err(test_charger_v2, mock_aioclient, caplog):
|
|
|
854
864
|
"""Test v4 Status reply."""
|
|
855
865
|
await test_charger_v2.update()
|
|
856
866
|
content_error = mock.Mock()
|
|
857
|
-
|
|
867
|
+
content_error.real_url = f"{TEST_URL_RAPI}"
|
|
858
868
|
mock_aioclient.post(
|
|
859
869
|
TEST_URL_RAPI,
|
|
860
870
|
exception=ContentTypeError(
|
|
@@ -1069,7 +1079,7 @@ async def test_set_divertmode(
|
|
|
1069
1079
|
|
|
1070
1080
|
|
|
1071
1081
|
async def test_test_and_get(test_charger, test_charger_v2, mock_aioclient, caplog):
|
|
1072
|
-
"""Test v4 Status reply"""
|
|
1082
|
+
"""Test v4 Status reply."""
|
|
1073
1083
|
data = await test_charger.test_and_get()
|
|
1074
1084
|
mock_aioclient.get(
|
|
1075
1085
|
TEST_URL_CONFIG,
|
|
@@ -1108,7 +1118,7 @@ async def test_firmware_check(
|
|
|
1108
1118
|
mock_aioclient,
|
|
1109
1119
|
caplog,
|
|
1110
1120
|
):
|
|
1111
|
-
"""Test v4 Status reply"""
|
|
1121
|
+
"""Test v4 Status reply."""
|
|
1112
1122
|
await test_charger.update()
|
|
1113
1123
|
mock_aioclient.get(
|
|
1114
1124
|
TEST_URL_GITHUB_v4,
|
|
@@ -3164,6 +3174,14 @@ async def test_firmware_check_errors(mock_aioclient):
|
|
|
3164
3174
|
)
|
|
3165
3175
|
assert await charger.firmware_check() is None
|
|
3166
3176
|
|
|
3177
|
+
# JSONDecodeError from github
|
|
3178
|
+
mock_aioclient.get(url, status=200, body="not json")
|
|
3179
|
+
assert await charger.firmware_check() is None
|
|
3180
|
+
|
|
3181
|
+
# Non-dict JSON from github
|
|
3182
|
+
mock_aioclient.get(url, status=200, body='"just a string"')
|
|
3183
|
+
assert await charger.firmware_check() == {}
|
|
3184
|
+
|
|
3167
3185
|
|
|
3168
3186
|
async def test_websocket_pong():
|
|
3169
3187
|
"""Test websocket handles pong message."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{python_openevse_http-0.2.5 → python_openevse_http-0.2.6}/python_openevse_http.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|