Skip to content

Commit

Permalink
Migrate to batch_update() method
Browse files Browse the repository at this point in the history
This allows us to only rrequest the modbus registers for entities that are currently enabled.
This will improve performance when you disable entities that you're not interested in
  • Loading branch information
wlcrs committed Mar 31, 2024
1 parent d817489 commit 9ffcddb
Show file tree
Hide file tree
Showing 10 changed files with 1,166 additions and 731 deletions.
818 changes: 617 additions & 201 deletions LICENSE

Large diffs are not rendered by default.

361 changes: 40 additions & 321 deletions __init__.py

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for Huawei Solar integration."""

from __future__ import annotations

import logging
Expand All @@ -9,10 +10,22 @@

from homeassistant import config_entries
from homeassistant.components import usb
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_TYPE, CONF_USERNAME
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_TYPE,
CONF_USERNAME,
)
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from huawei_solar import ConnectionException, HuaweiSolarBridge, HuaweiSolarException, InvalidCredentials, ReadException
from huawei_solar import (
ConnectionException,
HuaweiSolarBridge,
HuaweiSolarException,
InvalidCredentials,
ReadException,
)

from .const import (
CONF_ENABLE_PARAMETER_CONFIGURATION,
Expand Down
37 changes: 21 additions & 16 deletions diagnostics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Diagnostics support for Huawei Solar."""

from __future__ import annotations

from importlib.metadata import version
from typing import Any

from homeassistant.components.diagnostics import async_redact_data
Expand All @@ -24,43 +26,46 @@ async def async_get_config_entry_diagnostics(
][DATA_UPDATE_COORDINATORS]

diagnostics_data = {
"config_entry_data": async_redact_data(dict(entry.data), TO_REDACT)
"config_entry_data": async_redact_data(dict(entry.data), TO_REDACT),
"pymodbus_version": version("pymodbus"),
}
for ucs in coordinators:
diagnostics_data[
f"slave_{ucs.bridge.slave_id}"
] = await _build_bridge_diagnostics_info(ucs.bridge)

diagnostics_data[
f"slave_{ucs.bridge.slave_id}_inverter_data"
] = ucs.inverter_update_coordinator.data
diagnostics_data[f"slave_{ucs.bridge.slave_id}_inverter_data"] = (
ucs.inverter_update_coordinator.data
)

if ucs.power_meter_update_coordinator:
diagnostics_data[
f"slave_{ucs.bridge.slave_id}_power_meter_data"
] = ucs.power_meter_update_coordinator.data
diagnostics_data[f"slave_{ucs.bridge.slave_id}_power_meter_data"] = (
ucs.power_meter_update_coordinator.data
)

if ucs.energy_storage_update_coordinator:
diagnostics_data[
f"slave_{ucs.bridge.slave_id}_battery_data"
] = ucs.energy_storage_update_coordinator.data
diagnostics_data[f"slave_{ucs.bridge.slave_id}_battery_data"] = (
ucs.energy_storage_update_coordinator.data
)

if ucs.configuration_update_coordinator:
diagnostics_data[
f"slave_{ucs.bridge.slave_id}_config_data"
] = ucs.configuration_update_coordinator.data
diagnostics_data[f"slave_{ucs.bridge.slave_id}_config_data"] = (
ucs.configuration_update_coordinator.data
)

if ucs.optimizer_update_coordinator:
diagnostics_data[
f"slave_{ucs.bridge.slave_id}_optimizer_data"
] = ucs.optimizer_update_coordinator.data
diagnostics_data[f"slave_{ucs.bridge.slave_id}_optimizer_data"] = (
ucs.optimizer_update_coordinator.data
)

return diagnostics_data


async def _build_bridge_diagnostics_info(bridge: HuaweiSolarBridge) -> dict[str, Any]:
diagnostics_data = {
"model_name": bridge.model_name,
"firmware_version": bridge.firmware_version,
"software_version": bridge.software_version,
"pv_string_count": bridge.pv_string_count,
"has_optimizers": bridge.has_optimizers,
"battery_type": bridge.battery_type,
Expand Down
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
"documentation": "https://github.com/wlcrs/huawei_solar/wiki",
"issue_tracker": "https://github.com/wlcrs/huawei_solar/issues",
"requirements": [
"huawei-solar==2.3.0b4"
"huawei-solar==2.3.0b5"
],
"codeowners": [
"@wlcrs"
],
"iot_class": "local_polling",
"version": "1.3.4b1",
"version": "1.4.0b1",
"loggers": [
"huawei_solar",
"pymodbus"
Expand Down
125 changes: 82 additions & 43 deletions number.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Number entities for Huawei Solar."""

from __future__ import annotations

from dataclasses import dataclass
Expand All @@ -18,21 +19,14 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from huawei_solar import HuaweiSolarBridge, register_names as rn, register_values as rv

from . import (
HuaweiSolarConfigurationUpdateCoordinator,
HuaweiSolarEntity,
HuaweiSolarUpdateCoordinators,
)
from .const import (
CONF_ENABLE_PARAMETER_CONFIGURATION,
DATA_UPDATE_COORDINATORS,
DOMAIN,
)
from . import HuaweiSolarEntity, HuaweiSolarUpdateCoordinators
from .const import CONF_ENABLE_PARAMETER_CONFIGURATION, DATA_UPDATE_COORDINATORS, DOMAIN
from .update_coordinator import HuaweiSolarUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


@dataclass
@dataclass(frozen=True)
class HuaweiSolarNumberEntityDescription(NumberEntityDescription):
"""Huawei Solar Number Entity Description."""

Expand All @@ -46,10 +40,26 @@ class HuaweiSolarNumberEntityDescription(NumberEntityDescription):

def __post_init__(self):
"""Defaults the translation_key to the number key."""
self.translation_key = (
self.translation_key or self.key.replace("#", "_").lower()
# We use this special setter to be able to set/update the translation_key
# in this frozen dataclass.
# cfr. https://docs.python.org/3/library/dataclasses.html#frozen-instances
object.__setattr__(
self,
"translation_key",
self.translation_key or self.key.replace("#", "_").lower(),
)

@property
def context(self):
"""Context used by DataUpdateCoordinator."""

registers = [self.key]
if self.dynamic_minimum_key:
registers.append(self.dynamic_minimum_key)
if self.dynamic_maximum_key:
registers.append(self.dynamic_maximum_key)
return {"register_names": registers}


ENERGY_STORAGE_NUMBER_DESCRIPTIONS: tuple[HuaweiSolarNumberEntityDescription, ...] = (
HuaweiSolarNumberEntityDescription(
Expand Down Expand Up @@ -188,35 +198,43 @@ class HuaweiSolarNumberEntity(CoordinatorEntity, HuaweiSolarEntity, NumberEntity
"""Huawei Solar Number Entity."""

entity_description: HuaweiSolarNumberEntityDescription
_attr_mode = NumberMode.BOX # Always allow a precise number

_static_min_value: float | None = None
_static_max_value: float | None = None

_dynamic_min_value: float | None = None
_dynamic_max_value: float | None = None

def __init__(
self,
coordinator: HuaweiSolarConfigurationUpdateCoordinator,
coordinator: HuaweiSolarUpdateCoordinator,
bridge: HuaweiSolarBridge,
description: HuaweiSolarNumberEntityDescription,
device_info: DeviceInfo,
static_max_value: float | None = None,
static_min_value: float | None = None,
) -> None:
"""Huawei Solar Number Entity constructor.
Do not use directly. Use `.create` instead!
"""
super().__init__(coordinator)
super().__init__(coordinator, description.context)
self.coordinator = coordinator

self.bridge = bridge
self.entity_description = description

self._attr_device_info = device_info
self._attr_unique_id = f"{bridge.serial_number}_{description.key}"
self._attr_mode = NumberMode.BOX # Always allow a precise number

self._dynamic_min_value: float | None = None
self._dynamic_max_value: float | None = None
self._static_max_value = static_max_value
self._static_min_value = static_min_value

@classmethod
async def create(
cls,
coordinator: HuaweiSolarConfigurationUpdateCoordinator,
coordinator: HuaweiSolarUpdateCoordinator,
bridge: HuaweiSolarBridge,
description: HuaweiSolarNumberEntityDescription,
device_info: DeviceInfo,
Expand All @@ -225,40 +243,57 @@ async def create(
This async constructor fills in the necessary min/max values
"""
if description.static_minimum_key:
description.native_min_value = (
await bridge.client.get(description.static_minimum_key, bridge.slave_id)
).value

static_max_value = None
if description.static_maximum_key:
description.native_max_value = (
static_max_value = (
await bridge.client.get(description.static_maximum_key, bridge.slave_id)
).value

return cls(coordinator, bridge, description, device_info)
static_min_value = None
if description.static_minimum_key:
static_min_value = (
await bridge.client.get(description.static_minimum_key, bridge.slave_id)
).value

return cls(
coordinator,
bridge,
description,
device_info,
static_max_value,
static_min_value,
)

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_native_value = self.coordinator.data[
self.entity_description.key
].value

if self.entity_description.dynamic_minimum_key:
min_register = self.coordinator.data.get(
self.entity_description.dynamic_minimum_key
)
if (
self.coordinator.data
and self.entity_description.key in self.coordinator.data
):
self._attr_native_value = self.coordinator.data[
self.entity_description.key
].value

if self.entity_description.dynamic_minimum_key:
min_register = self.coordinator.data.get(
self.entity_description.dynamic_minimum_key
)

if min_register:
self._dynamic_min_value = min_register.value
if min_register:
self._dynamic_min_value = min_register.value

if self.entity_description.dynamic_maximum_key:
max_register = self.coordinator.data.get(
self.entity_description.dynamic_maximum_key
)
if self.entity_description.dynamic_maximum_key:
max_register = self.coordinator.data.get(
self.entity_description.dynamic_maximum_key
)

if max_register:
self._dynamic_max_value = max_register.value
if max_register:
self._dynamic_max_value = max_register.value
else:
self._attr_available = False
self._attr_native_value = None

self.async_write_ha_state()

Expand All @@ -272,7 +307,9 @@ async def async_set_native_value(self, value: float) -> None:
@property
def native_max_value(self) -> float:
"""Maximum value, possibly determined dynamically using _dynamic_max_value."""
native_max_value = self.entity_description.native_max_value
native_max_value = (
self._static_max_value or self.entity_description.native_max_value
)

if self._dynamic_max_value:
if native_max_value:
Expand All @@ -286,7 +323,9 @@ def native_max_value(self) -> float:
@property
def native_min_value(self) -> float:
"""Minimum value, possibly determined dynamically using _dynamic_min_value."""
native_min_value = self.entity_description.native_min_value
native_min_value = (
self._static_min_value or self.entity_description.native_min_value
)

if self._dynamic_min_value:
if native_min_value:
Expand Down
Loading

0 comments on commit 9ffcddb

Please sign in to comment.