Skip to content

Commit 4396629

Browse files
committed
Add OptionsFlow handler and strings
1 parent 811eb30 commit 4396629

File tree

6 files changed

+200
-4
lines changed

6 files changed

+200
-4
lines changed

custom_components/pv_curtailment/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ async def async_setup_entry(
1212
hass: HomeAssistant, entry: config_entries.ConfigEntry
1313
) -> bool:
1414
"""Set up platform from ConfigEntry"""
15+
platform_config = {**entry.data, **entry.options}
1516
hass.data.setdefault(DOMAIN, {})
16-
hass.data[DOMAIN][CONFIG] = entry.data
17+
hass.data[DOMAIN][CONFIG] = platform_config
1718

1819
pv_coordinator = PvCurtailingCoordinator(
1920
hass=hass, config_entry=entry
@@ -27,4 +28,13 @@ async def async_setup_entry(
2728

2829
# Forward setup to platforms
2930
await hass.config_entries.async_forward_entry_setups(entry=entry, platforms=["sensor", "switch"])
30-
return True
31+
return True
32+
33+
async def async_unload_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry) -> bool:
34+
"""Unload platform"""
35+
coordinator = hass.data[DOMAIN][COORDINATOR]
36+
if coordinator.d is not None:
37+
coordinator.d.close()
38+
39+
unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor", "switch"])
40+
return unload_ok

custom_components/pv_curtailment/config_flow.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from typing import Any
55
from homeassistant import config_entries
6+
from homeassistant.core import callback
67
from homeassistant.helpers.selector import selector, EntitySelector, EntityFilterSelectorConfig
78
from .const import *
89

@@ -96,4 +97,104 @@ async def async_step_pricing(self, user_input: dict[str, Any] | None = None) ->
9697
self.data[CONF_PRICING_STEP] = user_input
9798
return self.async_create_entry(title=DOMAIN, data=self.data)
9899

99-
return self.async_show_form(step_id="pricing", data_schema=PRICING_SCHEMA)
100+
return self.async_show_form(step_id="pricing", data_schema=PRICING_SCHEMA)
101+
102+
103+
@staticmethod
104+
@callback
105+
def async_get_options_flow(config_entry: config_entries.ConfigEntry):
106+
"""Get the options flow for this handler."""
107+
return OptionsFlowHandler()
108+
109+
110+
class OptionsFlowHandler(config_entries.OptionsFlowWithReload):
111+
"""Handles options flow for the component"""
112+
113+
async def async_step_init(self, user_input: dict[str, Any] | None = None) -> config_entries.ConfigFlowResult:
114+
"""Invoked when a user initiates a flow via the user interface."""
115+
old_config = self.hass.data[DOMAIN][CONFIG]
116+
old_brand = Brand(str(old_config[CONF_USER_STEP][CONF_INVERTER_BRAND]))
117+
OPTIONS_INIT_SCHEMA = vol.Schema(
118+
{
119+
vol.Required(CONF_INVERTER_BRAND, default=old_brand): selector({
120+
"select": {
121+
"options": [brand.value for brand in Brand]
122+
}
123+
})
124+
}
125+
)
126+
errors = {}
127+
self.data = {}
128+
if user_input is not None:
129+
self.brand = Brand(user_input[CONF_INVERTER_BRAND])
130+
self.data[CONF_USER_STEP] = user_input
131+
return await self.async_step_connect()
132+
133+
return self.async_show_form(step_id="init", data_schema=OPTIONS_INIT_SCHEMA, errors=errors)
134+
135+
async def async_step_connect(self, user_input: dict[str, Any] | None = None) -> config_entries.ConfigFlowResult:
136+
"""Configure IP, port and modbus slave ID"""
137+
errors = {}
138+
old_config = self.hass.data[DOMAIN][CONFIG]
139+
old_ip = old_config[CONF_CONNECT_STEP][CONF_IP]
140+
old_port = old_config[CONF_CONNECT_STEP][CONF_PORT]
141+
142+
OPTIONS_CONNECT_SCHEMA = vol.Schema(
143+
{
144+
vol.Required(CONF_IP, default=old_ip): str,
145+
vol.Required(CONF_PORT, default=old_port): vol.Coerce(int),
146+
vol.Required(CONF_SLAVE_ID, default=map_default_ID(brand=self.brand)): vol.Coerce(int),
147+
}
148+
)
149+
150+
if user_input is not None:
151+
try:
152+
validated_schema = OPTIONS_CONNECT_SCHEMA(user_input)
153+
except Exception as e:
154+
errors["base"] = "invalid_input"
155+
156+
if not errors:
157+
self.data[CONF_CONNECT_STEP] = user_input
158+
return await self.async_step_energy_meter()
159+
160+
return self.async_show_form(step_id="connect", data_schema=OPTIONS_CONNECT_SCHEMA, errors=errors)
161+
162+
async def async_step_energy_meter(self, user_input: dict[str, Any] | None = None) -> config_entries.ConfigFlowResult:
163+
"""Configure energy meter entity IDs"""
164+
errors = {}
165+
old_config = self.hass.data[DOMAIN][CONFIG]
166+
old_pwr_imp_ent_id = old_config[CONF_ENERGY_METER_STEP][CONF_PWR_IMP_ENT_ID]
167+
old_pwr_exp_ent_id = old_config[CONF_ENERGY_METER_STEP][CONF_PWR_EXP_ENT_ID]
168+
169+
OPTIONS_ENERGY_METER_SCHEMA = vol.Schema(
170+
{
171+
vol.Required(CONF_PWR_IMP_ENT_ID, default=old_pwr_imp_ent_id): EntitySelector(EntityFilterSelectorConfig(domain=["sensor", "input_number", "number"])),
172+
vol.Required(CONF_PWR_EXP_ENT_ID, default=old_pwr_exp_ent_id): EntitySelector(EntityFilterSelectorConfig(domain=["sensor", "input_number", "number"])),
173+
}
174+
)
175+
176+
if user_input is not None:
177+
self.data[CONF_ENERGY_METER_STEP] = user_input
178+
return await self.async_step_pricing()
179+
180+
return self.async_show_form(step_id="energy_meter", data_schema=OPTIONS_ENERGY_METER_SCHEMA, errors=errors)
181+
182+
async def async_step_pricing(self, user_input: dict[str, Any] | None = None) -> config_entries.ConfigFlowResult:
183+
"""Configure SDAC injection tariff entity ID"""
184+
errors = {}
185+
old_config = self.hass.data[DOMAIN][CONFIG]
186+
old_inj_tariff_ent_id = old_config[CONF_PRICING_STEP][CONF_INJ_TARIFF_ENT_ID]
187+
old_price_ent_id = old_config[CONF_PRICING_STEP][CONF_PRICE_ENT_ID]
188+
189+
OPTIONS_PRICING_SCHEMA = vol.Schema(
190+
{
191+
vol.Required(CONF_INJ_TARIFF_ENT_ID, default=old_inj_tariff_ent_id): EntitySelector(EntityFilterSelectorConfig(domain=["sensor", "input_number", "number"])),
192+
vol.Required(CONF_PRICE_ENT_ID, default=old_price_ent_id): EntitySelector(EntityFilterSelectorConfig(domain=["sensor", "input_number", "number"])),
193+
}
194+
)
195+
196+
if user_input is not None:
197+
self.data[CONF_PRICING_STEP] = user_input
198+
return self.async_create_entry(data=self.data)
199+
200+
return self.async_show_form(step_id="pricing", data_schema=OPTIONS_PRICING_SCHEMA, errors=errors)

custom_components/pv_curtailment/coordinator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,10 @@ async def try_reconnect(self) -> None:
331331

332332
while not is_connected:
333333
self.sleep = True
334+
if self.d is not None:
335+
self.d.close()
334336
await asyncio.sleep(sleep_time)
335337
try:
336-
self.d = None
337338
self.d = await self.hass.async_add_executor_job(self.connect_and_scan) # blocking call
338339
if len(self.d.models) == 0:
339340
_LOGGER.error("Modbus client succesfully reconnected to slave, but no SunSpec models are available. This integration will now shut down, a manual restart of Home Assistant is required to resume.")

custom_components/pv_curtailment/strings.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,33 @@
2626
}
2727
}
2828
}
29+
},
30+
"options": {
31+
"step": {
32+
"init": {
33+
"title": "Inverter brand",
34+
"description": "Select your inverter brand"
35+
},
36+
"connect": {
37+
"title": "Connect to inverter",
38+
"description": "Configure Modbus TCP settings"
39+
},
40+
"energy_meter": {
41+
"title": "Energy meter entities",
42+
"description": "Select the power import (power consumption) and export (power injection) entities that are read from the energy meter",
43+
"data": {
44+
"power_import_entity_id": "Power consumption",
45+
"power_export_entity_id": "Power injection"
46+
}
47+
},
48+
"pricing": {
49+
"title": "Energy pricing entities",
50+
"description": "Select the electricity price and injection tariff entities",
51+
"data": {
52+
"injection_tariff_entity_id": "Injection tariff",
53+
"electricity_price_entity_id": "electricity price (incl. network costs)"
54+
}
55+
}
56+
}
2957
}
3058
}

custom_components/pv_curtailment/translations/en.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,33 @@
2626
}
2727
}
2828
}
29+
},
30+
"options": {
31+
"step": {
32+
"init": {
33+
"title": "Inverter brand",
34+
"description": "Select your inverter brand"
35+
},
36+
"connect": {
37+
"title": "Connect to inverter",
38+
"description": "Configure Modbus TCP settings"
39+
},
40+
"energy_meter": {
41+
"title": "Energy meter entities",
42+
"description": "Select the power import (power consumption) and export (power injection) entities that are read from the energy meter",
43+
"data": {
44+
"power_import_entity_id": "Power consumption",
45+
"power_export_entity_id": "Power injection"
46+
}
47+
},
48+
"pricing": {
49+
"title": "Energy pricing entities",
50+
"description": "Select the electricity price and injection tariff entities",
51+
"data": {
52+
"injection_tariff_entity_id": "Injection tariff",
53+
"electricity_price_entity_id": "electricity price (incl. network costs)"
54+
}
55+
}
56+
}
2957
}
3058
}

custom_components/pv_curtailment/translations/nl.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,33 @@
2626
}
2727
}
2828
}
29+
},
30+
"options": {
31+
"step": {
32+
"init": {
33+
"title": "Omvormermerk",
34+
"description": "Selecteer het merk van uw omvormer"
35+
},
36+
"connect": {
37+
"title": "Verbinding maken met omvormer",
38+
"description": "Modbus TCP-instellingen configureren"
39+
},
40+
"energy_meter": {
41+
"title": "Energiemeter-entiteiten",
42+
"description": "Selecteer de entiteiten voor afnamevermogen en injectievermogen die worden uitgelezen van de energiemeter",
43+
"data": {
44+
"power_import_entity_id": "Afnamevermogen",
45+
"power_export_entity_id": "Injectievermogen"
46+
}
47+
},
48+
"pricing": {
49+
"title": "Energieprijs-entiteiten",
50+
"description": "Selecteer de entiteiten voor de elektriciteitsprijs en het injectietarief",
51+
"data": {
52+
"injection_tariff_entity_id": "Injectietarief",
53+
"electricity_price_entity_id": "Elektriciteitsprijs (incl. nettarieven)"
54+
}
55+
}
56+
}
2957
}
3058
}

0 commit comments

Comments
 (0)