Skip to content

Commit 2f5bcf5

Browse files
authored
feat: update override functions for better usability (#125)
* feat: update override functions for better usability * update tests
1 parent 7348719 commit 2f5bcf5

File tree

3 files changed

+165
-40
lines changed

3 files changed

+165
-40
lines changed

openevsehttp/__main__.py

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
MissingSerial,
2020
ParseJSONError,
2121
UnknownError,
22+
UnsupportedFeature,
2223
)
2324
from .websocket import (
2425
SIGNAL_CONNECTION_STATE,
@@ -300,6 +301,9 @@ async def divert_mode(self, mode: str = "normal") -> None:
300301

301302
async def get_override(self) -> None:
302303
"""Get the manual override status."""
304+
if not self._version_check("4.0.0"):
305+
_LOGGER.debug("Feature not supported for older firmware.")
306+
raise UnsupportedFeature
303307
url = f"{self.url}override"
304308

305309
_LOGGER.debug("Getting data from %s", url)
@@ -309,27 +313,37 @@ async def get_override(self) -> None:
309313
async def set_override(
310314
self,
311315
state: str,
312-
charge_current: int,
313-
max_current: int,
314-
energy_limit: int,
315-
time_limit: int,
316+
charge_current: int | None = None,
317+
max_current: int | None = None,
318+
energy_limit: int | None = None,
319+
time_limit: int | None = None,
316320
auto_release: bool = True,
317-
) -> str:
321+
) -> Any:
318322
"""Set the manual override status."""
323+
if not self._version_check("4.0.0"):
324+
_LOGGER.debug("Feature not supported for older firmware.")
325+
raise UnsupportedFeature
319326
url = f"{self.url}override"
320327

321328
if state not in ["active", "disabled"]:
329+
_LOGGER.error("Invalid override state: %s", state)
322330
raise ValueError
323331

324332
data = {
325333
"state": state,
326-
"charge_current": charge_current,
327-
"max_current": max_current,
328-
"energy_limit": energy_limit,
329-
"time_limit": time_limit,
330334
"auto_release": auto_release,
331335
}
332336

337+
if charge_current:
338+
data["charge_current"] = charge_current
339+
if max_current:
340+
data["max_current"] = max_current
341+
if energy_limit:
342+
data["energy_limit"] = energy_limit
343+
if time_limit:
344+
data["time_limit"] = time_limit
345+
346+
_LOGGER.debug("Override data: %s", data)
333347
_LOGGER.debug("Setting override config on %s", url)
334348
response = await self.process_request(
335349
url=url, method="post", data=data
@@ -340,22 +354,7 @@ async def toggle_override(self) -> None:
340354
"""Toggle the manual override status."""
341355
# 3.x: use RAPI commands $FE (enable) and $FS (sleep)
342356
# 4.x: use HTTP API call
343-
344-
cutoff = AwesomeVersion("4.0.0")
345-
current = ""
346-
347-
_LOGGER.debug("Detected firmware: %s", self._config["version"])
348-
349-
if "dev" in self._config["version"]:
350-
value = self._config["version"]
351-
_LOGGER.debug("Stripping 'dev' from version.")
352-
value = value.split(".")
353-
value = ".".join(value[0:3])
354-
current = AwesomeVersion(value)
355-
else:
356-
current = AwesomeVersion(self._config["version"])
357-
358-
if current >= cutoff:
357+
if self._version_check("4.0.0"):
359358
url = f"{self.url}override"
360359

361360
_LOGGER.debug("Toggling manual override %s", url)
@@ -370,6 +369,9 @@ async def toggle_override(self) -> None:
370369

371370
async def clear_override(self) -> None:
372371
"""Clear the manual override status."""
372+
if not self._version_check("4.0.0"):
373+
_LOGGER.debug("Feature not supported for older firmware.")
374+
raise UnsupportedFeature
373375
url = f"{self.url}override"
374376

375377
_LOGGER.debug("Clearing manual override %s", url)
@@ -381,21 +383,8 @@ async def set_current(self, amps: int = 6) -> None:
381383
# 3.x - 4.1.0: use RAPI commands $SC <amps>
382384
# 4.1.2: use HTTP API call
383385
amps = int(amps)
384-
cutoff = AwesomeVersion("4.1.2")
385-
current = ""
386-
387-
_LOGGER.debug("Detected firmware: %s", current)
388-
389-
if "dev" in self._config["version"]:
390-
value = self._config["version"]
391-
_LOGGER.debug("Stripping 'dev' from version.")
392-
value = value.split(".")
393-
value = ".".join(value[0:3])
394-
current = AwesomeVersion(value)
395-
else:
396-
current = AwesomeVersion(self._config["version"])
397386

398-
if current >= cutoff:
387+
if self._version_check("4.1.2"):
399388
url = f"{self.url}config"
400389

401390
if (
@@ -488,6 +477,26 @@ async def firmware_check(self) -> dict | None:
488477
response["release_url"] = message["html_url"]
489478
return response
490479

480+
def _version_check(self, min_version: str) -> bool:
481+
"""Return bool if minimum version is met."""
482+
cutoff = AwesomeVersion(min_version)
483+
current = ""
484+
485+
_LOGGER.debug("Detected firmware: %s", self._config["version"])
486+
487+
if "dev" in self._config["version"]:
488+
value = self._config["version"]
489+
_LOGGER.debug("Stripping 'dev' from version.")
490+
value = value.split(".")
491+
value = ".".join(value[0:3])
492+
current = AwesomeVersion(value)
493+
else:
494+
current = AwesomeVersion(self._config["version"])
495+
496+
if current >= cutoff:
497+
return True
498+
return False
499+
491500
@property
492501
def hostname(self) -> str:
493502
"""Return charger hostname."""

openevsehttp/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,7 @@ class AlreadyListening(Exception):
2323

2424
class MissingSerial(Exception):
2525
"""Exception for missing serial number."""
26+
27+
28+
class UnsupportedFeature(Exception):
29+
"""Exception for firmware that is too old."""

tests/test_main.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import openevsehttp.__main__ as main
1212
from tests.common import load_fixture
13-
from openevsehttp.exceptions import MissingSerial
13+
from openevsehttp.exceptions import MissingSerial, UnsupportedFeature
1414

1515
pytestmark = pytest.mark.asyncio
1616

@@ -971,3 +971,115 @@ async def test_max_current_soft(fixture, expected, request):
971971
await charger.update()
972972
status = charger.max_current_soft
973973
assert status == expected
974+
975+
976+
async def test_set_override(test_charger, test_charger_v2, mock_aioclient, caplog):
977+
"""Test set override function."""
978+
await test_charger.update()
979+
mock_aioclient.post(
980+
TEST_URL_OVERRIDE,
981+
status=200,
982+
body='{"msg": "OK"}',
983+
)
984+
with caplog.at_level(logging.DEBUG):
985+
status = await test_charger.set_override("active")
986+
assert status == {"msg": "OK"}
987+
assert "Override data: {'state': 'active', 'auto_release': True}" in caplog.text
988+
989+
mock_aioclient.post(
990+
TEST_URL_OVERRIDE,
991+
status=200,
992+
body='{"msg": "OK"}',
993+
)
994+
status = await test_charger.set_override("active", 30)
995+
assert (
996+
"Override data: {'state': 'active', 'auto_release': True, 'charge_current': 30}"
997+
in caplog.text
998+
)
999+
mock_aioclient.post(
1000+
TEST_URL_OVERRIDE,
1001+
status=200,
1002+
body='{"msg": "OK"}',
1003+
)
1004+
status = await test_charger.set_override("active", 30, 32)
1005+
assert (
1006+
"Override data: {'state': 'active', 'auto_release': True, 'charge_current': 30, 'max_current': 32}"
1007+
in caplog.text
1008+
)
1009+
mock_aioclient.post(
1010+
TEST_URL_OVERRIDE,
1011+
status=200,
1012+
body='{"msg": "OK"}',
1013+
)
1014+
status = await test_charger.set_override("active", 30, 32, 2000)
1015+
assert (
1016+
"Override data: {'state': 'active', 'auto_release': True, 'charge_current': 30, 'max_current': 32, 'energy_limit': 2000}"
1017+
in caplog.text
1018+
)
1019+
mock_aioclient.post(
1020+
TEST_URL_OVERRIDE,
1021+
status=200,
1022+
body='{"msg": "OK"}',
1023+
)
1024+
status = await test_charger.set_override("active", 30, 32, 2000, 5000)
1025+
assert (
1026+
"Override data: {'state': 'active', 'auto_release': True, 'charge_current': 30, 'max_current': 32, 'energy_limit': 2000, 'time_limit': 5000}"
1027+
in caplog.text
1028+
)
1029+
1030+
with pytest.raises(ValueError):
1031+
with caplog.at_level(logging.DEBUG):
1032+
await test_charger.set_override("invalid")
1033+
assert "Invalid override state: invalid" in caplog.text
1034+
1035+
with pytest.raises(UnsupportedFeature):
1036+
with caplog.at_level(logging.DEBUG):
1037+
await test_charger_v2.update()
1038+
status = await test_charger_v2.set_override("active")
1039+
assert "Feature not supported for older firmware." in caplog.text
1040+
1041+
1042+
async def test_clear_override(test_charger, test_charger_v2, mock_aioclient, caplog):
1043+
"""Test clear override function."""
1044+
await test_charger.update()
1045+
mock_aioclient.delete(
1046+
TEST_URL_OVERRIDE,
1047+
status=200,
1048+
body='{"msg": "OK"}',
1049+
)
1050+
with caplog.at_level(logging.DEBUG):
1051+
await test_charger.clear_override()
1052+
assert "Toggle response: OK" in caplog.text
1053+
1054+
with pytest.raises(UnsupportedFeature):
1055+
with caplog.at_level(logging.DEBUG):
1056+
await test_charger_v2.update()
1057+
await test_charger_v2.clear_override()
1058+
assert "Feature not supported for older firmware." in caplog.text
1059+
1060+
1061+
async def test_get_override(test_charger, test_charger_v2, mock_aioclient, caplog):
1062+
"""Test get override function."""
1063+
await test_charger.update()
1064+
value = {
1065+
"state": "active",
1066+
"charge_current": 0,
1067+
"max_current": 0,
1068+
"energy_limit": 0,
1069+
"time_limit": 0,
1070+
"auto_release": True,
1071+
}
1072+
mock_aioclient.get(
1073+
TEST_URL_OVERRIDE,
1074+
status=200,
1075+
body=json.dumps(value),
1076+
)
1077+
with caplog.at_level(logging.DEBUG):
1078+
status = await test_charger.get_override()
1079+
assert status == value
1080+
1081+
with pytest.raises(UnsupportedFeature):
1082+
with caplog.at_level(logging.DEBUG):
1083+
await test_charger_v2.update()
1084+
await test_charger_v2.get_override()
1085+
assert "Feature not supported for older firmware." in caplog.text

0 commit comments

Comments
 (0)