Skip to content

Commit e9b339a

Browse files
authored
Allow to set scan interval, and reconfigure integration (#4)
1 parent 71a2d7a commit e9b339a

6 files changed

Lines changed: 135 additions & 9 deletions

File tree

‎custom_components/axpert_inverter/__init__.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
2121

2222
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2323
"""Set up Axpert Inverter from a config entry."""
24-
device_path = entry.data[CONF_DEVICE_PATH]
24+
# Prioritize options over data
25+
device_path = entry.options.get(CONF_DEVICE_PATH, entry.data.get(CONF_DEVICE_PATH))
2526

2627
inverter = AxpertInverter(device_path)
2728
coordinator = AxpertDataUpdateCoordinator(hass, inverter, entry)
2829

30+
# Listen for options changes
31+
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
32+
2933
# Fetch firmware version once at startup
3034
try:
3135
coordinator.firmware_version = await hass.async_add_executor_job(inverter.get_firmware_version)
@@ -69,3 +73,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
6973
hass.data[DOMAIN].pop(entry.entry_id)
7074

7175
return unload_ok
76+
77+
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
78+
"""Reload config entry."""
79+
await hass.config_entries.async_reload(entry.entry_id)

‎custom_components/axpert_inverter/config_flow.py‎

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from .const import (
1010
DOMAIN,
1111
CONF_DEVICE_PATH,
12+
CONF_SCAN_INTERVAL,
1213
DEFAULT_DEVICE_PATH,
14+
DEFAULT_SCAN_INTERVAL,
1315
)
1416
from .axpert import AxpertInverter
1517

@@ -26,6 +28,7 @@ async def async_step_user(self, user_input=None) -> FlowResult:
2628

2729
if user_input is not None:
2830
device_path = user_input.get(CONF_DEVICE_PATH)
31+
scan_interval = user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
2932

3033
# Validate connection
3134
try:
@@ -44,10 +47,16 @@ async def async_step_user(self, user_input=None) -> FlowResult:
4447
step_id="user",
4548
data_schema=vol.Schema({
4649
vol.Required(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): str,
50+
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All(vol.Coerce(int), vol.Range(min=1)),
4751
}),
4852
errors=errors,
4953
)
5054

55+
@staticmethod
56+
def async_get_options_flow(config_entry):
57+
"""Get the options flow for this handler."""
58+
return AxpertOptionsFlowHandler()
59+
5160
def _validate_connection(self, path: str):
5261
"""Try to connect to the inverter."""
5362
inverter = AxpertInverter(path)
@@ -56,11 +65,58 @@ def _validate_connection(self, path: str):
5665
# Actually in axpert.py we implemented send_command.
5766
# Let's try get_device_id. If it fails, open might fail or read timeout.
5867
try:
59-
# Just instantiate and try one command
60-
# Note: get_device_id might not work on all firmware, QPIGS is safer as main data source.
61-
# But let's try QPIGS to be sure we can read data.
62-
res = inverter.get_general_status()
63-
if not res:
64-
raise Exception("Empty response from QPIGS during validation")
68+
# Just instantiate and try one command
69+
# Note: get_device_id might not work on all firmware, QPIGS is safer as main data source.
70+
# But let's try QPIGS to be sure we can read data.
71+
res = inverter.get_general_status()
72+
if not res:
73+
raise Exception("Empty response from QPIGS during validation")
74+
except Exception as e:
75+
raise Exception(f"Validation failed: {e}")
76+
77+
class AxpertOptionsFlowHandler(config_entries.OptionsFlow):
78+
"""Handle options flow for Axpert Inverter."""
79+
80+
async def async_step_init(self, user_input=None) -> FlowResult:
81+
"""Manage the options."""
82+
errors = {}
83+
84+
if user_input is not None:
85+
device_path = user_input.get(CONF_DEVICE_PATH)
86+
87+
# We can optionally validate the new path here
88+
# But normally we just accept it and let the reload handle connection
89+
try:
90+
# Basic validation using the executor
91+
inverter = AxpertInverter(device_path)
92+
await self.hass.async_add_executor_job(self._validate_connection_options, inverter)
93+
94+
return self.async_create_entry(title="", data=user_input)
95+
except Exception as e:
96+
_LOGGER.error(f"Failed to connect to new path: {e}")
97+
errors["base"] = "cannot_connect"
98+
99+
current_path = self.config_entry.options.get(
100+
CONF_DEVICE_PATH, self.config_entry.data.get(CONF_DEVICE_PATH, DEFAULT_DEVICE_PATH)
101+
)
102+
current_scan_interval = self.config_entry.options.get(
103+
CONF_SCAN_INTERVAL, self.config_entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
104+
)
105+
106+
return self.async_show_form(
107+
step_id="init",
108+
data_schema=vol.Schema({
109+
vol.Required(CONF_DEVICE_PATH, default=current_path): str,
110+
vol.Optional(CONF_SCAN_INTERVAL, default=current_scan_interval): vol.All(vol.Coerce(int), vol.Range(min=1)),
111+
}),
112+
errors=errors,
113+
)
114+
115+
def _validate_connection_options(self, inverter: AxpertInverter):
116+
"""Validate connection during options update."""
117+
try:
118+
res = inverter.get_general_status()
119+
if not res:
120+
raise Exception("Empty response from QPIGS during validation")
65121
except Exception as e:
66122
raise Exception(f"Validation failed: {e}")

‎custom_components/axpert_inverter/const.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# Configuration
66
CONF_DEVICE_PATH = "device_path"
7+
CONF_SCAN_INTERVAL = "scan_interval"
78
DEFAULT_DEVICE_PATH = "/dev/hidraw0"
89

910
# Default poll interval (seconds)

‎custom_components/axpert_inverter/coordinator.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
)
1010

1111
from .axpert import AxpertInverter
12-
from .const import DOMAIN, DEFAULT_SCAN_INTERVAL
12+
from .const import DOMAIN, DEFAULT_SCAN_INTERVAL, CONF_SCAN_INTERVAL
1313

1414
_LOGGER = logging.getLogger(__name__)
1515

@@ -20,11 +20,16 @@ def __init__(self, hass: HomeAssistant, inverter: AxpertInverter, entry: ConfigE
2020
"""Initialize."""
2121
self.inverter = inverter
2222
self.entry = entry
23+
24+
scan_interval = entry.options.get(
25+
CONF_SCAN_INTERVAL, entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
26+
)
27+
2328
super().__init__(
2429
hass,
2530
_LOGGER,
2631
name=DOMAIN,
27-
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
32+
update_interval=timedelta(seconds=scan_interval),
2833
)
2934
self.firmware_version = None
3035
self.model_id = None

‎custom_components/axpert_inverter/strings.json‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11
{
2+
"config": {
3+
"step": {
4+
"user": {
5+
"title": "Configure Axpert Inverter",
6+
"data": {
7+
"device_path": "Device Path",
8+
"scan_interval": "Polling Interval (seconds)"
9+
}
10+
}
11+
},
12+
"error": {
13+
"cannot_connect": "Failed to connect to the inverter"
14+
}
15+
},
16+
"options": {
17+
"step": {
18+
"init": {
19+
"title": "Axpert Inverter Options",
20+
"data": {
21+
"device_path": "Device Path",
22+
"scan_interval": "Polling Interval (seconds)"
23+
}
24+
}
25+
},
26+
"error": {
27+
"cannot_connect": "Failed to connect to the inverter"
28+
}
29+
},
230
"entity": {
331
"select": {
432
"ac_input_range": {

‎custom_components/axpert_inverter/translations/ca.json‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11
{
2+
"config": {
3+
"step": {
4+
"user": {
5+
"title": "Configurar Inversor Axpert",
6+
"data": {
7+
"device_path": "Ruta del Dispositiu",
8+
"scan_interval": "Interval d'Actualització (segons)"
9+
}
10+
}
11+
},
12+
"error": {
13+
"cannot_connect": "No s'ha pogut connectar a l'inversor"
14+
}
15+
},
16+
"options": {
17+
"step": {
18+
"init": {
19+
"title": "Opcions de l'Inversor Axpert",
20+
"data": {
21+
"device_path": "Ruta del Dispositiu",
22+
"scan_interval": "Interval d'Actualització (segons)"
23+
}
24+
}
25+
},
26+
"error": {
27+
"cannot_connect": "No s'ha pogut connectar a l'inversor"
28+
}
29+
},
230
"entity": {
331
"select": {
432
"ac_input_range": {

0 commit comments

Comments
 (0)