Обновить custom_components/motion_recorder/config_flow.py

This commit is contained in:
2026-07-05 13:34:29 +03:00
parent 57d2441d3b
commit 84127a1229
+290 -234
View File
@@ -1,235 +1,291 @@
"""Config flow for Motion Recorder.""" """Config flow for Motion Recorder."""
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import callback from homeassistant.core import callback
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers import selector from homeassistant.helpers import selector
from .const import ( from .const import (
DOMAIN, DOMAIN,
CONF_CAMERA_ENTITY, CONF_CAMERA_ENTITY,
CONF_SAVE_PATH, CONF_SAVE_PATH,
CONF_FILENAME_TEMPLATE, CONF_FILENAME_TEMPLATE,
CONF_MAX_DURATION, CONF_MAX_DURATION,
CONF_PREBUFFER, CONF_PREBUFFER,
CONF_MOTION_SENSORS, CONF_MOTION_SENSORS,
CONF_MOTION_FILTER, CONF_MOTION_FILTER,
CONF_OFF_DELAY, CONF_OFF_DELAY,
CONF_FORCE_STOP_SENSOR, CONF_FORCE_STOP_SENSOR,
CONF_FORCE_STOP_STATE, CONF_FORCE_STOP_STATE,
CONF_LOG_LEVEL, CONF_LOG_LEVEL,
CONF_RETENTION_DAYS, CONF_RETENTION_DAYS,
DEFAULT_NAME, CONF_CONTROLLED_ENTITIES,
DEFAULT_SAVE_PATH, CONF_CONTROL_STATES,
DEFAULT_FILENAME_TEMPLATE, DEFAULT_NAME,
DEFAULT_MAX_DURATION, DEFAULT_SAVE_PATH,
DEFAULT_PREBUFFER, DEFAULT_FILENAME_TEMPLATE,
DEFAULT_MOTION_FILTER, DEFAULT_MAX_DURATION,
DEFAULT_OFF_DELAY, DEFAULT_PREBUFFER,
DEFAULT_FORCE_STOP_STATE, DEFAULT_MOTION_FILTER,
DEFAULT_LOG_LEVEL, DEFAULT_OFF_DELAY,
DEFAULT_RETENTION_DAYS, DEFAULT_FORCE_STOP_STATE,
) DEFAULT_LOG_LEVEL,
DEFAULT_RETENTION_DAYS,
DEFAULT_CONTROL_STATES,
# Список шаблонов имени файла (все с секундами для уникальности) STATE_IDLE,
FILENAME_TEMPLATE_OPTIONS = [ STATE_DETECTING,
selector.SelectOptionDict(value="%d-%m-%Y_%H-%M-%S", label="ДД-ММ-ГГГГ_ЧЧ-ММ-СС (02-07-2026_20-30-45)"), STATE_RECORDING,
selector.SelectOptionDict(value="%Y-%m-%d_%H-%M-%S", label="ГГГГ-ММ-ДД_ЧЧ-ММ-СС (2026-07-02_20-30-45)"), STATE_DELAYING,
selector.SelectOptionDict(value="%d.%m.%Y_%H-%M-%S", label="ДД.ММ.ГГГГ_ЧЧ-ММ-СС (02.07.2026_20-30-45)"), STATE_FINALIZING,
selector.SelectOptionDict(value="%Y%m%d_%H%M%S", label="ГГГГММДД_ЧЧММСС (20260702_203045)"), STATE_ERROR,
selector.SelectOptionDict(value="%d.%m.%Y_%H.%M.%S", label="ДД.ММ.ГГГГ_ЧЧ.ММ.СС (все точки)"), STATE_DISABLED,
] )
FILENAME_TEMPLATE_OPTIONS = [
class MotionRecorderConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): selector.SelectOptionDict(value="%d-%m-%Y_%H-%M-%S", label="ДД-ММ-ГГГГ_ЧЧ-ММ-СС (02-07-2026_20-30-45)"),
"""Handle a config flow for Motion Recorder.""" selector.SelectOptionDict(value="%Y-%m-%d_%H-%M-%S", label="ГГГГ-ММ-ДД_ЧЧ-ММ-СС (2026-07-02_20-30-45)"),
selector.SelectOptionDict(value="%d.%m.%Y_%H-%M-%S", label="ДД.ММ.ГГГГ_ЧЧ-ММ-СС (02.07.2026_20-30-45)"),
VERSION = 1 selector.SelectOptionDict(value="%Y%m%d_%H%M%S", label="ГГГГММДД_ЧЧММСС (20260702_203045)"),
selector.SelectOptionDict(value="%d.%m.%Y_%H.%M.%S", label="ДД.ММ.ГГГГ_ЧЧ.ММ.СС (все точки)"),
async def async_step_user(self, user_input=None): ]
"""Handle the initial step."""
errors = {} CONTROL_STATE_OPTIONS = [
selector.SelectOptionDict(value=STATE_IDLE, label="Ожидание (idle)"),
if user_input is not None: selector.SelectOptionDict(value=STATE_DETECTING, label="Обнаружение (detecting)"),
await self.async_set_unique_id(user_input[CONF_CAMERA_ENTITY]) selector.SelectOptionDict(value=STATE_RECORDING, label="Запись (recording)"),
self._abort_if_unique_id_configured() selector.SelectOptionDict(value=STATE_DELAYING, label="Задержка (delaying)"),
selector.SelectOptionDict(value=STATE_FINALIZING, label="Завершение (finalizing)"),
return self.async_create_entry( selector.SelectOptionDict(value=STATE_ERROR, label="Ошибка (error)"),
title=user_input.get("name", DEFAULT_NAME), selector.SelectOptionDict(value=STATE_DISABLED, label="Отключено (disabled)"),
data=user_input, ]
)
return self.async_show_form( class MotionRecorderConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user", """Handle a config flow for Motion Recorder."""
data_schema=vol.Schema({
vol.Required("name", default=DEFAULT_NAME): str, VERSION = 1
vol.Required(CONF_CAMERA_ENTITY): selector.EntitySelector(
selector.EntitySelectorConfig(domain="camera") async def async_step_user(self, user_input=None):
), """Handle the initial step."""
vol.Required(CONF_SAVE_PATH, default=DEFAULT_SAVE_PATH): str, errors = {}
vol.Required(CONF_FILENAME_TEMPLATE, default=DEFAULT_FILENAME_TEMPLATE): selector.SelectSelector(
selector.SelectSelectorConfig( if user_input is not None:
options=FILENAME_TEMPLATE_OPTIONS, await self.async_set_unique_id(user_input[CONF_CAMERA_ENTITY])
mode=selector.SelectSelectorMode.DROPDOWN, self._abort_if_unique_id_configured()
)
), return self.async_create_entry(
vol.Required(CONF_MAX_DURATION, default=DEFAULT_MAX_DURATION): selector.NumberSelector( title=user_input.get("name", DEFAULT_NAME),
selector.NumberSelectorConfig( data=user_input,
min=10, max=3600, step=1, )
mode=selector.NumberSelectorMode.SLIDER,
unit_of_measurement="сек", return self.async_show_form(
) step_id="user",
), data_schema=vol.Schema({
vol.Required(CONF_PREBUFFER, default=DEFAULT_PREBUFFER): selector.NumberSelector( vol.Required("name", default=DEFAULT_NAME): str,
selector.NumberSelectorConfig( vol.Required(CONF_CAMERA_ENTITY): selector.EntitySelector(
min=0, max=60, step=1, selector.EntitySelectorConfig(domain="camera")
mode=selector.NumberSelectorMode.SLIDER, ),
unit_of_measurement="сек", vol.Required(CONF_SAVE_PATH, default=DEFAULT_SAVE_PATH): str,
) vol.Required(CONF_FILENAME_TEMPLATE, default=DEFAULT_FILENAME_TEMPLATE): selector.SelectSelector(
), selector.SelectSelectorConfig(
vol.Required(CONF_MOTION_SENSORS): selector.EntitySelector( options=FILENAME_TEMPLATE_OPTIONS,
selector.EntitySelectorConfig(domain="binary_sensor", multiple=True) mode=selector.SelectSelectorMode.DROPDOWN,
), )
vol.Required(CONF_MOTION_FILTER, default=DEFAULT_MOTION_FILTER): selector.NumberSelector( ),
selector.NumberSelectorConfig( vol.Required(CONF_MAX_DURATION, default=DEFAULT_MAX_DURATION): selector.NumberSelector(
min=0, max=10, step=1, selector.NumberSelectorConfig(
mode=selector.NumberSelectorMode.SLIDER, min=10, max=3600, step=1,
unit_of_measurement="сек", mode=selector.NumberSelectorMode.SLIDER,
) unit_of_measurement="сек",
), )
vol.Required(CONF_OFF_DELAY, default=DEFAULT_OFF_DELAY): selector.NumberSelector( ),
selector.NumberSelectorConfig( vol.Required(CONF_PREBUFFER, default=DEFAULT_PREBUFFER): selector.NumberSelector(
min=0, max=60, step=1, selector.NumberSelectorConfig(
mode=selector.NumberSelectorMode.SLIDER, min=0, max=60, step=1,
unit_of_measurement="сек", mode=selector.NumberSelectorMode.SLIDER,
) unit_of_measurement="сек",
), )
vol.Required(CONF_RETENTION_DAYS, default=DEFAULT_RETENTION_DAYS): selector.NumberSelector( ),
selector.NumberSelectorConfig( vol.Required(CONF_MOTION_SENSORS): selector.EntitySelector(
min=0, max=365, step=1, selector.EntitySelectorConfig(domain="binary_sensor", multiple=True)
mode=selector.NumberSelectorMode.SLIDER, ),
unit_of_measurement="дн", vol.Required(CONF_MOTION_FILTER, default=DEFAULT_MOTION_FILTER): selector.NumberSelector(
) selector.NumberSelectorConfig(
), min=0, max=10, step=1,
vol.Optional(CONF_FORCE_STOP_SENSOR): selector.EntitySelector( mode=selector.NumberSelectorMode.SLIDER,
selector.EntitySelectorConfig( unit_of_measurement="сек",
domain=["binary_sensor", "switch", "input_boolean"], )
multiple=False, ),
) vol.Required(CONF_OFF_DELAY, default=DEFAULT_OFF_DELAY): selector.NumberSelector(
), selector.NumberSelectorConfig(
vol.Required(CONF_FORCE_STOP_STATE, default=DEFAULT_FORCE_STOP_STATE): str, min=0, max=60, step=1,
vol.Required(CONF_LOG_LEVEL, default=DEFAULT_LOG_LEVEL): selector.SelectSelector( mode=selector.NumberSelectorMode.SLIDER,
selector.SelectSelectorConfig( unit_of_measurement="сек",
options=[ )
selector.SelectOptionDict(value="DEBUG", label="DEBUG (подробно)"), ),
selector.SelectOptionDict(value="INFO", label="INFO (обычный)"), vol.Optional(CONF_FORCE_STOP_SENSOR): selector.EntitySelector(
selector.SelectOptionDict(value="WARNING", label="WARNING (только предупреждения)"), selector.EntitySelectorConfig(
selector.SelectOptionDict(value="ERROR", label="ERROR (только ошибки)"), domain=["binary_sensor", "switch", "input_boolean"],
], multiple=False,
mode=selector.SelectSelectorMode.DROPDOWN, )
) ),
), vol.Required(CONF_FORCE_STOP_STATE, default=DEFAULT_FORCE_STOP_STATE): str,
}), vol.Required(CONF_RETENTION_DAYS, default=DEFAULT_RETENTION_DAYS): selector.NumberSelector(
errors=errors, selector.NumberSelectorConfig(
) min=0, max=365, step=1,
mode=selector.NumberSelectorMode.SLIDER,
@staticmethod unit_of_measurement="дн",
@callback )
def async_get_options_flow(config_entry): ),
"""Get the options flow for this handler.""" vol.Optional(CONF_CONTROLLED_ENTITIES, default=[]): selector.EntitySelector(
return MotionRecorderOptionsFlow() selector.EntitySelectorConfig(
domain=["light", "switch"],
multiple=True,
class MotionRecorderOptionsFlow(config_entries.OptionsFlow): )
"""Handle options flow for Motion Recorder.""" ),
vol.Optional(CONF_CONTROL_STATES, default=DEFAULT_CONTROL_STATES): selector.SelectSelector(
async def async_step_init(self, user_input=None): selector.SelectSelectorConfig(
"""Manage the options.""" options=CONTROL_STATE_OPTIONS,
if user_input is not None: multiple=True,
return self.async_create_entry(title="", data=user_input) mode=selector.SelectSelectorMode.LIST,
)
return self.async_show_form( ),
step_id="init", vol.Required(CONF_LOG_LEVEL, default=DEFAULT_LOG_LEVEL): selector.SelectSelector(
data_schema=vol.Schema({ selector.SelectSelectorConfig(
vol.Required( options=[
CONF_FILENAME_TEMPLATE, selector.SelectOptionDict(value="DEBUG", label="DEBUG (подробно)"),
default=self.config_entry.options.get(CONF_FILENAME_TEMPLATE, selector.SelectOptionDict(value="INFO", label="INFO (обычный)"),
self.config_entry.data.get(CONF_FILENAME_TEMPLATE, DEFAULT_FILENAME_TEMPLATE)) selector.SelectOptionDict(value="WARNING", label="WARNING (только предупреждения)"),
): selector.SelectSelector( selector.SelectOptionDict(value="ERROR", label="ERROR (только ошибки)"),
selector.SelectSelectorConfig( ],
options=FILENAME_TEMPLATE_OPTIONS, mode=selector.SelectSelectorMode.DROPDOWN,
mode=selector.SelectSelectorMode.DROPDOWN, )
) ),
), }),
vol.Required( errors=errors,
CONF_MAX_DURATION, )
default=self.config_entry.options.get(CONF_MAX_DURATION,
self.config_entry.data.get(CONF_MAX_DURATION, DEFAULT_MAX_DURATION)) @staticmethod
): selector.NumberSelector( @callback
selector.NumberSelectorConfig( def async_get_options_flow(config_entry):
min=10, max=3600, step=1, """Get the options flow for this handler."""
mode=selector.NumberSelectorMode.SLIDER, return MotionRecorderOptionsFlow()
unit_of_measurement="сек",
)
), class MotionRecorderOptionsFlow(config_entries.OptionsFlow):
vol.Required( """Handle options flow for Motion Recorder."""
CONF_PREBUFFER,
default=self.config_entry.options.get(CONF_PREBUFFER, async def async_step_init(self, user_input=None):
self.config_entry.data.get(CONF_PREBUFFER, DEFAULT_PREBUFFER)) """Manage the options."""
): selector.NumberSelector( if user_input is not None:
selector.NumberSelectorConfig( return self.async_create_entry(title="", data=user_input)
min=0, max=60, step=1,
mode=selector.NumberSelectorMode.SLIDER, return self.async_show_form(
unit_of_measurement="сек", step_id="init",
) data_schema=vol.Schema({
), vol.Required(
vol.Required( CONF_FILENAME_TEMPLATE,
CONF_MOTION_FILTER, default=self.config_entry.options.get(CONF_FILENAME_TEMPLATE,
default=self.config_entry.options.get(CONF_MOTION_FILTER, self.config_entry.data.get(CONF_FILENAME_TEMPLATE, DEFAULT_FILENAME_TEMPLATE))
self.config_entry.data.get(CONF_MOTION_FILTER, DEFAULT_MOTION_FILTER)) ): selector.SelectSelector(
): selector.NumberSelector( selector.SelectSelectorConfig(
selector.NumberSelectorConfig( options=FILENAME_TEMPLATE_OPTIONS,
min=0, max=10, step=1, mode=selector.SelectSelectorMode.DROPDOWN,
mode=selector.NumberSelectorMode.SLIDER, )
unit_of_measurement="сек", ),
) vol.Required(
), CONF_MAX_DURATION,
vol.Required( default=self.config_entry.options.get(CONF_MAX_DURATION,
CONF_OFF_DELAY, self.config_entry.data.get(CONF_MAX_DURATION, DEFAULT_MAX_DURATION))
default=self.config_entry.options.get(CONF_OFF_DELAY, ): selector.NumberSelector(
self.config_entry.data.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY)) selector.NumberSelectorConfig(
): selector.NumberSelector( min=10, max=3600, step=1,
selector.NumberSelectorConfig( mode=selector.NumberSelectorMode.SLIDER,
min=0, max=60, step=1, unit_of_measurement="сек",
mode=selector.NumberSelectorMode.SLIDER, )
unit_of_measurement="сек", ),
) vol.Required(
), CONF_PREBUFFER,
vol.Required( default=self.config_entry.options.get(CONF_PREBUFFER,
CONF_RETENTION_DAYS, self.config_entry.data.get(CONF_PREBUFFER, DEFAULT_PREBUFFER))
default=self.config_entry.options.get(CONF_RETENTION_DAYS, ): selector.NumberSelector(
self.config_entry.data.get(CONF_RETENTION_DAYS, DEFAULT_RETENTION_DAYS)) selector.NumberSelectorConfig(
): selector.NumberSelector( min=0, max=60, step=1,
selector.NumberSelectorConfig( mode=selector.NumberSelectorMode.SLIDER,
min=0, max=365, step=1, unit_of_measurement="сек",
mode=selector.NumberSelectorMode.SLIDER, )
unit_of_measurement="дн", ),
) vol.Required(
), CONF_MOTION_FILTER,
vol.Required( default=self.config_entry.options.get(CONF_MOTION_FILTER,
CONF_LOG_LEVEL, self.config_entry.data.get(CONF_MOTION_FILTER, DEFAULT_MOTION_FILTER))
default=self.config_entry.options.get(CONF_LOG_LEVEL, ): selector.NumberSelector(
self.config_entry.data.get(CONF_LOG_LEVEL, DEFAULT_LOG_LEVEL)) selector.NumberSelectorConfig(
): selector.SelectSelector( min=0, max=10, step=1,
selector.SelectSelectorConfig( mode=selector.NumberSelectorMode.SLIDER,
options=[ unit_of_measurement="сек",
selector.SelectOptionDict(value="DEBUG", label="DEBUG (подробно)"), )
selector.SelectOptionDict(value="INFO", label="INFO (обычный)"), ),
selector.SelectOptionDict(value="WARNING", label="WARNING (только предупреждения)"), vol.Required(
selector.SelectOptionDict(value="ERROR", label="ERROR (только ошибки)"), CONF_OFF_DELAY,
], default=self.config_entry.options.get(CONF_OFF_DELAY,
mode=selector.SelectSelectorMode.DROPDOWN, self.config_entry.data.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY))
) ): selector.NumberSelector(
), selector.NumberSelectorConfig(
}), min=0, max=60, step=1,
mode=selector.NumberSelectorMode.SLIDER,
unit_of_measurement="сек",
)
),
vol.Required(
CONF_RETENTION_DAYS,
default=self.config_entry.options.get(CONF_RETENTION_DAYS,
self.config_entry.data.get(CONF_RETENTION_DAYS, DEFAULT_RETENTION_DAYS))
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0, max=365, step=1,
mode=selector.NumberSelectorMode.SLIDER,
unit_of_measurement="дн",
)
),
vol.Optional(
CONF_CONTROLLED_ENTITIES,
default=self.config_entry.options.get(
CONF_CONTROLLED_ENTITIES,
self.config_entry.data.get(CONF_CONTROLLED_ENTITIES, [])
)
): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["light", "switch"],
multiple=True,
)
),
vol.Optional(
CONF_CONTROL_STATES,
default=self.config_entry.options.get(
CONF_CONTROL_STATES,
self.config_entry.data.get(CONF_CONTROL_STATES, DEFAULT_CONTROL_STATES)
)
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONTROL_STATE_OPTIONS,
multiple=True,
mode=selector.SelectSelectorMode.LIST,
)
),
vol.Required(
CONF_LOG_LEVEL,
default=self.config_entry.options.get(CONF_LOG_LEVEL,
self.config_entry.data.get(CONF_LOG_LEVEL, DEFAULT_LOG_LEVEL))
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(value="DEBUG", label="DEBUG (подробно)"),
selector.SelectOptionDict(value="INFO", label="INFO (обычный)"),
selector.SelectOptionDict(value="WARNING", label="WARNING (только предупреждения)"),
selector.SelectOptionDict(value="ERROR", label="ERROR (только ошибки)"),
],
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
}),
) )