From f212945603e94065fe2c7e4ce743c9cad5a8fccd Mon Sep 17 00:00:00 2001 From: Guy Khmelnitsky Date: Sun, 15 Mar 2026 13:11:43 +0200 Subject: [PATCH] feat: add update_statistics_date service to unstick partial hour statistics When the IEC API returns partial data for an hour (e.g., 23:00 exists but 23:15/23:30/23:45 are missing), the statistics fetch gets stuck because _insert_statistics skips hours with fewer than 4 readings. This commit adds an `update_statistics_date` service that inserts a zero-value statistic record at a target hour, naturally advancing the DB's "last statistics" point so the next refresh continues from there. - Add asyncio.Lock to protect concurrent statistics operations - Wrap _insert_statistics with the lock - Add set_statistics_from_date method with full validation - Normalize device_number by stripping leading zeros - Register service, services.yaml, and translations (en/he) Co-Authored-By: Claude Opus 4.6 --- custom_components/iec/__init__.py | 16 +- custom_components/iec/coordinator.py | 544 +++++++++++++-------- custom_components/iec/services.yaml | 16 +- custom_components/iec/translations/en.json | 14 + custom_components/iec/translations/he.json | 14 + 5 files changed, 404 insertions(+), 200 deletions(-) diff --git a/custom_components/iec/__init__.py b/custom_components/iec/__init__.py index 1011e84..466579a 100644 --- a/custom_components/iec/__init__.py +++ b/custom_components/iec/__init__.py @@ -5,7 +5,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, ServiceCall from .const import DOMAIN from .coordinator import IecApiCoordinator @@ -40,6 +40,20 @@ async def handle_debug_get_coordinator_data(call) -> None: # noqa: ANN001 ARG00 DOMAIN, "debug_get_coordinator_data", handle_debug_get_coordinator_data ) + async def handle_update_statistics_date(call: ServiceCall) -> None: + datetime_str = call.data.get("datetime") + device_number = call.data.get("device_number") + if not datetime_str or not device_number: + _LOGGER.error("update_statistics_date: missing required fields (datetime, device_number)") + return + result = await iec_coordinator.set_statistics_from_date(str(datetime_str), str(device_number)) + _LOGGER.info("update_statistics_date result: %s", result) + hass.bus.async_fire("iec_statistics_date_updated", {"result": result}) + + hass.services.async_register( + DOMAIN, "update_statistics_date", handle_update_statistics_date + ) + return True diff --git a/custom_components/iec/coordinator.py b/custom_components/iec/coordinator.py index f3b754d..c2d5be0 100644 --- a/custom_components/iec/coordinator.py +++ b/custom_components/iec/coordinator.py @@ -129,6 +129,7 @@ def __init__( self._shared_contract_ids: set[int] = set() self._contract_account_mapping_loaded = False self._connection_size_by_account_id: dict[UUID, str] = {} + self._statistics_lock = asyncio.Lock() self._api_session = aiohttp_client.async_get_clientsession( hass, family=socket.AF_INET ) @@ -318,6 +319,7 @@ async def _get_devices_by_contract_id(self, contract_id) -> list[Device]: _LOGGER.exception( f"Failed fetching devices by contract {contract_id}", e ) + return [] return devices async def _get_devices_by_device_id(self, meter_id) -> Devices: @@ -1097,220 +1099,375 @@ async def _insert_statistics(self, contract_id: int, is_smart_meter: bool) -> No # Support only smart meters at the moment return - _LOGGER.debug( - f"[IEC Statistics] Updating statistics for IEC Contract {contract_id}" - ) - devices = await self._get_devices_by_contract_id(contract_id) - kwh_price = await self._get_kwh_tariff() - localized_today = localize_datetime(datetime.now()) - - if not devices: - _LOGGER.error( - f"[IEC Statistics] Failed fetching devices for IEC Contract {contract_id}" + async with self._statistics_lock: + _LOGGER.debug( + f"[IEC Statistics] Updating statistics for IEC Contract {contract_id}" ) - return + devices = await self._get_devices_by_contract_id(contract_id) + kwh_price = await self._get_kwh_tariff() + localized_today = localize_datetime(datetime.now()) - for device in devices: - id_prefix = f"iec_meter_{device.device_number}" - consumption_statistic_id = f"{DOMAIN}:{id_prefix}_energy_consumption" - cost_statistic_id = f"{DOMAIN}:{id_prefix}_energy_est_cost" + if not devices: + _LOGGER.error( + f"[IEC Statistics] Failed fetching devices for IEC Contract {contract_id}" + ) + return - last_stat = await get_instance(self.hass).async_add_executor_job( - get_last_statistics, self.hass, 1, consumption_statistic_id, True, set() - ) + for device in devices: + device_number = str(int(device.device_number)) + id_prefix = f"iec_meter_{device_number}" + consumption_statistic_id = f"{DOMAIN}:{id_prefix}_energy_consumption" + cost_statistic_id = f"{DOMAIN}:{id_prefix}_energy_est_cost" - if not last_stat: - _LOGGER.debug( - "[IEC Statistics] No statistics found, fetching today's MONTHLY readings to extract field `meterStartDate`" + last_stat = await get_instance(self.hass).async_add_executor_job( + get_last_statistics, self.hass, 1, consumption_statistic_id, True, set() ) - month_ago_time = localized_today - timedelta(weeks=4) - readings = await self._get_readings( - contract_id, - device.device_number, - device.device_code, - localized_today, - ReadingResolution.MONTHLY, - ) + if not last_stat: + _LOGGER.debug( + "[IEC Statistics] No statistics found, fetching today's MONTHLY readings to extract field `meterStartDate`" + ) - if ( - readings - and readings.meter_list - and readings.meter_list[0].meter_start_date - ): - # Fetching the last reading from either the installation date or a month ago - month_ago_time = max( + month_ago_time = localized_today - timedelta(weeks=4) + readings = await self._get_readings( + contract_id, + device.device_number, + device.device_code, + localized_today, + ReadingResolution.MONTHLY, + ) + + if ( + readings + and readings.meter_list + and readings.meter_list[0].meter_start_date + ): + # Fetching the last reading from either the installation date or a month ago + month_ago_time = max( + month_ago_time, + localize_datetime( + datetime.combine( + readings.meter_list[0].meter_start_date, + datetime.min.time(), + ) + ), + ) + else: + _LOGGER.debug( + "[IEC Statistics] Failed to extract field `meterStartDate`, falling back to a month ago" + ) + + _LOGGER.debug("[IEC Statistics] Updating statistic for the first time") + _LOGGER.debug( + f"[IEC Statistics] Fetching consumption from {month_ago_time.strftime('%Y-%m-%d %H:%M:%S')}" + ) + last_stat_time = 0 + readings = await self._get_readings( + contract_id, + device.device_number, + device.device_code, month_ago_time, - localize_datetime( - datetime.combine( - readings.meter_list[0].meter_start_date, - datetime.min.time(), - ) - ), + ReadingResolution.DAILY, ) + else: + last_stat_time = last_stat[consumption_statistic_id][0]["start"] + # API returns daily data, so need to increase the start date by 4 hrs to get the next day + from_date = localize_datetime(datetime.fromtimestamp(last_stat_time)) + _LOGGER.debug( + f"[IEC Statistics] Last statistics are from {from_date.strftime('%Y-%m-%d %H:%M:%S')}" + ) + + if from_date.hour == 23: + from_date = from_date + timedelta(hours=2) + + if localized_today.date() == from_date.date(): + _LOGGER.debug( + "[IEC Statistics] The date to fetch is today or later, replacing it with Today at 01:00:00" + ) + from_date = localized_today.replace( + hour=1, minute=0, second=0, microsecond=0 + ) + + min_from_date = (localized_today - timedelta(days=30)).replace( + hour=1, minute=0, second=0, microsecond=0 + ) + if from_date < min_from_date: + _LOGGER.debug( + "[IEC Statistics] Last statistics are too old, limiting fetch window to %s", + min_from_date.strftime("%Y-%m-%d %H:%M:%S"), + ) + from_date = min_from_date + _LOGGER.debug( - "[IEC Statistics] Failed to extract field `meterStartDate`, falling back to a month ago" + f"[IEC Statistics] Fetching consumption from {from_date.strftime('%Y-%m-%d %H:%M:%S')}" ) + readings = await self._get_readings( + contract_id, + device.device_number, + device.device_code, + from_date, + ReadingResolution.DAILY, + ) + if from_date.date() == localized_today.date(): + self._today_readings[ + str(contract_id) + "-" + device.device_number + ] = readings + + if ( + not readings + or not readings.meter_list + or not len(readings.meter_list) > 0 + or not readings.meter_list[0].period_consumptions + or not len(readings.meter_list[0].period_consumptions) > 0 + ): + _LOGGER.debug("[IEC Statistics] No recent usage data. Skipping update") + continue + + last_stat_hour = ( + localize_datetime(datetime.fromtimestamp(last_stat_time)) + if last_stat_time + else readings.meter_list[0].period_consumptions[0].interval + ) + last_stat_req_hour = ( + last_stat_hour + if last_stat_hour.hour > 0 + else (last_stat_hour - timedelta(hours=1)) + ) - _LOGGER.debug("[IEC Statistics] Updating statistic for the first time") _LOGGER.debug( - f"[IEC Statistics] Fetching consumption from {month_ago_time.strftime('%Y-%m-%d %H:%M:%S')}" + f"[IEC Statistics] Fetching LongTerm Statistics since {last_stat_req_hour}" ) - last_stat_time = 0 - readings = await self._get_readings( - contract_id, - device.device_number, - device.device_code, - month_ago_time, - ReadingResolution.DAILY, + stats = await get_instance(self.hass).async_add_executor_job( + statistics_during_period, + self.hass, + last_stat_req_hour, + None, + {cost_statistic_id, consumption_statistic_id}, + "hour", + None, + {"sum"}, ) - else: - last_stat_time = last_stat[consumption_statistic_id][0]["start"] - # API returns daily data, so need to increase the start date by 4 hrs to get the next day - from_date = localize_datetime(datetime.fromtimestamp(last_stat_time)) + if not stats.get(consumption_statistic_id): + _LOGGER.debug("[IEC Statistics] No recent usage data") + consumption_sum = 0 + else: + consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"]) + + if not stats.get(cost_statistic_id): + if not stats.get(consumption_statistic_id): + _LOGGER.debug("[IEC Statistics] No recent cost data") + cost_sum = 0.0 + else: + cost_sum = ( + cast(float, stats[consumption_statistic_id][0]["sum"]) + * kwh_price + ) + else: + cost_sum = cast(float, stats[cost_statistic_id][0]["sum"]) + _LOGGER.debug( - f"[IEC Statistics] Last statistics are from {from_date.strftime('%Y-%m-%d %H:%M:%S')}" + f"[IEC Statistics] Last Consumption Sum for C[{contract_id}] D[{device.device_number}]: {consumption_sum}" + ) + _LOGGER.debug( + f"[IEC Statistics] Last Estimated Cost Sum for C[{contract_id}] D[{device.device_number}]: {cost_sum}" ) - if from_date.hour == 23: - from_date = from_date + timedelta(hours=2) + new_readings: list[PeriodConsumption] = [ + reading + for reading in readings.meter_list[0].period_consumptions + if reading.interval + >= localize_datetime(datetime.fromtimestamp(last_stat_time)) + ] - if localized_today.date() == from_date.date(): - _LOGGER.debug( - "[IEC Statistics] The date to fetch is today or later, replacing it with Today at 01:00:00" - ) - from_date = localized_today.replace( - hour=1, minute=0, second=0, microsecond=0 + grouped_new_readings_by_hour = itertools.groupby( + new_readings, + key=lambda reading: reading.interval.replace( + minute=0, second=0, microsecond=0 + ), + ) + readings_by_hour: dict[datetime, float] = {} + if last_stat_req_hour and last_stat_req_hour.tzinfo is None: + last_stat_req_hour = localize_datetime(last_stat_req_hour) + + for key, group in grouped_new_readings_by_hour: + group_list = list(group) + # Apply 4 listings per hour check only for days less than 1 month old + one_month_ago = localized_today - timedelta(days=30) + if key.date() >= one_month_ago.date() and len(group_list) < 4: + _LOGGER.debug( + f"[IEC Statistics] LongTerm Statistics - Skipping {key} since it's partial for the hour " + f"(data is less than 1 month old and has only {len(group_list)} readings)" + ) + continue + if key <= last_stat_req_hour: + _LOGGER.debug( + f"[IEC Statistics] LongTerm Statistics - Skipping {key} data since it's already reported" + ) + continue + readings_by_hour[key] = sum( + reading.consumption for reading in group_list ) - min_from_date = (localized_today - timedelta(days=30)).replace( - hour=1, minute=0, second=0, microsecond=0 + consumption_metadata = StatisticMetaData( + has_mean=False, + has_sum=True, + name=f"IEC Meter {device.device_number} Consumption", + source=DOMAIN, + statistic_id=consumption_statistic_id, + unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + mean_type=StatisticMeanType.NONE, + ) + + cost_metadata = StatisticMetaData( + has_mean=False, + has_sum=True, + name=f"IEC Meter {device.device_number} Estimated Cost", + source=DOMAIN, + statistic_id=cost_statistic_id, + unit_of_measurement=ILS, + mean_type=StatisticMeanType.NONE, ) - if from_date < min_from_date: + + consumption_statistics = [] + cost_statistics = [] + for key, value in sorted(readings_by_hour.items()): + consumption_sum += value + cost_sum += value * kwh_price + + consumption_statistics.append( + StatisticData(start=key, sum=consumption_sum, state=value) + ) + + cost_statistics.append( + StatisticData(start=key, sum=cost_sum, state=value * kwh_price) + ) + + if readings_by_hour: + _LOGGER.debug( + f"[IEC Statistics] Last hour fetched for C[{contract_id}] D[{device.device_number}]: " + f"{max(readings_by_hour, key=lambda k: k)}" + ) _LOGGER.debug( - "[IEC Statistics] Last statistics are too old, limiting fetch window to %s", - min_from_date.strftime("%Y-%m-%d %H:%M:%S"), + f"[IEC Statistics] New Consumption Sum for C[{contract_id}] D[{device.device_number}]: {consumption_sum}" + ) + _LOGGER.debug( + f"[IEC Statistics] New Estimated Cost Sum for C[{contract_id}] D[{device.device_number}]: {cost_sum}" ) - from_date = min_from_date - _LOGGER.debug( - f"[IEC Statistics] Fetching consumption from {from_date.strftime('%Y-%m-%d %H:%M:%S')}" - ) - readings = await self._get_readings( - contract_id, - device.device_number, - device.device_code, - from_date, - ReadingResolution.DAILY, + async_add_external_statistics( + self.hass, consumption_metadata, consumption_statistics ) - if from_date.date() == localized_today.date(): - self._today_readings[ - str(contract_id) + "-" + device.device_number - ] = readings - if ( - not readings - or not readings.meter_list - or not len(readings.meter_list) > 0 - or not readings.meter_list[0].period_consumptions - or not len(readings.meter_list[0].period_consumptions) > 0 - ): - _LOGGER.debug("[IEC Statistics] No recent usage data. Skipping update") - continue + async_add_external_statistics(self.hass, cost_metadata, cost_statistics) - last_stat_hour = ( - localize_datetime(datetime.fromtimestamp(last_stat_time)) - if last_stat_time - else readings.meter_list[0].period_consumptions[0].interval - ) - last_stat_req_hour = ( - last_stat_hour - if last_stat_hour.hour > 0 - else (last_stat_hour - timedelta(hours=1)) - ) + async def set_statistics_from_date( + self, datetime_str: str, device_number: str + ) -> dict[str, Any]: + """Insert a zero-value statistic record to advance the statistics fetch point.""" + # Normalize device number (strip leading zeros) + try: + device_number = str(int(device_number)) + except (ValueError, TypeError): + return {"success": False, "error": "Invalid device number format"} - _LOGGER.debug( - f"[IEC Statistics] Fetching LongTerm Statistics since {last_stat_req_hour}" + # Parse datetime + try: + target_dt = localize_datetime(datetime.fromisoformat(datetime_str)) + except (ValueError, TypeError): + return {"success": False, "error": "Invalid datetime format, use ISO format (e.g. 2024-01-16T01:00:00)"} + + now = localize_datetime(datetime.now()) + + # Validate: not in the future + if target_dt > now: + return {"success": False, "error": "Date must not be in the future"} + + # Validate: not older than 30 days + min_date = (now - timedelta(days=30)).replace(hour=1, minute=0, second=0, microsecond=0) + if target_dt < min_date: + return { + "success": False, + "error": f"Date must not be older than {min_date.strftime('%Y-%m-%d %H:%M:%S')}", + } + + # Validate: hour-aligned (minute and second must be 0) + if target_dt.minute != 0 or target_dt.second != 0: + return {"success": False, "error": "Date must be hour-aligned (minute and second must be 0)"} + + # Validate: device_number belongs to a contract + device_found = False + for contract_id in self._contract_ids: + devices = await self._get_devices_by_contract_id(contract_id) + if not devices: + continue + for device in devices: + if str(int(device.device_number)) == device_number: + device_found = True + break + if device_found: + break + + if not device_found: + return {"success": False, "error": f"Device number {device_number} not found in any contract"} + + consumption_statistic_id = f"{DOMAIN}:iec_meter_{device_number}_energy_consumption" + cost_statistic_id = f"{DOMAIN}:iec_meter_{device_number}_energy_est_cost" + + # Acquire lock to prevent race conditions with _insert_statistics + async with self._statistics_lock: + # Re-check: get last statistics + last_stat = await get_instance(self.hass).async_add_executor_job( + get_last_statistics, self.hass, 1, consumption_statistic_id, True, set() ) - stats = await get_instance(self.hass).async_add_executor_job( + + if last_stat and consumption_statistic_id in last_stat: + last_stats_dt = localize_datetime( + datetime.fromtimestamp(last_stat[consumption_statistic_id][0]["start"]) + ) + if target_dt <= last_stats_dt: + return { + "success": False, + "error": f"New date must be after current last statistics ({last_stats_dt.strftime('%Y-%m-%d %H:%M:%S')})", + } + + # Check if valid stats already exist for the target hour + hour_end = target_dt + timedelta(hours=1) + existing_stats = await get_instance(self.hass).async_add_executor_job( statistics_during_period, self.hass, - last_stat_req_hour, - None, - {cost_statistic_id, consumption_statistic_id}, + target_dt, + hour_end, + {consumption_statistic_id, cost_statistic_id}, "hour", None, {"sum"}, ) - if not stats.get(consumption_statistic_id): - _LOGGER.debug("[IEC Statistics] No recent usage data") - consumption_sum = 0 - else: - consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"]) - - if not stats.get(cost_statistic_id): - if not stats.get(consumption_statistic_id): - _LOGGER.debug("[IEC Statistics] No recent cost data") - cost_sum = 0.0 - else: - cost_sum = ( - cast(float, stats[consumption_statistic_id][0]["sum"]) - * kwh_price - ) - else: - cost_sum = cast(float, stats[cost_statistic_id][0]["sum"]) - - _LOGGER.debug( - f"[IEC Statistics] Last Consumption Sum for C[{contract_id}] D[{device.device_number}]: {consumption_sum}" - ) - _LOGGER.debug( - f"[IEC Statistics] Last Estimated Cost Sum for C[{contract_id}] D[{device.device_number}]: {cost_sum}" - ) - - new_readings: list[PeriodConsumption] = filter( - lambda reading: ( - reading.interval - >= localize_datetime(datetime.fromtimestamp(last_stat_time)) - ), - readings.meter_list[0].period_consumptions, - ) + if existing_stats.get(consumption_statistic_id): + return { + "success": False, + "error": f"Valid statistics already exist at {target_dt.strftime('%Y-%m-%d %H:%M:%S')}", + } - grouped_new_readings_by_hour = itertools.groupby( - new_readings, - key=lambda reading: reading.interval.replace( - minute=0, second=0, microsecond=0 - ), - ) - readings_by_hour: dict[datetime, float] = {} - if last_stat_req_hour and last_stat_req_hour.tzinfo is None: - last_stat_req_hour = localize_datetime(last_stat_req_hour) - - for key, group in grouped_new_readings_by_hour: - group_list = list(group) - # Apply 4 listings per hour check only for days less than 1 month old - one_month_ago = localized_today - timedelta(days=30) - if key.date() >= one_month_ago.date() and len(group_list) < 4: - _LOGGER.debug( - f"[IEC Statistics] LongTerm Statistics - Skipping {key} since it's partial for the hour " - f"(data is less than 1 month old and has only {len(group_list)} readings)" - ) - continue - if key <= last_stat_req_hour: - _LOGGER.debug( - f"[IEC Statistics] LongTerm Statistics - Skipping {key} data since it's already reported" - ) - continue - readings_by_hour[key] = sum( - reading.consumption for reading in group_list + # Get existing sum values to carry forward + existing_consumption_sum = 0.0 + existing_cost_sum = 0.0 + if last_stat and consumption_statistic_id in last_stat: + existing_consumption_sum = cast( + float, last_stat[consumption_statistic_id][0].get("sum", 0) + ) + if last_stat and cost_statistic_id in last_stat: + existing_cost_sum = cast( + float, last_stat[cost_statistic_id][0].get("sum", 0) ) + # Insert zero-value statistics consumption_metadata = StatisticMetaData( has_mean=False, has_sum=True, - name=f"IEC Meter {device.device_number} Consumption", + name=f"IEC Meter {device_number} Consumption", source=DOMAIN, statistic_id=consumption_statistic_id, unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1320,44 +1477,35 @@ async def _insert_statistics(self, contract_id: int, is_smart_meter: bool) -> No cost_metadata = StatisticMetaData( has_mean=False, has_sum=True, - name=f"IEC Meter {device.device_number} Estimated Cost", + name=f"IEC Meter {device_number} Estimated Cost", source=DOMAIN, statistic_id=cost_statistic_id, unit_of_measurement=ILS, mean_type=StatisticMeanType.NONE, ) - consumption_statistics = [] - cost_statistics = [] - for key, value in sorted(readings_by_hour.items()): - consumption_sum += value - cost_sum += value * kwh_price - - consumption_statistics.append( - StatisticData(start=key, sum=consumption_sum, state=value) - ) - - cost_statistics.append( - StatisticData(start=key, sum=cost_sum, state=value * kwh_price) - ) - - if readings_by_hour: - _LOGGER.debug( - f"[IEC Statistics] Last hour fetched for C[{contract_id}] D[{device.device_number}]: " - f"{max(readings_by_hour, key=lambda k: k)}" - ) - _LOGGER.debug( - f"[IEC Statistics] New Consumption Sum for C[{contract_id}] D[{device.device_number}]: {consumption_sum}" - ) - _LOGGER.debug( - f"[IEC Statistics] New Estimated Cost Sum for C[{contract_id}] D[{device.device_number}]: {cost_sum}" - ) - async_add_external_statistics( - self.hass, consumption_metadata, consumption_statistics + self.hass, + consumption_metadata, + [StatisticData(start=target_dt, sum=existing_consumption_sum, state=0)], + ) + async_add_external_statistics( + self.hass, + cost_metadata, + [StatisticData(start=target_dt, sum=existing_cost_sum, state=0)], ) - async_add_external_statistics(self.hass, cost_metadata, cost_statistics) + _LOGGER.info( + "[IEC Statistics] Inserted zero-value statistic at %s for device %s", + target_dt.strftime("%Y-%m-%d %H:%M:%S"), + device_number, + ) + + return { + "success": True, + "device_number": device_number, + "datetime": target_dt.isoformat(), + } async def _estimate_bill( self, diff --git a/custom_components/iec/services.yaml b/custom_components/iec/services.yaml index 273a422..a8b485b 100644 --- a/custom_components/iec/services.yaml +++ b/custom_components/iec/services.yaml @@ -1,3 +1,17 @@ debug_get_coordinator_data: description: "Fetch and return the coordinator data for debugging purposes." - fields: {} \ No newline at end of file + fields: {} + +update_statistics_date: + description: "Manually update the statistics fetch start date when stuck on a partial hour." + fields: + datetime: + description: "The datetime to start fetching statistics from (ISO format, e.g. 2024-01-16T01:00:00)." + required: true + selector: + datetime: + device_number: + description: "The device number (meter ID) to update statistics for." + required: true + selector: + text: \ No newline at end of file diff --git a/custom_components/iec/translations/en.json b/custom_components/iec/translations/en.json index c13adbb..bf6af73 100644 --- a/custom_components/iec/translations/en.json +++ b/custom_components/iec/translations/en.json @@ -67,6 +67,20 @@ "debug_get_coordinator_data": { "name": "Get IEC Coordinator Data", "description": "Fetch and return the coordinator data for debugging purposes." + }, + "update_statistics_date": { + "name": "Update Statistics Date", + "description": "Manually update the statistics fetch start date when stuck on a partial hour.", + "fields": { + "datetime": { + "name": "Start Date/Time", + "description": "The datetime to start fetching statistics from (ISO format)." + }, + "device_number": { + "name": "Device Number", + "description": "The device number (meter ID) to update statistics for." + } + } } }, "config": { diff --git a/custom_components/iec/translations/he.json b/custom_components/iec/translations/he.json index 1748ef0..9b23b20 100644 --- a/custom_components/iec/translations/he.json +++ b/custom_components/iec/translations/he.json @@ -67,6 +67,20 @@ "debug_get_coordinator_data": { "name": "הדפס מידע במערכת מחברת החשמל", "description": "הדפס מידע שנטען מחב' חשמל לצורך ניפוי שגיאות" + }, + "update_statistics_date": { + "name": "עדכן תאריך סטטיסטיקות", + "description": "עדכן ידנית את תאריך התחלת שליפת הסטטיסטיקות כאשר הנתונים תקועים בשעה חלקית.", + "fields": { + "datetime": { + "name": "תאריך/שעה התחלה", + "description": "התאריך והשעה להתחיל מהם שליפת סטטיסטיקות (פורמט ISO)." + }, + "device_number": { + "name": "מספר מונה", + "description": "מספר המונה לעדכון הסטטיסטיקות עבורו." + } + } } }, "config": {