Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions openevsehttp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,3 +667,23 @@ async def toggle_shaper(self) -> None:

new_state = not bool(shaper_active)
await self.set_shaper(new_state)

async def set_mqtt_vehicle_range_miles(self, enable: bool = True) -> None:
"""Set mqtt_vehicle_range_miles configuration setting.

Dynamically changing this setting will affect future evaluations of
the vehicle_range_with_unit property.
"""
if not isinstance(enable, bool):
raise TypeError("Value must be a boolean.")

url = f"{self.url}config"
data = {"mqtt_vehicle_range_miles": enable}

_LOGGER.debug("Setting mqtt_vehicle_range_miles to %s", enable)
response = await self.process_request(url=url, method="post", data=data)
response = self._normalize_response(response)
msg = response.get("msg") if isinstance(response, Mapping) else None
if msg not in SUCCESS_ANSWERS:
_LOGGER.error("Problem issuing command: %s", response)
raise UnknownError
23 changes: 23 additions & 0 deletions openevsehttp/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from collections.abc import Mapping
from datetime import datetime, timedelta, timezone
from typing import Any, cast
Expand Down Expand Up @@ -458,11 +459,33 @@ def vehicle_soc(self) -> int | None:
@property
def vehicle_range(self) -> int | None:
"""Return battery range."""
warnings.warn(
"vehicle_range is deprecated, use vehicle_range_with_unit instead",
DeprecationWarning,
stacklevel=2,
)
return cast(
"int | None",
self._status.get("vehicle_range", self._status.get("battery_range", None)),
)

@property
def vehicle_range_with_unit(self) -> tuple[int, str] | None:
"""Return battery range and its unit."""
value = cast(
"int | None",
self._status.get("vehicle_range", self._status.get("battery_range", None)),
)
if value is None:
return None
unit = "miles" if self.mqtt_vehicle_range_miles else "km"
return (value, unit)

@property
def mqtt_vehicle_range_miles(self) -> bool:
"""Return True if mqtt vehicle range is in miles, False if km."""
return bool(self._config.get("mqtt_vehicle_range_miles", False))

@property
def vehicle_eta(self) -> datetime | None:
"""Return time to full charge."""
Expand Down
33 changes: 33 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,3 +1334,36 @@ async def test_update_firmware_bytes_empty(test_charger, caplog):
with pytest.raises(ValueError):
await test_charger.update_firmware(firmware_bytes=b"")
assert "Empty firmware bytes provided" in caplog.text


async def test_set_mqtt_vehicle_range_miles(test_charger_new, mock_aioclient, caplog):
"""Test set_mqtt_vehicle_range_miles command."""
await test_charger_new.update()
mock_aioclient.post(
TEST_URL_CONFIG,
status=200,
body='{"msg": "OK"}',
)
with caplog.at_level(logging.DEBUG):
await test_charger_new.set_mqtt_vehicle_range_miles(True)
assert "Setting mqtt_vehicle_range_miles to True" in caplog.text

mock_aioclient.post(
TEST_URL_CONFIG,
status=200,
body='{"msg": "OK"}',
)
with caplog.at_level(logging.DEBUG):
await test_charger_new.set_mqtt_vehicle_range_miles(False)
assert "Setting mqtt_vehicle_range_miles to False" in caplog.text

with pytest.raises(TypeError, match=r"Value must be a boolean\."):
await test_charger_new.set_mqtt_vehicle_range_miles("invalid")

mock_aioclient.post(
TEST_URL_CONFIG,
status=200,
body='{"msg": "error"}',
)
with pytest.raises(UnknownError):
await test_charger_new.set_mqtt_vehicle_range_miles(True)
28 changes: 27 additions & 1 deletion tests/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@
("test_charger_v2", "vehicle_soc", None),
("test_charger", "vehicle_range", 468),
("test_charger_v2", "vehicle_range", None),
("test_charger", "vehicle_range_with_unit", (468, "km")),
("test_charger_v2", "vehicle_range_with_unit", None),
# shaper
("test_charger", "shaper_active", True),
("test_charger_v2", "shaper_active", None),
Expand Down Expand Up @@ -207,7 +209,11 @@ async def test_simple_properties(fixture, prop, expected, request):
with pytest.raises(expected):
_ = getattr(charger, prop)
else:
assert getattr(charger, prop) == expected
if prop == "vehicle_range":
with pytest.deprecated_call():
assert getattr(charger, prop) == expected
else:
assert getattr(charger, prop) == expected
await charger.ws_disconnect()


Expand Down Expand Up @@ -490,3 +496,23 @@ async def test_wifi_firmware_none():
charger = OpenEVSE(SERVER_URL)
charger._config = {}
assert charger.wifi_firmware is None


async def test_mqtt_vehicle_range_miles():
"""Test mqtt_vehicle_range_miles property and vehicle_range_with_unit unit selection."""
charger = OpenEVSE(SERVER_URL)
# Default is False
assert charger.mqtt_vehicle_range_miles is False

charger._config = {"mqtt_vehicle_range_miles": True}
assert charger.mqtt_vehicle_range_miles is True

charger._status = {"vehicle_range": 150}
with pytest.deprecated_call():
assert charger.vehicle_range == 150
assert charger.vehicle_range_with_unit == (150, "miles")

charger._config = {"mqtt_vehicle_range_miles": False}
with pytest.deprecated_call():
assert charger.vehicle_range == 150
assert charger.vehicle_range_with_unit == (150, "km")
Loading