Skip to content

Commit 8b97a2f

Browse files
authored
Merge pull request #230 from nickknissen/claude/add-variable-scan-intervals-011CUpWPjfACpCGVaXbsjYVm
Add variable scan intervals for different API endpoints
2 parents 972aae2 + 3f4a6b5 commit 8b97a2f

File tree

9 files changed

+310
-149
lines changed

9 files changed

+310
-149
lines changed

custom_components/monta/__init__.py

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,22 @@
1212
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1313

1414
from .api import MontaApiClient
15-
from .const import CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, DOMAIN, STORAGE_KEY, STORAGE_VERSION
16-
from .coordinator import MontaDataUpdateCoordinator
15+
from .const import (
16+
CONF_SCAN_INTERVAL_CHARGE_POINTS,
17+
CONF_SCAN_INTERVAL_WALLET,
18+
CONF_SCAN_INTERVAL_TRANSACTIONS,
19+
DEFAULT_SCAN_INTERVAL_CHARGE_POINTS,
20+
DEFAULT_SCAN_INTERVAL_WALLET,
21+
DEFAULT_SCAN_INTERVAL_TRANSACTIONS,
22+
DOMAIN,
23+
STORAGE_KEY,
24+
STORAGE_VERSION,
25+
)
26+
from .coordinator import (
27+
MontaChargePointCoordinator,
28+
MontaWalletCoordinator,
29+
MontaTransactionCoordinator,
30+
)
1731
from .services import async_setup_services
1832

1933
PLATFORMS: list[Platform] = [
@@ -31,24 +45,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
3145
store = Store(hass, STORAGE_VERSION, STORAGE_KEY)
3246
await store.async_remove()
3347

34-
# Get scan interval from options or data, with default fallback
35-
scan_interval = entry.options.get(
36-
CONF_SCAN_INTERVAL,
37-
entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
48+
# Get individual scan intervals for each data type
49+
scan_interval_charge_points = entry.options.get(
50+
CONF_SCAN_INTERVAL_CHARGE_POINTS,
51+
entry.data.get(CONF_SCAN_INTERVAL_CHARGE_POINTS, DEFAULT_SCAN_INTERVAL_CHARGE_POINTS),
52+
)
53+
scan_interval_wallet = entry.options.get(
54+
CONF_SCAN_INTERVAL_WALLET,
55+
entry.data.get(CONF_SCAN_INTERVAL_WALLET, DEFAULT_SCAN_INTERVAL_WALLET),
56+
)
57+
scan_interval_transactions = entry.options.get(
58+
CONF_SCAN_INTERVAL_TRANSACTIONS,
59+
entry.data.get(CONF_SCAN_INTERVAL_TRANSACTIONS, DEFAULT_SCAN_INTERVAL_TRANSACTIONS),
3860
)
3961

40-
hass.data[DOMAIN][entry.entry_id] = coordinator = MontaDataUpdateCoordinator(
62+
# Create API client shared by all coordinators
63+
client = MontaApiClient(
64+
client_id=entry.data[CONF_CLIENT_ID],
65+
client_secret=entry.data[CONF_CLIENT_SECRET],
66+
session=async_get_clientsession(hass),
67+
store=store,
68+
)
69+
70+
# Create separate coordinators for each data type
71+
charge_point_coordinator = MontaChargePointCoordinator(
72+
hass=hass,
73+
client=client,
74+
scan_interval=scan_interval_charge_points,
75+
)
76+
wallet_coordinator = MontaWalletCoordinator(
77+
hass=hass,
78+
client=client,
79+
scan_interval=scan_interval_wallet,
80+
)
81+
transaction_coordinator = MontaTransactionCoordinator(
4182
hass=hass,
42-
client=MontaApiClient(
43-
client_id=entry.data[CONF_CLIENT_ID],
44-
client_secret=entry.data[CONF_CLIENT_SECRET],
45-
session=async_get_clientsession(hass),
46-
store=store,
47-
),
48-
scan_interval=scan_interval,
83+
client=client,
84+
scan_interval=scan_interval_transactions,
4985
)
86+
87+
# Store coordinators in a dictionary
88+
hass.data[DOMAIN][entry.entry_id] = {
89+
"charge_point": charge_point_coordinator,
90+
"wallet": wallet_coordinator,
91+
"transaction": transaction_coordinator,
92+
}
93+
5094
# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
51-
await coordinator.async_config_entry_first_refresh()
95+
# Refresh all coordinators
96+
await charge_point_coordinator.async_config_entry_first_refresh()
97+
await wallet_coordinator.async_config_entry_first_refresh()
98+
await transaction_coordinator.async_config_entry_first_refresh()
5299

53100
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
54101

custom_components/monta/binary_sensor.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
)
1313
from homeassistant.helpers.entity import generate_entity_id
1414

15-
from .const import ATTR_CHARGE_POINTS, DOMAIN
16-
from .coordinator import MontaDataUpdateCoordinator
15+
from .const import DOMAIN
16+
from .coordinator import MontaChargePointCoordinator
1717
from .entity import MontaEntity
1818
from .utils import snake_case
1919

@@ -30,13 +30,14 @@
3030

3131
async def async_setup_entry(hass, entry, async_add_devices):
3232
"""Set up the binary_sensor platform."""
33-
coordinator = hass.data[DOMAIN][entry.entry_id]
33+
coordinators = hass.data[DOMAIN][entry.entry_id]
34+
charge_point_coordinator = coordinators["charge_point"]
3435

35-
for charge_point_id in coordinator.data[ATTR_CHARGE_POINTS]:
36+
for charge_point_id in charge_point_coordinator.data:
3637
async_add_devices(
3738
[
3839
MontaBinarySensor(
39-
coordinator=coordinator,
40+
coordinator=charge_point_coordinator,
4041
entity_description=entity_description,
4142
charge_point_id=charge_point_id,
4243
)
@@ -50,7 +51,7 @@ class MontaBinarySensor(MontaEntity, BinarySensorEntity):
5051

5152
def __init__(
5253
self,
53-
coordinator: MontaDataUpdateCoordinator,
54+
coordinator: MontaChargePointCoordinator,
5455
entity_description: BinarySensorEntityDescription,
5556
charge_point_id: int,
5657
) -> None:
@@ -67,6 +68,6 @@ def __init__(
6768
@property
6869
def is_on(self) -> bool:
6970
"""Return true if the binary_sensor is on."""
70-
return self.coordinator.data[ATTR_CHARGE_POINTS][self.charge_point_id].get(
71+
return self.coordinator.data[self.charge_point_id].get(
7172
self.entity_description.key, False
7273
)

custom_components/monta/config_flow.py

Lines changed: 115 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,88 @@
1414
MontaApiClientCommunicationError,
1515
MontaApiClientError,
1616
)
17-
from .const import CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, STORAGE_KEY, STORAGE_VERSION
17+
from .const import (
18+
CONF_SCAN_INTERVAL,
19+
CONF_SCAN_INTERVAL_CHARGE_POINTS,
20+
CONF_SCAN_INTERVAL_WALLET,
21+
CONF_SCAN_INTERVAL_TRANSACTIONS,
22+
DEFAULT_SCAN_INTERVAL,
23+
DEFAULT_SCAN_INTERVAL_CHARGE_POINTS,
24+
DEFAULT_SCAN_INTERVAL_WALLET,
25+
DEFAULT_SCAN_INTERVAL_TRANSACTIONS,
26+
DOMAIN,
27+
LOGGER,
28+
STORAGE_KEY,
29+
STORAGE_VERSION,
30+
)
31+
32+
33+
def build_schema(defaults: dict) -> vol.Schema:
34+
"""Build the configuration schema with provided defaults."""
35+
return vol.Schema(
36+
{
37+
vol.Required(
38+
CONF_CLIENT_ID,
39+
default=defaults.get(CONF_CLIENT_ID),
40+
): selector.TextSelector(
41+
selector.TextSelectorConfig(
42+
type=selector.TextSelectorType.TEXT
43+
),
44+
),
45+
vol.Required(
46+
CONF_CLIENT_SECRET,
47+
default=defaults.get(CONF_CLIENT_SECRET),
48+
): selector.TextSelector(
49+
selector.TextSelectorConfig(
50+
type=selector.TextSelectorType.PASSWORD
51+
),
52+
),
53+
vol.Optional(
54+
CONF_SCAN_INTERVAL,
55+
default=defaults.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
56+
): selector.NumberSelector(
57+
selector.NumberSelectorConfig(
58+
min=30,
59+
max=3600,
60+
unit_of_measurement="seconds",
61+
mode=selector.NumberSelectorMode.BOX,
62+
),
63+
),
64+
vol.Optional(
65+
CONF_SCAN_INTERVAL_CHARGE_POINTS,
66+
default=defaults.get(CONF_SCAN_INTERVAL_CHARGE_POINTS, DEFAULT_SCAN_INTERVAL_CHARGE_POINTS),
67+
): selector.NumberSelector(
68+
selector.NumberSelectorConfig(
69+
min=30,
70+
max=3600,
71+
unit_of_measurement="seconds",
72+
mode=selector.NumberSelectorMode.BOX,
73+
),
74+
),
75+
vol.Optional(
76+
CONF_SCAN_INTERVAL_WALLET,
77+
default=defaults.get(CONF_SCAN_INTERVAL_WALLET, DEFAULT_SCAN_INTERVAL_WALLET),
78+
): selector.NumberSelector(
79+
selector.NumberSelectorConfig(
80+
min=30,
81+
max=7200,
82+
unit_of_measurement="seconds",
83+
mode=selector.NumberSelectorMode.BOX,
84+
),
85+
),
86+
vol.Optional(
87+
CONF_SCAN_INTERVAL_TRANSACTIONS,
88+
default=defaults.get(CONF_SCAN_INTERVAL_TRANSACTIONS, DEFAULT_SCAN_INTERVAL_TRANSACTIONS),
89+
): selector.NumberSelector(
90+
selector.NumberSelectorConfig(
91+
min=30,
92+
max=7200,
93+
unit_of_measurement="seconds",
94+
mode=selector.NumberSelectorMode.BOX,
95+
),
96+
),
97+
}
98+
)
1899

19100

20101
class MontaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@@ -51,34 +132,7 @@ async def async_step_user(
51132

52133
return self.async_show_form(
53134
step_id="user",
54-
data_schema=vol.Schema(
55-
{
56-
vol.Required(
57-
CONF_CLIENT_ID,
58-
default=(user_input or {}).get(CONF_CLIENT_ID),
59-
): selector.TextSelector(
60-
selector.TextSelectorConfig(
61-
type=selector.TextSelectorType.TEXT
62-
),
63-
),
64-
vol.Required(CONF_CLIENT_SECRET): selector.TextSelector(
65-
selector.TextSelectorConfig(
66-
type=selector.TextSelectorType.PASSWORD
67-
),
68-
),
69-
vol.Optional(
70-
CONF_SCAN_INTERVAL,
71-
default=(user_input or {}).get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
72-
): selector.NumberSelector(
73-
selector.NumberSelectorConfig(
74-
min=30,
75-
max=3600,
76-
unit_of_measurement="seconds",
77-
mode=selector.NumberSelectorMode.BOX,
78-
),
79-
),
80-
}
81-
),
135+
data_schema=build_schema(user_input or {}),
82136
errors=_errors,
83137
)
84138

@@ -145,52 +199,44 @@ async def async_step_init(
145199
CONF_CLIENT_ID: user_input[CONF_CLIENT_ID],
146200
CONF_CLIENT_SECRET: user_input[CONF_CLIENT_SECRET],
147201
CONF_SCAN_INTERVAL: user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
202+
CONF_SCAN_INTERVAL_CHARGE_POINTS: user_input.get(CONF_SCAN_INTERVAL_CHARGE_POINTS, DEFAULT_SCAN_INTERVAL_CHARGE_POINTS),
203+
CONF_SCAN_INTERVAL_WALLET: user_input.get(CONF_SCAN_INTERVAL_WALLET, DEFAULT_SCAN_INTERVAL_WALLET),
204+
CONF_SCAN_INTERVAL_TRANSACTIONS: user_input.get(CONF_SCAN_INTERVAL_TRANSACTIONS, DEFAULT_SCAN_INTERVAL_TRANSACTIONS),
148205
},
149206
)
150207
return self.async_create_entry(title="", data=user_input)
151208

209+
# Build defaults from user_input -> options -> data
210+
defaults = {
211+
CONF_CLIENT_ID: (user_input or {}).get(
212+
CONF_CLIENT_ID,
213+
self.config_entry.data.get(CONF_CLIENT_ID),
214+
),
215+
CONF_CLIENT_SECRET: (user_input or {}).get(
216+
CONF_CLIENT_SECRET,
217+
self.config_entry.data.get(CONF_CLIENT_SECRET),
218+
),
219+
CONF_SCAN_INTERVAL: self.config_entry.options.get(
220+
CONF_SCAN_INTERVAL,
221+
self.config_entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
222+
),
223+
CONF_SCAN_INTERVAL_CHARGE_POINTS: self.config_entry.options.get(
224+
CONF_SCAN_INTERVAL_CHARGE_POINTS,
225+
self.config_entry.data.get(CONF_SCAN_INTERVAL_CHARGE_POINTS, DEFAULT_SCAN_INTERVAL_CHARGE_POINTS),
226+
),
227+
CONF_SCAN_INTERVAL_WALLET: self.config_entry.options.get(
228+
CONF_SCAN_INTERVAL_WALLET,
229+
self.config_entry.data.get(CONF_SCAN_INTERVAL_WALLET, DEFAULT_SCAN_INTERVAL_WALLET),
230+
),
231+
CONF_SCAN_INTERVAL_TRANSACTIONS: self.config_entry.options.get(
232+
CONF_SCAN_INTERVAL_TRANSACTIONS,
233+
self.config_entry.data.get(CONF_SCAN_INTERVAL_TRANSACTIONS, DEFAULT_SCAN_INTERVAL_TRANSACTIONS),
234+
),
235+
}
236+
152237
return self.async_show_form(
153238
step_id="init",
154-
data_schema=vol.Schema(
155-
{
156-
vol.Required(
157-
CONF_CLIENT_ID,
158-
default=(user_input or {}).get(
159-
CONF_CLIENT_ID,
160-
self.config_entry.data.get(CONF_CLIENT_ID),
161-
),
162-
): selector.TextSelector(
163-
selector.TextSelectorConfig(
164-
type=selector.TextSelectorType.TEXT
165-
),
166-
),
167-
vol.Required(
168-
CONF_CLIENT_SECRET,
169-
default=(user_input or {}).get(
170-
CONF_CLIENT_SECRET,
171-
self.config_entry.data.get(CONF_CLIENT_SECRET),
172-
),
173-
): selector.TextSelector(
174-
selector.TextSelectorConfig(
175-
type=selector.TextSelectorType.PASSWORD
176-
),
177-
),
178-
vol.Optional(
179-
CONF_SCAN_INTERVAL,
180-
default=self.config_entry.options.get(
181-
CONF_SCAN_INTERVAL,
182-
self.config_entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
183-
),
184-
): selector.NumberSelector(
185-
selector.NumberSelectorConfig(
186-
min=30,
187-
max=3600,
188-
unit_of_measurement="seconds",
189-
mode=selector.NumberSelectorMode.BOX,
190-
),
191-
),
192-
}
193-
),
239+
data_schema=build_schema(defaults),
194240
errors=_errors,
195241
)
196242

custom_components/monta/const.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
CONF_SCAN_INTERVAL = "scan_interval"
1818
DEFAULT_SCAN_INTERVAL = 120 # Default to 120 seconds to stay within rate limits
1919

20+
# Separate scan intervals for different data types
21+
CONF_SCAN_INTERVAL_CHARGE_POINTS = "scan_interval_charge_points"
22+
CONF_SCAN_INTERVAL_WALLET = "scan_interval_wallet"
23+
CONF_SCAN_INTERVAL_TRANSACTIONS = "scan_interval_transactions"
24+
25+
DEFAULT_SCAN_INTERVAL_CHARGE_POINTS = 120 # Charge points need frequent updates
26+
DEFAULT_SCAN_INTERVAL_WALLET = 600 # Wallet updates less frequently (10 minutes)
27+
DEFAULT_SCAN_INTERVAL_TRANSACTIONS = 600 # Transactions update less frequently (10 minutes)
28+
2029
PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300
2130
STORAGE_KEY = "monta_auth"
2231
STORAGE_VERSION = 1

0 commit comments

Comments
 (0)