Skip to content

Commit

Permalink
Merge pull request #27 from tijsverkoyen/25-access-frequency-to-high
Browse files Browse the repository at this point in the history
25 Access frequency to high
  • Loading branch information
tijsverkoyen authored Dec 20, 2022
2 parents e559402 + 65ca629 commit 0d189ef
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 185 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ The devices that support realtime information (getDevRealKpi api call):
* Battery
* Power Sensor

The exposed entities can be different per device. These are documented in the "Interface reference" that you can
request from Huawei. But the names are pretty self explanatory.
The exposed entities can be different per device. These are documented in the "Interface reference" that you can
request from Huawei. But the names are pretty self-explanatory.

The realtime data is updated every minute.
The realtime data is updated every minute per device group. As the API only allows 1 call per minute to each
endpoint and the same endpoint is needed for each device group. So the more different devices you have the slower
the update will be.

### Total yields
The integration updates the total yields (current day, current month, current year, lifetime) every 10 minutes.
93 changes: 93 additions & 0 deletions custom_components/fusion_solar/device_real_kpi_coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from datetime import timedelta
import math
import logging

from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN
from .fusion_solar.const import ATTR_DEVICE_REAL_KPI_DEV_ID, ATTR_DEVICE_REAL_KPI_DATA_ITEM_MAP, \
PARAM_DEVICE_TYPE_ID_STRING_INVERTER, PARAM_DEVICE_TYPE_ID_EMI, PARAM_DEVICE_TYPE_ID_GRID_METER, \
PARAM_DEVICE_TYPE_ID_RESIDENTIAL_INVERTER, PARAM_DEVICE_TYPE_ID_BATTERY, PARAM_DEVICE_TYPE_ID_POWER_SENSOR
from .fusion_solar.openapi.openapi_api import FusionSolarOpenApiAccessFrequencyTooHighError

_LOGGER = logging.getLogger(__name__)


class DeviceRealKpiDataCoordinator(DataUpdateCoordinator):
def __init__(self, hass, api, devices):
self.name = 'FusionSolarOpenAPIDeviceRealKpiType'

super().__init__(
hass,
_LOGGER,
name=self.name,
update_interval=timedelta(minutes=1),
)

self.api = api
self.devices = devices
self.skip_counter = 0
self.skip = False
self.counter = 0

async def _async_update_data(self):
if self.should_skip:
self.skip_counter += 1
_LOGGER.warning(
f'{self.name} Skipped call due to rate limiting. Wait for {self.skip_for} seconds. {self.skip_counter}/{self.counter_limit}')
raise UpdateFailed(f'Skipped call due to rate limiting. Wait for {self.skip_for} seconds.')

data = {}
device_ids_grouped_per_type_id = self.device_ids_grouped_per_type_id()
index_to_fetch = self.counter % len(device_ids_grouped_per_type_id)
type_id_to_fetch = list(device_ids_grouped_per_type_id.keys())[index_to_fetch]

self.counter += 1

try:
response = await self.hass.async_add_executor_job(
self.api.get_dev_real_kpi,
device_ids_grouped_per_type_id[type_id_to_fetch],
type_id_to_fetch
)
self.skip = False
self.skip_counter = 0
except FusionSolarOpenApiAccessFrequencyTooHighError as e:
self.skip = True
raise UpdateFailed(f'Error fetching data: {e}') from e

for response_data in response:
key = f'{DOMAIN}-{response_data[ATTR_DEVICE_REAL_KPI_DEV_ID]}'
data[key] = response_data[ATTR_DEVICE_REAL_KPI_DATA_ITEM_MAP]

_LOGGER.debug(f'async_update_device_real_kpi_data: {data}')

return data

def device_ids_grouped_per_type_id(self):
device_ids_grouped_per_type_id = {}

for device in self.devices:
# skip devices wherefore no real kpi data is available
if device.type_id not in [PARAM_DEVICE_TYPE_ID_STRING_INVERTER, PARAM_DEVICE_TYPE_ID_EMI,
PARAM_DEVICE_TYPE_ID_GRID_METER, PARAM_DEVICE_TYPE_ID_RESIDENTIAL_INVERTER,
PARAM_DEVICE_TYPE_ID_BATTERY, PARAM_DEVICE_TYPE_ID_POWER_SENSOR]:
continue

if device.type_id not in device_ids_grouped_per_type_id:
device_ids_grouped_per_type_id[device.type_id] = []
device_ids_grouped_per_type_id[device.type_id].append(str(device.device_id))

return device_ids_grouped_per_type_id

@property
def counter_limit(self) -> int:
return math.ceil(60 / self.update_interval.total_seconds()) + 1

@property
def should_skip(self) -> bool:
return self.skip and self.skip_counter <= self.counter_limit

@property
def skip_for(self) -> int:
return (self.counter_limit - self.skip_counter + 1) * self.update_interval.total_seconds()
16 changes: 14 additions & 2 deletions custom_components/fusion_solar/fusion_solar/openapi/openapi_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,17 @@ def _do_call(self, url: str, json: dict):
self._token = None
return self._do_call(url, json)

if ATTR_SUCCESS in json_data and not json_data[ATTR_SUCCESS]:
if ATTR_FAIL_CODE in json_data and json_data[ATTR_FAIL_CODE] == 407:
_LOGGER.debug(
f'Access frequency to high, while calling {url}: {json_data[ATTR_DATA]}, failcode: {json_data[ATTR_FAIL_CODE]}')
raise FusionSolarOpenApiAccessFrequencyTooHighError(
f'Access frequency to high. failCode: {json_data[ATTR_FAIL_CODE]}, message: {json_data[ATTR_DATA]}'
)

if ATTR_FAIL_CODE in json_data and json_data[ATTR_FAIL_CODE] != 0:
_LOGGER.debug(f'Error calling {url}: {json_data[ATTR_DATA]}, failcode: {json_data[ATTR_FAIL_CODE]}')
raise FusionSolarOpenApiError(
f'Retrieving the data failed with failCode: {json_data[ATTR_FAIL_CODE]}, message: {json_data[ATTR_MESSAGE]}'
f'Retrieving the data for {url} failed with failCode: {json_data[ATTR_FAIL_CODE]}, message: {json_data[ATTR_DATA]}'
)

return json_data
Expand All @@ -166,3 +174,7 @@ def _do_call(self, url: str, json: dict):

class FusionSolarOpenApiError(Exception):
pass


class FusionSolarOpenApiAccessFrequencyTooHighError(Exception):
pass
Loading

0 comments on commit 0d189ef

Please sign in to comment.