Skip to content

Commit

Permalink
2.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
thomluther committed Dec 16, 2024
1 parent 1ab5fe4 commit d142752
Show file tree
Hide file tree
Showing 21 changed files with 385 additions and 253 deletions.
113 changes: 66 additions & 47 deletions INFO.md

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions custom_components/anker_solix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
CONF_EXCLUDE,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
Expand All @@ -31,6 +30,7 @@
EXAMPLESFOLDER,
INTERVALMULT,
LOGGER,
PLATFORMS,
REGISTERED_EXCLUDES,
SERVICE_CLEAR_SOLARBANK_SCHEDULE,
SERVICE_EXPORT_SYSTEMS,
Expand All @@ -44,15 +44,6 @@
)
from .coordinator import AnkerSolixDataUpdateCoordinator

PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]


# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down
22 changes: 18 additions & 4 deletions custom_components/anker_solix/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,29 @@ async def authenticate(self, restart: bool = False) -> bool:
) from exception

async def async_get_data(
self, from_cache: bool = False, device_details: bool = False
self, from_cache: bool = False, device_details: bool = False, reset_cache: bool = False,
) -> any:
"""Get data from the API."""
try:
if self._allow_refresh:
if reset_cache:
# if reset_cache is requested, clear existing api sites and devices caches first prior refresh to avoid stale structures
_LOGGER.debug(
"Api Coordinator %s is clearing Api cache",
self.api.apisession.nickname,
)
# reset last refresh time to allow details refresh
self.last_device_refresh = None
# TODO: Implementent method into api for clearing caches (except account cache)
self.api.sites = {}
self.api.devices = {}
if self.api.powerpanelApi:
self.api.powerpanelApi.sites = {}
self.api.powerpanelApi.devices = {}
if from_cache:
# if refresh from cache is requested, only the actual api dictionaries will be returned of coordinator data
# if refresh from cache is requested, only the actual api cache will be returned for coordinator data
_LOGGER.debug(
"Api Coordinator %s is updating data from Api dictionaries",
"Api Coordinator %s is updating data from Api cache",
self.api.apisession.nickname,
)
elif device_details:
Expand All @@ -178,7 +192,7 @@ async def async_get_data(
< self.min_device_refresh
):
_LOGGER.warning(
"Api Coordinator %s cannot enforce device update within less than %s seconds, using data from Api dictionaries",
"Api Coordinator %s cannot enforce device update within less than %s seconds, using data from Api cache",
self.api.apisession.nickname,
str(self.min_device_refresh),
)
Expand Down
21 changes: 16 additions & 5 deletions custom_components/anker_solix/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import voluptuous as vol

from homeassistant.const import Platform
import homeassistant.helpers.config_validation as cv

from .solixapi import api
Expand All @@ -13,12 +14,19 @@

NAME: str = "Anker Solix"
DOMAIN: str = "anker_solix"
VERSION: str = "2.2.0"
MANUFACTURER: str = "Anker"
ATTRIBUTION: str = "Data provided by Anker Solix Api"
ACCEPT_TERMS: str = "accept_terms"
TERMS_LINK: str = "terms_link"
TC_LINK: str = "https://github.com/thomluther/ha-anker-solix/blob/main/README.md"
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]
TESTMODE: str = "testmode"
TESTFOLDER: str = "testfolder"
INTERVALMULT: str = "dev_interval_mult"
Expand Down Expand Up @@ -55,6 +63,7 @@
DEVICE_LOAD = "device_load"
CHARGE_PRIORITY_LIMIT = "charge_priority_limit"
ALLOW_EXPORT = "allow_export"
DISCHARGE_PRIORITY = "discharge_priority"
INCLUDE_CACHE = "include_cache"


Expand Down Expand Up @@ -95,8 +104,7 @@
),
),
)
VALID_ALLOW_DISCHARGE = vol.Any(None, cv.ENTITY_MATCH_NONE, vol.Coerce(bool))
VALID_INCLUDE_CACHE = vol.Any(None, cv.ENTITY_MATCH_NONE, vol.Coerce(bool))
VALID_SWITCH = vol.Any(None, cv.ENTITY_MATCH_NONE, vol.Coerce(bool))
VALID_WEEK_DAYS = vol.Any(None, cv.ENTITY_MATCH_NONE, cv.weekdays)
VALID_PLAN = vol.Any(
None, cv.ENTITY_MATCH_NONE, vol.Any("active", "custom_rate_plan", "blend_plan")
Expand All @@ -123,7 +131,10 @@
): VALID_CHARGE_PRIORITY,
vol.Optional(
ALLOW_EXPORT,
): VALID_ALLOW_DISCHARGE,
): VALID_SWITCH,
vol.Optional(
DISCHARGE_PRIORITY,
): VALID_SWITCH,
}

SOLARBANK_TIMESLOT_SCHEMA: vol.Schema = vol.All(
Expand Down Expand Up @@ -152,7 +163,7 @@
**cv.TARGET_SERVICE_FIELDS,
vol.Optional(
INCLUDE_CACHE,
): VALID_INCLUDE_CACHE,
): VALID_SWITCH,
}
),
)
38 changes: 30 additions & 8 deletions custom_components/anker_solix/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from datetime import timedelta
from typing import Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
Expand All @@ -16,7 +17,7 @@
AnkerSolixApiClientError,
AnkerSolixApiClientRetryExceededError,
)
from .const import DOMAIN, LOGGER
from .const import DOMAIN, LOGGER, PLATFORMS


# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
Expand Down Expand Up @@ -46,7 +47,7 @@ def __init__(
update_interval=timedelta(seconds=update_interval),
)

async def _async_update_data(self):
async def _async_update_data(self) -> None:
"""Update data via library."""
try:
if self.skip_update:
Expand All @@ -64,20 +65,41 @@ async def _async_update_data(self):
) as exception:
raise UpdateFailed(exception) from exception

async def async_refresh_data_from_apidict(self):
"""Update data from client api dictionaries."""
async def async_refresh_data_from_apidict(self) -> None:
"""Update data from client api dictionaries without resetting update interval."""
self.data = await self.client.async_get_data(from_cache=True)
# inform listeners about changed data
self.async_update_listeners()

async def async_refresh_device_details(self):
async def async_refresh_device_details(self, reset_cache: bool = False) -> None:
"""Update data including device details and reset update interval."""
self.async_set_updated_data(
await self.client.async_get_data(device_details=True)
data = await self.client.async_get_data(
device_details=True, reset_cache=reset_cache
)
if reset_cache:
# ensure to refresh entity setup when cache was reset to unload all entities and reload remaining entities
self.data = data
if await self.hass.config_entries.async_unload_platforms(
self.config_entry, PLATFORMS
):
await self.hass.config_entries.async_forward_entry_setups(
self.config_entry, PLATFORMS
)
else:
# update only coordinator data and notify listeners
self.async_set_updated_data(data)

async def async_execute_command(self, command: str):
async def async_execute_command(self, command: str, option: Any = None) -> None:
"""Execute the given command."""
match command:
case "refresh_device":
await self.async_refresh_device_details()
case "allow_refresh":
if isinstance(option,bool):
self.client.allow_refresh(allow=option)
if option:
# enable Api refresh and clear cache
await self.async_refresh_device_details(reset_cache=True)
else:
# disable Api refresh
await self.async_refresh_data_from_apidict()
1 change: 1 addition & 0 deletions custom_components/anker_solix/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class AnkerSolixPicturePath:
A1780: str = str(Path(IMAGEPATH) / "PPS_F2000_A1780_pub.png")
A1781: str = str(Path(IMAGEPATH) / "PPS_F2600_A1781_pub.png")
A1790: str = str(Path(IMAGEPATH) / "PPS_F3800_A1790_pub.png")
A17B2: str = str(Path(IMAGEPATH) / "Fitting_A17B2_pub.png")

A17A0: str = str(Path(IMAGEPATH) / "PowerCooler30_A17A0_pub.png")
A17A1: str = str(Path(IMAGEPATH) / "PowerCooler40_A17A1_pub.png")
Expand Down
9 changes: 8 additions & 1 deletion custom_components/anker_solix/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"default": "mdi:generator-stationary"
},
"preset_charge_priority": {
"default": "mdi:battery-arrow-up"
"default": "mdi:battery-arrow-up-outline"
},
"system_price": {
"default": "mdi:cash-edit"
Expand Down Expand Up @@ -299,6 +299,13 @@
"off": "mdi:card-off-outline"
}
},
"preset_discharge_priority": {
"default": "mdi:battery-alert",
"state": {
"on": "mdi:battery-arrow-down",
"off": "mdi:battery-clock"
}
},
"allow_refresh": {
"default": "mdi:sync-alert",
"state": {
Expand Down
2 changes: 1 addition & 1 deletion custom_components/anker_solix/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"aiofiles>=23.2.0",
"cryptography>=3.4.8"
],
"version": "2.3.0"
"version": "2.3.1"
}
28 changes: 16 additions & 12 deletions custom_components/anker_solix/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
APPLIANCE_LOAD,
DEVICE_LOAD,
CHARGE_PRIORITY_LIMIT,
DISCHARGE_PRIORITY,
PLAN,
WEEK_DAYS,
CONF_SKIP_INVALID,
Expand Down Expand Up @@ -1967,23 +1968,25 @@ async def _solarbank_schedule_service( # noqa: C901
if (start_time := kwargs.get(START_TIME)) < (
end_time := kwargs.get(END_TIME)
):
plan = kwargs.get(PLAN)
if plan in [cv.ENTITY_MATCH_NONE]:
if (plan := kwargs.get(PLAN)) in [cv.ENTITY_MATCH_NONE]:
plan = None
weekdays = kwargs.get(WEEK_DAYS)
if weekdays == cv.ENTITY_MATCH_NONE:
if (weekdays := kwargs.get(WEEK_DAYS)) == cv.ENTITY_MATCH_NONE:
weekdays = None
load = kwargs.get(APPLIANCE_LOAD)
if load == cv.ENTITY_MATCH_NONE:
if (load := kwargs.get(APPLIANCE_LOAD)) == cv.ENTITY_MATCH_NONE:
load = None
dev_load = kwargs.get(DEVICE_LOAD)
if dev_load == cv.ENTITY_MATCH_NONE:
if (
dev_load := kwargs.get(DEVICE_LOAD)
) == cv.ENTITY_MATCH_NONE:
dev_load = None
allow_export = kwargs.get(ALLOW_EXPORT)
if allow_export == cv.ENTITY_MATCH_NONE:
if (
allow_export := kwargs.get(ALLOW_EXPORT)
) == cv.ENTITY_MATCH_NONE:
allow_export = None
prio = kwargs.get(CHARGE_PRIORITY_LIMIT)
if prio == cv.ENTITY_MATCH_NONE:
if (
discharge_prio := kwargs.get(DISCHARGE_PRIORITY)
) == cv.ENTITY_MATCH_NONE:
discharge_prio = None
if (prio := kwargs.get(CHARGE_PRIORITY_LIMIT)) == cv.ENTITY_MATCH_NONE:
prio = None
# check if now is in given time range and ensure preset increase is limited by min interval
now = datetime.now().astimezone()
Expand Down Expand Up @@ -2080,6 +2083,7 @@ async def _solarbank_schedule_service( # noqa: C901
appliance_load=load,
device_load=dev_load,
allow_export=allow_export,
discharge_priority=discharge_prio,
charge_priority_limit=prio,
)
if service_name == SERVICE_SET_SOLARBANK_SCHEDULE:
Expand Down
10 changes: 10 additions & 0 deletions custom_components/anker_solix/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ set_solarbank_schedule:
example: true
selector:
boolean:
discharge_priority:
required: false
example: false
selector:
boolean:
charge_priority_limit:
required: false
example: 80
Expand Down Expand Up @@ -252,6 +257,11 @@ update_solarbank_schedule:
example: true
selector:
boolean:
discharge_priority:
required: false
example: false
selector:
boolean:
charge_priority_limit:
required: false
example: 80
Expand Down
16 changes: 15 additions & 1 deletion custom_components/anker_solix/solixapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _update_dev( # noqa: C901
This method is used to consolidate various device related key values from various requests under a common set of device keys.
"""
sn = devData.get("device_sn")
sn = devData.pop("device_sn", None)
if sn:
device: dict = self.devices.get(sn, {}) # lookup old device info if any
device.update({"device_sn": str(sn)})
Expand Down Expand Up @@ -403,6 +403,7 @@ def _update_dev( # noqa: C901
{
"preset_system_output_power": SolixDefaults.PRESET_NOSCHEDULE,
"preset_allow_export": SolixDefaults.ALLOW_EXPORT,
"preset_discharge_priority": SolixDefaults.DISCHARGE_PRIORITY_DEF,
"preset_charge_priority": SolixDefaults.CHARGE_PRIORITY_DEF,
"preset_power_mode": SolixDefaults.POWER_MODE
if cnt > 1
Expand Down Expand Up @@ -504,10 +505,17 @@ def _update_dev( # noqa: C901
)[0].get("power")
export = slot.get("turn_on")
prio = slot.get("charge_priority")
if bool(value.get("is_show_priority_discharge")):
discharge_prio = slot.get(
"priority_discharge_switch"
)
else:
discharge_prio = None
device.update(
{
"preset_system_output_power": preset_power,
"preset_allow_export": export,
"preset_discharge_priority": discharge_prio,
"preset_charge_priority": prio,
}
)
Expand Down Expand Up @@ -678,6 +686,12 @@ def _update_dev( # noqa: C901
self.devices.update({str(sn): device})
return sn

def clearCaches(self) -> None:
"""Clear the api cache dictionaries except the account cache."""
super().clearCaches()
if self.powerpanelApi:
self.powerpanelApi.clearCaches()

async def update_sites(
self,
siteId: str | None = None,
Expand Down
5 changes: 5 additions & 0 deletions custom_components/anker_solix/solixapi/apibase.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ def getCaches(self) -> dict:
"""Return a merged dictionary with api cache dictionaries."""
return self.sites | self.devices | {self.apisession.email: self.account}

def clearCaches(self) -> None:
"""Clear the api cache dictionaries except the account cache."""
self.sites = {}
self.devices = {}

def recycleDevices(
self, extraDevices: set | None = None, activeDevices: set | None = None
) -> None:
Expand Down
Loading

0 comments on commit d142752

Please sign in to comment.