Skip to content

Commit 393dcd1

Browse files
authored
Allow adding and removing areas (#94)
- Migrate config flow to version 5 - Converts areas from map to list - Adds new configurations to Add and Delete areas Closes #67 #89 - Bump version to 1.4.0
1 parent 1891351 commit 393dcd1

File tree

5 files changed

+104
-89
lines changed

5 files changed

+104
-89
lines changed

custom_components/load_shedding/__init__.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
8484
area_coordinator.update_interval = timedelta(
8585
seconds=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
8686
)
87-
for conf in config_entry.options.get(CONF_AREAS, {}).values():
87+
for conf in config_entry.options.get(CONF_AREAS, []):
8888
area = Area(
8989
id=conf.get(CONF_ID),
9090
name=conf.get(CONF_NAME),
@@ -147,6 +147,30 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
147147
config_entry, data=new_data, options=new_options
148148
)
149149

150+
if config_entry.version == 4:
151+
old_data = {**config_entry.data}
152+
old_options = {**config_entry.options}
153+
new_data = {}
154+
new_options = {
155+
CONF_API_KEY: old_options.get(CONF_API_KEY),
156+
CONF_AREAS: [],
157+
}
158+
for field in old_options:
159+
if field == CONF_AREAS:
160+
areas = old_options.get(CONF_AREAS, {})
161+
for area_id in areas:
162+
new_options[CONF_AREAS].append(areas[area_id])
163+
continue
164+
165+
value = old_options.get(field)
166+
if value is not None:
167+
new_options[field] = value
168+
169+
config_entry.version = 5
170+
hass.config_entries.async_update_entry(
171+
config_entry, data=new_data, options=new_options
172+
)
173+
150174
_LOGGER.info("Migration to version %s successful", config_entry.version)
151175
return True
152176

custom_components/load_shedding/config_flow.py

Lines changed: 73 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77
import voluptuous as vol
88
from homeassistant import config_entries
9-
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
9+
from homeassistant.config_entries import (
10+
ConfigEntry,
11+
ConfigFlow,
12+
OptionsFlow,
13+
OptionsFlowWithConfigEntry,
14+
)
1015
from homeassistant.const import CONF_API_KEY, CONF_DESCRIPTION, CONF_ID, CONF_NAME
1116
from homeassistant.core import callback
1217
from homeassistant.data_entry_flow import FlowResult, FlowHandler
@@ -17,7 +22,6 @@
1722
from .const import (
1823
CONF_AREA_ID,
1924
CONF_AREAS,
20-
CONF_PROVIDER,
2125
CONF_SEARCH,
2226
DOMAIN,
2327
NAME,
@@ -36,13 +40,12 @@
3640
class LoadSheddingFlowHandler(ConfigFlow, domain=DOMAIN):
3741
"""Config flow for LoadShedding."""
3842

39-
VERSION = 4
43+
VERSION = 5
4044

4145
def __init__(self):
4246
self.provider: Provider = None
4347
self.api_key: str = ""
4448
self.areas: dict = {}
45-
# self.device_unique_id = f"{DOMAIN}"
4649

4750
@staticmethod
4851
@callback
@@ -86,7 +89,7 @@ async def async_step_sepush(
8689
# Validate the token by checking the allowance.
8790
sepush = SePush(token=self.api_key)
8891
await self.hass.async_add_executor_job(sepush.check_allowance)
89-
except (SePushError) as err:
92+
except SePushError as err:
9093
status_code = err.__cause__.args[0]
9194
if status_code == 400:
9295
errors["base"] = "sepush_400"
@@ -212,27 +215,15 @@ async def async_step_select_area(
212215
data = {}
213216
options = {
214217
CONF_API_KEY: self.api_key,
215-
CONF_AREAS: {
216-
area.id: {
218+
CONF_AREAS: [
219+
{
217220
CONF_DESCRIPTION: description,
218221
CONF_NAME: area.name,
219222
CONF_ID: area.id,
220223
},
221-
},
224+
],
222225
}
223226

224-
# entry = await self.async_set_unique_id(DOMAIN)
225-
# if entry:
226-
# try:
227-
# _LOGGER.debug("Entry exists: %s", entry)
228-
# if self.hass.config_entries.async_update_entry(entry, data=data):
229-
# await self.hass.config_entries.async_reload(entry.entry_id)
230-
# except Exception:
231-
# _LOGGER.debug("Unknown error", exc_info=True)
232-
# raise
233-
# else:
234-
# return self.async_abort(reason=FlowResultType.SHOW_PROGRESS_DONE)
235-
236227
return self.async_create_entry(
237228
title=NAME,
238229
data=data,
@@ -241,14 +232,12 @@ async def async_step_select_area(
241232
)
242233

243234

244-
class LoadSheddingOptionsFlowHandler(OptionsFlow):
235+
class LoadSheddingOptionsFlowHandler(OptionsFlowWithConfigEntry):
245236
"""Load Shedding config flow options handler."""
246237

247238
def __init__(self, config_entry: ConfigEntry) -> None:
248239
"""Initialize options flow."""
249-
# self.config_entry: ConfigEntry = config_entry
250-
self.opts = dict(config_entry.options)
251-
240+
super().__init__(config_entry)
252241
self.provider = Provider.SE_PUSH
253242
self.api_key = config_entry.options.get(CONF_API_KEY)
254243
self.areas = {}
@@ -260,36 +249,38 @@ async def async_step_init(
260249

261250
CONF_ACTIONS = {
262251
CONF_SETUP_API: "Configure API",
263-
# CONF_ADD_AREA: "Add area",
264-
# CONF_DELETE_AREA: "Remove area",
265-
# CONF_MULTI_STAGE_EVENTS: ""
252+
CONF_ADD_AREA: "Add area",
253+
CONF_DELETE_AREA: "Remove area",
266254
}
267255

256+
if user_input is not None:
257+
if user_input.get(CONF_ACTION) == CONF_SETUP_API:
258+
return await self.async_step_sepush()
259+
if user_input.get(CONF_ACTION) == CONF_ADD_AREA:
260+
return await self.async_step_add_area()
261+
if user_input.get(CONF_ACTION) == CONF_DELETE_AREA:
262+
return await self.async_step_delete_area()
263+
self.options[CONF_MULTI_STAGE_EVENTS] = user_input.get(
264+
CONF_MULTI_STAGE_EVENTS
265+
)
266+
self.options[CONF_MIN_EVENT_DURATION] = user_input.get(
267+
CONF_MIN_EVENT_DURATION
268+
)
269+
return self.async_create_entry(title=NAME, data=self.options)
270+
268271
OPTIONS_SCHEMA = vol.Schema(
269272
{
270273
vol.Optional(CONF_ACTION): vol.In(CONF_ACTIONS),
271274
vol.Optional(
272275
CONF_MULTI_STAGE_EVENTS,
273-
default=self.opts.get(CONF_MULTI_STAGE_EVENTS, False),
276+
default=self.options.get(CONF_MULTI_STAGE_EVENTS, True),
274277
): bool,
275278
vol.Optional(
276279
CONF_MIN_EVENT_DURATION,
277-
default=self.opts.get(CONF_MIN_EVENT_DURATION, 30),
280+
default=self.options.get(CONF_MIN_EVENT_DURATION, 31),
278281
): int,
279282
}
280283
)
281-
282-
if user_input is not None:
283-
if user_input.get(CONF_ACTION) == CONF_SETUP_API:
284-
return await self.async_step_sepush()
285-
if user_input.get(CONF_ACTION) == CONF_ADD_AREA:
286-
return await self.async_step_add_area()
287-
# if user_input.get(CONF_ACTION) == CONF_DELETE_AREA:
288-
# return await self.async_step_delete_area()
289-
self.opts[CONF_MULTI_STAGE_EVENTS] = user_input.get(CONF_MULTI_STAGE_EVENTS)
290-
self.opts[CONF_MIN_EVENT_DURATION] = user_input.get(CONF_MIN_EVENT_DURATION)
291-
return self.async_create_entry(title=NAME, data=self.opts)
292-
293284
return self.async_show_form(
294285
step_id="init",
295286
data_schema=OPTIONS_SCHEMA,
@@ -312,7 +303,7 @@ async def async_step_sepush(
312303
sepush = SePush(token=api_key)
313304
esp = await self.hass.async_add_executor_job(sepush.check_allowance)
314305
_LOGGER.debug("Validate API Key Response: %s", esp)
315-
except (SePushError) as err:
306+
except SePushError as err:
316307
status_code = err.__cause__.args[0]
317308
if status_code == 400:
318309
errors["base"] = "sepush_400"
@@ -326,8 +317,8 @@ async def async_step_sepush(
326317
errors["base"] = "provider_error"
327318
else:
328319
self.api_key = api_key
329-
self.opts[CONF_API_KEY] = api_key
330-
return self.async_create_entry(title=NAME, data=self.opts)
320+
self.options[CONF_API_KEY] = api_key
321+
return self.async_create_entry(title=NAME, data=self.options)
331322

332323
data_schema = vol.Schema(
333324
{
@@ -340,37 +331,6 @@ async def async_step_sepush(
340331
errors=errors,
341332
)
342333

343-
# async def async_step_init(
344-
# self, user_input: dict[str, Any] | None = None
345-
# ) -> FlowResult: # pylint: disable=unused-argument
346-
# """Manage the options."""
347-
348-
# CONF_ACTIONS = {
349-
# CONF_ADD_DEVICE: "Add Area",
350-
# CONF_EDIT_DEVICE: "Remove Area",
351-
# }
352-
353-
# CONFIGURE_SCHEMA = vol.Schema(
354-
# {
355-
# vol.Required(CONF_ACTION, default=CONF_ADD_DEVICE): vol.In(
356-
# CONF_ACTIONS
357-
# ),
358-
# }
359-
# )
360-
361-
# return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))
362-
363-
# schema: dict[vol.Marker, type] = {}
364-
# areas = self.opts.get(CONF_AREAS, {})
365-
# for area_id, area in areas.items():
366-
# schema[vol.Required(area_id, default=True)] = vol.In(
367-
# {area_id: area.get(CONF_DESCRIPTION)}
368-
# )
369-
370-
# return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))
371-
372-
# return await self.async_step_lookup_areas()
373-
374334
async def async_step_add_area(
375335
self, user_input: dict[str, Any] | None = None
376336
) -> FlowResult:
@@ -469,7 +429,6 @@ async def async_step_select_area(
469429
self, user_input: dict[str, Any] | None = None
470430
) -> FlowResult:
471431
"""Handle the flow step to create a area."""
472-
areas = self.opts.get(CONF_AREAS, {})
473432
area = self.areas.get(user_input.get(CONF_AREA_ID))
474433

475434
description = f"{area.name}"
@@ -478,16 +437,45 @@ async def async_step_select_area(
478437
if area.province is not Province.UNKNOWN:
479438
description += f", {area.province}"
480439

481-
areas[area.id] = {
482-
CONF_DESCRIPTION: description,
483-
CONF_NAME: area.name,
484-
CONF_ID: area.id,
485-
}
486-
487-
self.opts.update(
440+
self.options[CONF_AREAS].append(
488441
{
489-
CONF_AREAS: areas,
442+
CONF_DESCRIPTION: description,
443+
CONF_NAME: area.name,
444+
CONF_ID: area.id,
490445
}
491446
)
492-
result = self.async_create_entry(title=NAME, data=self.opts)
447+
448+
result = self.async_create_entry(title=NAME, data=self.options)
493449
return result
450+
451+
async def async_step_delete_area(
452+
self, user_input: dict[str, Any] | None = None
453+
) -> FlowResult:
454+
"""Handle the flow step to delete an area."""
455+
errors = None
456+
if user_input is None:
457+
area_idx = {}
458+
for idx, area in enumerate(self.options.get(CONF_AREAS, [])):
459+
area_idx[idx] = area.get(CONF_NAME)
460+
461+
if not errors:
462+
data_schema = vol.Schema(
463+
{
464+
vol.Optional(CONF_AREA_ID): vol.In(area_idx),
465+
}
466+
)
467+
468+
return self.async_show_form(
469+
step_id="delete_area",
470+
data_schema=data_schema,
471+
errors=errors,
472+
)
473+
else:
474+
new_areas = []
475+
for idx, area in enumerate(self.options.get(CONF_AREAS, [])):
476+
if idx == user_input.get(CONF_AREA_ID):
477+
continue
478+
new_areas.append(area)
479+
480+
self.options[CONF_AREAS] = new_areas
481+
return self.async_create_entry(title=NAME, data=self.options)

custom_components/load_shedding/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
MAX_FORECAST_DAYS: Final = 7
1010
NAME: Final = "Load Shedding"
1111
MANUFACTURER: Final = "@wernerhp"
12-
VERSION: Final = "1.1.0"
12+
VERSION: Final = "1.4.0"
1313
DEFAULT_SCAN_INTERVAL: Final = 60
1414
AREA_UPDATE_INTERVAL: Final = 86400 # 60sec * 60min * 24h / daily
1515
QUOTA_UPDATE_INTERVAL: Final = 1800 # 60sec * 30min
@@ -43,6 +43,7 @@
4343

4444
ATTR_AREA: Final = "area"
4545
ATTR_AREAS: Final = "areas"
46+
ATTR_AREA_ID: Final = "area_id"
4647
ATTR_CURRENT: Final = "current"
4748
ATTR_END_IN: Final = "ends_in"
4849
ATTR_END_TIME: Final = "end_time"

custom_components/load_shedding/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
"dependencies": [],
1212
"codeowners": ["@wernerhp"],
1313
"iot_class": "cloud_polling",
14-
"version": "1.2.0"
14+
"version": "1.4.0"
1515
}

custom_components/load_shedding/sensor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from . import LoadSheddingDevice
2525
from .const import (
2626
ATTR_AREA,
27+
ATTR_AREA_ID,
2728
ATTR_END_IN,
2829
ATTR_END_TIME,
2930
ATTR_FORECAST,
@@ -293,6 +294,7 @@ def extra_state_attributes(self) -> dict[str, list, Any]:
293294
forecast = data[ATTR_FORECAST]
294295

295296
attrs = get_sensor_attrs(forecast)
297+
attrs[ATTR_AREA_ID] = self.area.id
296298
attrs[ATTR_FORECAST] = forecast
297299
attrs[ATTR_LAST_UPDATE] = self.coordinator.last_update
298300
attrs = clean(attrs)
@@ -435,7 +437,7 @@ def get_sensor_attrs(forecast: list, stage: Stage = Stage.NO_LOAD_SHEDDING) -> d
435437

436438
def clean(data: dict) -> dict:
437439
"""Remove default values from dict"""
438-
for (key, value) in CLEAN_DATA.items():
440+
for key, value in CLEAN_DATA.items():
439441
if key not in data:
440442
continue
441443
if data[key] == value:

0 commit comments

Comments
 (0)