Skip to content

Commit

Permalink
refactor: adjust charge rate based on claims if available
Browse files Browse the repository at this point in the history
  • Loading branch information
firstof9 committed Nov 19, 2024
1 parent f4e1e23 commit 4b1081f
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 81 deletions.
80 changes: 77 additions & 3 deletions custom_components/openevse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
LIGHT_TYPES,
MANAGER,
PLATFORMS,
NUMBER_TYPES,
SELECT_TYPES,
SENSOR_TYPES,
TIMEOUT_ERROR,
Expand Down Expand Up @@ -323,13 +324,16 @@ async def update_sensors(self) -> dict:
raise UpdateFailed(error) from error

self.parse_sensors()
await self.async_parse_sensors()
_LOGGER.debug("Coordinator data: %s", self._data)
return self._data

@callback
def websocket_update(self):
async def websocket_update(self):
"""Trigger processing updated websocket data."""
_LOGGER.debug("Websocket update!")
self.parse_sensors()
await self.async_parse_sensors()
coordinator = self.hass.data[DOMAIN][self.config.entry_id][COORDINATOR]
coordinator.async_set_updated_data(self._data)

Expand Down Expand Up @@ -373,6 +377,8 @@ def parse_sensors(self) -> None:
data.update(_sensor)
for select in SELECT_TYPES: # pylint: disable=consider-using-dict-items
_sensor = {}
if SELECT_TYPES[select].is_async_value:
continue
try:
sensor_property = SELECT_TYPES[select].key
# Data can be sent as boolean or as 1/0
Expand All @@ -389,6 +395,26 @@ def parse_sensors(self) -> None:
select,
)
data.update(_sensor)
for number in NUMBER_TYPES: # pylint: disable=consider-using-dict-items
_sensor = {}
if NUMBER_TYPES[number].is_async_value:
continue
try:
sensor_property = NUMBER_TYPES[number].key
# Data can be sent as boolean or as 1/0
_sensor[number] = getattr(self._manager, sensor_property)
_LOGGER.debug(
"number: %s sensor_property: %s value %s",
number,
sensor_property,
_sensor[number],
)
except (ValueError, KeyError):
_LOGGER.info(
"Could not update status for %s",
number,
)
data.update(_sensor)
for light in LIGHT_TYPES: # pylint: disable=consider-using-dict-items
_sensor = {}
try:
Expand All @@ -397,7 +423,7 @@ def parse_sensors(self) -> None:
_sensor[light] = getattr(self._manager, sensor_property)
_LOGGER.debug(
"light: %s sensor_property: %s value %s",
select,
light,
sensor_property,
_sensor[light],
)
Expand All @@ -408,7 +434,55 @@ def parse_sensors(self) -> None:
)
data.update(_sensor)
_LOGGER.debug("DEBUG: %s", data)
self._data = data
self._data.update(data)

async def async_parse_sensors(self) -> None:
"""Parse updated sensor data using async."""
data = {}
for select in SELECT_TYPES: # pylint: disable=consider-using-dict-items
_sensor = {}
if not SELECT_TYPES[select].is_async_value:
continue
try:
sensor_property = SELECT_TYPES[select].key
sensor_value = SELECT_TYPES[select].value
# Data can be sent as boolean or as 1/0
_sensor[select] = await getattr(self._manager, sensor_value)
_LOGGER.debug(
"select: %s sensor_property: %s value %s",
select,
sensor_property,
_sensor[select],
)
except (ValueError, KeyError):
_LOGGER.info(
"Could not update status for %s",
select,
)
data.update(_sensor)
for number in NUMBER_TYPES: # pylint: disable=consider-using-dict-items
_sensor = {}
if not NUMBER_TYPES[number].is_async_value:
continue
try:
sensor_property = NUMBER_TYPES[number].key
sensor_value = NUMBER_TYPES[number].value
# Data can be sent as boolean or as 1/0
_sensor[number] = await getattr(self._manager, sensor_value)
_LOGGER.debug(
"number: %s sensor_property: %s value %s",
number,
sensor_property,
_sensor[number],
)
except (ValueError, KeyError):
_LOGGER.info(
"Could not update status for %s",
number,
)
data.update(_sensor)
_LOGGER.debug("DEBUG: %s", data)
self._data.update(data)


async def send_command(handler, command) -> None:
Expand Down
8 changes: 6 additions & 2 deletions custom_components/openevse/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,13 @@
# ),
"max_current_soft": OpenEVSESelectEntityDescription(
name="Charge Rate",
key="current_capacity",
key="max_current_soft",
default_options=None,
command="set_current",
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
is_async_value=True,
value="async_charge_current",
),
"charge_mode": OpenEVSESelectEntityDescription(
name="Divert Mode",
Expand Down Expand Up @@ -504,13 +506,15 @@
NUMBER_TYPES: Final[dict[str, OpenEVSENumberEntityDescription]] = {
"max_current_soft": OpenEVSENumberEntityDescription(
name="Charge Rate",
key="current_capacity",
key="max_current_soft",
default_options=None,
command="set_current",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=NumberDeviceClass.CURRENT,
mode=NumberMode.AUTO,
is_async_value=True,
value="async_charge_current",
),
}

Expand Down
4 changes: 4 additions & 0 deletions custom_components/openevse/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class OpenEVSESelectEntityDescription(SelectEntityDescription):

command: str | None = None
default_options: list | None = None
is_async_value: bool | None = False
value: int | None = None


@dataclass
Expand All @@ -33,6 +35,8 @@ class OpenEVSENumberEntityDescription(NumberEntityDescription):
default_options: list | None = None
min: int | None = None
max: int | None = None
is_async_value: bool | None = False
value: int | None = None


@dataclass
Expand Down
2 changes: 1 addition & 1 deletion custom_components/openevse/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/firstof9/openevse/issues",
"loggers": ["openevsehttp"],
"requirements": ["python-openevse-http==0.1.63"],
"requirements": ["python-openevse-http==0.1.65"],
"version": "0.0.0-dev",
"zeroconf": ["_openevse._tcp.local."]
}
19 changes: 16 additions & 3 deletions custom_components/openevse/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import CONF_NAME, COORDINATOR, DOMAIN, NUMBER_TYPES, MANAGER
from .entity import OpenEVSESelectEntityDescription
from .entity import OpenEVSENumberEntityDescription

from . import (
OpenEVSEManager,
Expand Down Expand Up @@ -48,7 +48,7 @@ def __init__(
self,
config_entry: ConfigEntry,
coordinator: OpenEVSEUpdateCoordinator,
description: OpenEVSESelectEntityDescription,
description: OpenEVSENumberEntityDescription,
manager: OpenEVSEManager,
) -> None:
"""Initialize a ZwaveNumberEntity entity."""
Expand All @@ -62,6 +62,7 @@ def __init__(
self._command = description.command
self._min = description.min
self._max = description.max
self._value = description.value
self._manager = manager
# Entity class attributes
self._attr_name = f"{config_entry.data[CONF_NAME]} {self._name}"
Expand All @@ -78,6 +79,17 @@ def device_info(self):
}
return info

@property
def available(self) -> bool:
"""Return if entity is available."""
data = self.coordinator.data
attributes = ("charge_mode", "divert_active")
if set(attributes).issubset(data.keys()) and self._type == "max_current_soft":
if data["divert_active"] and data["charge_mode"] == "eco":
_LOGGER.debug("Disabling %s due to PV Divert being active.", self._attr_name)
return False
return self.coordinator.last_update_success

@property
def native_min_value(self) -> float:
"""Return the minimum value."""
Expand All @@ -94,9 +106,10 @@ def native_max_value(self) -> float:
def native_value(self) -> float | None:
"""Return the entity value."""
data = self.coordinator.data
value = None
if self._type in data and data is not None:
value = data[self._type]
_LOGGER.debug("Select [%s] updated value: %s", self._type, value)
_LOGGER.debug("Number [%s] updated value: %s", self._type, value)
return None if value is None else float(value)

@property
Expand Down
10 changes: 7 additions & 3 deletions custom_components/openevse/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,17 @@ async def async_select_option(self, option: Any) -> None:
@property
def available(self) -> bool:
"""Return if entity is available."""
if self._type not in self.coordinator.data:
return False
data = self.coordinator.data
attributes = ("charge_mode", "divert_active")
if set(attributes).issubset(data.keys()) and self._type == "max_current_soft":
if data["divert_active"] and data["charge_mode"] == "eco":
_LOGGER.debug("Disabling %s due to PV Divert being active.", self._attr_name)
return False
return self.coordinator.last_update_success

def get_options(self) -> list[str]:
"""Return a set of selectable options."""
if self._type == "current_capacity":
if self._type == "max_current_soft":
amps_min = self.coordinator.data["min_amps"]
amps_max = self.coordinator.data["max_amps"] + 1
# pylint: disable-next=consider-using-generator
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-openevse-http==0.1.63
python-openevse-http==0.1.65
20 changes: 20 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
TEST_URL_STATUS = "http://openevse.test.tld/status"
TEST_URL_CONFIG = "http://openevse.test.tld/config"
TEST_URL_OVERRIDE = "http://openevse.test.tld/override"
TEST_URL_CLAIMS = "http://openevse.test.tld/claims"
TEST_URL_CLAIMS_TARGET = "http://openevse.test.tld/claims/target"
TEST_URL_RAPI = "http://openevse.test.tld/r"
TEST_URL_WS = "ws://openevse.test.tld/ws"
TEST_TLD = "openevse.test.tld"
Expand Down Expand Up @@ -112,6 +114,12 @@ def test_charger(mock_aioclient):
body='{ "msg": "OK" }',
repeat=True,
)
mock_aioclient.get(
TEST_URL_CLAIMS_TARGET,
status=200,
body='{"properties":{"state":"disabled","charge_current":28,"max_current":23,"auto_release":false},"claims":{"state":65540,"charge_current":65537,"max_current":65548}}',
repeat=True,
)
return main.OpenEVSE(TEST_TLD)


Expand Down Expand Up @@ -142,6 +150,12 @@ def test_charger_bad_serial(mock_aioclient):
body=load_fixture("github.json"),
repeat=True,
)
mock_aioclient.get(
TEST_URL_CLAIMS_TARGET,
status=200,
body='{"properties":{"state":"disabled","charge_current":28,"max_current":23,"auto_release":false},"claims":{"state":65540,"charge_current":65537,"max_current":65548}}',
repeat=True,
)
return main.OpenEVSE(TEST_TLD)


Expand Down Expand Up @@ -177,6 +191,12 @@ def test_charger_bad_post(mock_aioclient):
body=load_fixture("github.json"),
repeat=True,
)
mock_aioclient.get(
TEST_URL_CLAIMS_TARGET,
status=200,
body='{"properties":{"state":"disabled","charge_current":28,"max_current":23,"auto_release":false},"claims":{"state":65540,"charge_current":65537,"max_current":65548}}',
repeat=True,
)
return main.OpenEVSE(TEST_TLD)


Expand Down
Loading

0 comments on commit 4b1081f

Please sign in to comment.