Skip to content

Commit 6fa1924

Browse files
SantaFoxclaude
andcommitted
0.1.2: options flow for poll interval + history window
Users can now reach Settings → Devices & Services → ⋮ → Configure to change two runtime knobs that were previously hard-coded: * Polling interval — dropdown of 30 min / 1h / 3h / 6h (default) / 12h / 24h. Pushed onto the coordinator in-place via add_update_listener, so changes take effect on the next tick without reloading the entry. * History window (1–60 days) — how far back the coordinator pulls on every refresh. Read fresh from entry.options each poll, no listener needed. A description in the form warns the user that polling more often than 6 hours does not produce a smoother Energy Dashboard graph: the EAC portal updates daily and 30-min profile data already lags by hours. The real fix for graph density is statistics import (planned). Coordinator no longer uses const-level SCAN_INTERVAL / HISTORY_DAYS; those are replaced by defaults + per-entry options. Bumps manifest version 0.1.1 → 0.1.2. EN and RU translations updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e6d344e commit 6fa1924

9 files changed

Lines changed: 293 additions & 15 deletions

File tree

custom_components/eac_cyprus/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
3333

3434
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
3535
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
36+
entry.async_on_unload(entry.add_update_listener(_async_options_updated))
3637
return True
3738

3839

40+
async def _async_options_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
41+
"""Apply changes from the options flow without reloading the entry."""
42+
coordinator: EacCoordinator = hass.data[DOMAIN][entry.entry_id]
43+
coordinator.apply_options()
44+
45+
3946
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4047
"""Unload a config entry."""
4148
unloaded = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

custom_components/eac_cyprus/config_flow.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,29 @@
77
import aiohttp
88
import voluptuous as vol
99

10-
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
10+
from homeassistant.config_entries import (
11+
ConfigEntry,
12+
ConfigFlow,
13+
ConfigFlowResult,
14+
OptionsFlow,
15+
)
16+
from homeassistant.core import callback
17+
from homeassistant.helpers import selector
1118
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1219

1320
from eac_dso_portal import EacApiError, EacAuthError, EacClient
14-
from .const import CONF_BASE_URL, CONF_EMAIL, CONF_PASSWORD, DEFAULT_BASE_URL, DOMAIN
21+
from .const import (
22+
CONF_BASE_URL,
23+
CONF_EMAIL,
24+
CONF_HISTORY_DAYS,
25+
CONF_PASSWORD,
26+
CONF_SCAN_INTERVAL_MINUTES,
27+
DEFAULT_BASE_URL,
28+
DEFAULT_HISTORY_DAYS,
29+
DEFAULT_SCAN_INTERVAL_MINUTES,
30+
DOMAIN,
31+
SCAN_INTERVAL_CHOICES,
32+
)
1533

1634
_LOGGER = logging.getLogger(__name__)
1735

@@ -29,10 +47,17 @@ class EacConfigFlow(ConfigFlow, domain=DOMAIN):
2947
3048
The portal account is per-email and unique; we use the email as the unique
3149
id of the config entry, preventing duplicate entries for the same account.
50+
Editable runtime settings (poll interval, history window) live in the
51+
options flow below.
3252
"""
3353

3454
VERSION = 1
3555

56+
@staticmethod
57+
@callback
58+
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
59+
return EacOptionsFlow()
60+
3661
async def async_step_user(
3762
self, user_input: dict[str, Any] | None = None
3863
) -> ConfigFlowResult:
@@ -74,3 +99,64 @@ async def async_step_user(
7499
return self.async_show_form(
75100
step_id="user", data_schema=STEP_USER_SCHEMA, errors=errors
76101
)
102+
103+
104+
# Human labels for the polling-interval dropdown.
105+
_INTERVAL_LABELS: dict[int, str] = {
106+
30: "30 minutes",
107+
60: "1 hour",
108+
180: "3 hours",
109+
360: "6 hours (recommended)",
110+
720: "12 hours",
111+
1440: "24 hours",
112+
}
113+
114+
115+
class EacOptionsFlow(OptionsFlow):
116+
"""Editable runtime settings: polling cadence and history window."""
117+
118+
async def async_step_init(
119+
self, user_input: dict[str, Any] | None = None
120+
) -> ConfigFlowResult:
121+
if user_input is not None:
122+
# The select selector hands back strings; coerce to int once.
123+
user_input[CONF_SCAN_INTERVAL_MINUTES] = int(
124+
user_input[CONF_SCAN_INTERVAL_MINUTES]
125+
)
126+
return self.async_create_entry(title="", data=user_input)
127+
128+
current = self.config_entry.options
129+
current_interval = current.get(
130+
CONF_SCAN_INTERVAL_MINUTES, DEFAULT_SCAN_INTERVAL_MINUTES
131+
)
132+
current_history = current.get(CONF_HISTORY_DAYS, DEFAULT_HISTORY_DAYS)
133+
134+
schema = vol.Schema(
135+
{
136+
vol.Required(
137+
CONF_SCAN_INTERVAL_MINUTES, default=str(current_interval)
138+
): selector.SelectSelector(
139+
selector.SelectSelectorConfig(
140+
options=[
141+
selector.SelectOptionDict(
142+
value=str(m), label=_INTERVAL_LABELS[m]
143+
)
144+
for m in SCAN_INTERVAL_CHOICES
145+
],
146+
mode=selector.SelectSelectorMode.DROPDOWN,
147+
),
148+
),
149+
vol.Required(
150+
CONF_HISTORY_DAYS, default=current_history
151+
): selector.NumberSelector(
152+
selector.NumberSelectorConfig(
153+
min=1,
154+
max=60,
155+
step=1,
156+
mode=selector.NumberSelectorMode.BOX,
157+
unit_of_measurement="days",
158+
),
159+
),
160+
}
161+
)
162+
return self.async_show_form(step_id="init", data_schema=schema)
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
"""Constants for the EAC Cyprus integration."""
22
from __future__ import annotations
33

4-
from datetime import timedelta
5-
64
DOMAIN = "eac_cyprus"
75

86
CONF_EMAIL = "email"
97
CONF_PASSWORD = "password"
108
CONF_BASE_URL = "base_url"
119

10+
# Runtime-tunable options (config entry .options dict).
11+
CONF_SCAN_INTERVAL_MINUTES = "scan_interval_minutes"
12+
CONF_HISTORY_DAYS = "history_days"
13+
1214
DEFAULT_BASE_URL = "https://meterreading-dso.eac.com.cy"
1315

14-
# Smart-meter daily values arrive with several hours of lag from the DSO.
15-
# Polling more often gains nothing, so we stay polite.
16-
SCAN_INTERVAL = timedelta(hours=6)
16+
# 6h is plenty: the EAC portal updates daily and 30-min profile data lags by
17+
# hours, so polling more often gains nothing in practice.
18+
DEFAULT_SCAN_INTERVAL_MINUTES = 360
19+
20+
# Pulling a few days of history on every refresh lets us recover gracefully
21+
# from missed reports (e.g. the integration was down or network blipped).
22+
DEFAULT_HISTORY_DAYS = 14
1723

18-
# How much history to fetch on every refresh. The cumulative meter reading is
19-
# used as the sensor state, so we only need the latest value, but pulling a
20-
# few days lets us recover gracefully from missed reports.
21-
HISTORY_DAYS = 14
24+
# Values offered in the options-flow dropdown (minutes).
25+
SCAN_INTERVAL_CHOICES: tuple[int, ...] = (30, 60, 180, 360, 720, 1440)
2226

2327
MANUFACTURER = "EAC (Electricity Authority of Cyprus)"

custom_components/eac_cyprus/coordinator.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@
2020
ServicePoint,
2121
UserDetails,
2222
)
23-
from .const import DOMAIN, HISTORY_DAYS, SCAN_INTERVAL
23+
from .const import (
24+
CONF_HISTORY_DAYS,
25+
CONF_SCAN_INTERVAL_MINUTES,
26+
DEFAULT_HISTORY_DAYS,
27+
DEFAULT_SCAN_INTERVAL_MINUTES,
28+
DOMAIN,
29+
)
2430

2531
_LOGGER = logging.getLogger(__name__)
2632

@@ -56,11 +62,20 @@ def __init__(self, hass: HomeAssistant, client: EacClient, entry: ConfigEntry) -
5662
hass,
5763
_LOGGER,
5864
name=f"{DOMAIN} ({entry.title})",
59-
update_interval=SCAN_INTERVAL,
65+
update_interval=_interval_from_options(entry),
6066
)
6167
self._client = client
6268
self._entry = entry
6369

70+
def apply_options(self) -> None:
71+
"""Re-read the config entry's options and adjust polling cadence.
72+
73+
Called by the options-flow update listener. ``history_days`` is read
74+
on every refresh, so only the poll interval needs to be propagated
75+
here. The new value takes effect on the next scheduled tick.
76+
"""
77+
self.update_interval = _interval_from_options(self._entry)
78+
6479
async def _async_update_data(self) -> EacData:
6580
try:
6681
user = await self._client.get_user_details()
@@ -71,7 +86,8 @@ async def _async_update_data(self) -> EacData:
7186
raise UpdateFailed(f"API error: {err}") from err
7287

7388
end = datetime.now(UTC)
74-
start = end - timedelta(days=HISTORY_DAYS)
89+
history_days = self._entry.options.get(CONF_HISTORY_DAYS, DEFAULT_HISTORY_DAYS)
90+
start = end - timedelta(days=history_days)
7591

7692
points: dict[str, ServicePointState] = {}
7793
for sp in sps:
@@ -123,3 +139,10 @@ def _latest_reading(channels: list[ChannelReadings], channel_id: str) -> Reading
123139
continue
124140
return max(cr.readings, key=lambda r: r.dt)
125141
return None
142+
143+
144+
def _interval_from_options(entry: ConfigEntry) -> timedelta:
145+
minutes = entry.options.get(
146+
CONF_SCAN_INTERVAL_MINUTES, DEFAULT_SCAN_INTERVAL_MINUTES
147+
)
148+
return timedelta(minutes=int(minutes))

custom_components/eac_cyprus/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"iot_class": "cloud_polling",
1010
"issue_tracker": "https://github.com/SantaFox/ha-eac/issues",
1111
"requirements": ["eac-dso-portal==0.1.0"],
12-
"version": "0.1.1"
12+
"version": "0.1.2"
1313
}

custom_components/eac_cyprus/strings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,17 @@
1919
"abort": {
2020
"already_configured": "This account is already configured."
2121
}
22+
},
23+
"options": {
24+
"step": {
25+
"init": {
26+
"title": "EAC settings",
27+
"description": "Heads-up: the portal updates daily and 30-min profile data lags by hours. Polling more often than 6 hours does not give a smoother Energy Dashboard graph — for that you need statistics import (planned).",
28+
"data": {
29+
"scan_interval_minutes": "Polling interval",
30+
"history_days": "History window (days fetched on each refresh)"
31+
}
32+
}
33+
}
2234
}
2335
}

custom_components/eac_cyprus/translations/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,17 @@
1919
"abort": {
2020
"already_configured": "This account is already configured."
2121
}
22+
},
23+
"options": {
24+
"step": {
25+
"init": {
26+
"title": "EAC settings",
27+
"description": "Heads-up: the portal updates daily and 30-min profile data lags by hours. Polling more often than 6 hours does not give a smoother Energy Dashboard graph — for that you need statistics import (planned).",
28+
"data": {
29+
"scan_interval_minutes": "Polling interval",
30+
"history_days": "History window (days fetched on each refresh)"
31+
}
32+
}
33+
}
2234
}
2335
}

custom_components/eac_cyprus/translations/ru.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,17 @@
1919
"abort": {
2020
"already_configured": "Этот аккаунт уже добавлен."
2121
}
22+
},
23+
"options": {
24+
"step": {
25+
"init": {
26+
"title": "Настройки EAC",
27+
"description": "Имейте в виду: портал обновляет данные раз в сутки, а 30-минутный профиль приходит с задержкой в часы. Опрос чаще 6 часов не сделает график Energy Dashboard плотнее — для этого нужен импорт statistics (в плане).",
28+
"data": {
29+
"scan_interval_minutes": "Интервал опроса",
30+
"history_days": "Окно истории (за сколько дней тянуть на каждом опросе)"
31+
}
32+
}
33+
}
2234
}
2335
}

0 commit comments

Comments
 (0)