Загрузить файлы в «custom_components/motion_recorder»

This commit is contained in:
2026-07-03 15:10:03 +03:00
parent 4232e7d1f4
commit b66a2c4d8b
3 changed files with 246 additions and 0 deletions
@@ -0,0 +1,70 @@
"""Sensor platform for Motion Recorder."""
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.device_registry import DeviceInfo
from .const import DOMAIN, CONF_CAMERA_ENTITY
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Motion Recorder sensor."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([MotionRecorderStatusSensor(coordinator, entry)])
class MotionRecorderStatusSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Motion Recorder status sensor."""
def __init__(self, coordinator, entry):
"""Initialize the sensor."""
super().__init__(coordinator)
self.entry = entry
self._attr_unique_id = f"{entry.entry_id}_status"
self._attr_name = f"{entry.title} Статус"
self._attr_translation_key = "motion_recorder_status"
# Добавляем информацию об устройстве
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.entry_id)},
name=entry.title,
manufacturer="Motion Recorder",
model="Camera Recording Integration",
)
# Icon mapping
self._icon_map = {
"idle": "mdi:sleep",
"detecting": "mdi:motion-sensor",
"recording": "mdi:record-rec",
"delaying": "mdi:timer-sand",
"finalizing": "mdi:file-check",
"completed": "mdi:check-circle",
"error": "mdi:alert-circle",
"disabled": "mdi:video-off",
}
@property
def native_value(self):
"""Return the state of the sensor."""
return self.coordinator.state
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return self.coordinator.attributes
@property
def icon(self):
"""Return the icon."""
return self._icon_map.get(self.coordinator.state, "mdi:camera")
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.async_write_ha_state()
@@ -0,0 +1,91 @@
{
"config": {
"step": {
"user": {
"title": "Настройки Motion Recorder",
"description": "Настройка интеграции записи камер по движению",
"data": {
"name": "Имя интеграции",
"camera_entity": "Камера для записи",
"save_path": "Путь сохранения (относительно media)",
"filename_template": "Формат имени файла",
"max_duration": "Максимальная длительность записи (сек)",
"prebuffer": "Предбуфер (сек, 0 = отключено)",
"motion_sensors": "Датчики движения",
"motion_filter": "Фильтр обнаружения движения (сек, 0 = без фильтрации)",
"off_delay": "Задержка после пропадания движения (сек, 0 = без задержки)",
"retention_days": "Хранить записи (дней, 0 = не удалять)",
"force_stop_sensor": "Датчик принудительной остановки (binary_sensor, switch, input_boolean)",
"force_stop_state": "Состояние для остановки",
"log_level": "Уровень логирования"
},
"data_description": {
"name": "Имя, которое будет отображаться в интеграции",
"camera_entity": "Выберите камеру из списка доступных",
"save_path": "Папка для сохранения записей относительно /media. Поддерживаются переменные, например camera_name",
"filename_template": "Выберите формат даты и времени для имён файлов записей. Все форматы содержат секунды, чтобы избежать перезаписи файлов при нескольких срабатываниях в одну минуту",
"max_duration": "Максимальное время одной записии (10-3600 секунд). Если движение не прекратится, запись остановится по таймауту",
"prebuffer": "Сколько секунд до обнаружения движения включить в запись. Работает только если стрим уже активен",
"motion_sensors": "Можно выбрать несколько датчиков. Запись начнётся при сработке любого из них",
"motion_filter": "Задержка перед началом записи. Если движение пропадёт раньше — запись не начнётся (защита от ложных срабатываний)",
"off_delay": "Сколько секунд ждать после пропадания движения перед остановкой записи. Если движение вернётся — таймер сбросится",
"retention_days": "Автоматически удалять записи старше указанного количества дней. 0 = не удалять никогда. Очистка выполняется ежедневно в 3:00",
"force_stop_sensor": "Необязательно. Сущность, при переходе в указанное состояние запись немедленно остановится",
"force_stop_state": "Значение состояния датчика принудительной остановки (обычно 'off' или 'on')",
"log_level": "Уровень детализации логов. DEBUG — для отладки, INFO — обычный режим"
}
}
},
"abort": {
"single_instance_allowed": "Разрешена только одна конфигурация на камеру"
}
},
"options": {
"step": {
"init": {
"title": "Настройки Motion Recorder",
"description": "Изменение параметров записи",
"data": {
"filename_template": "Формат имени файла",
"max_duration": "Максимальная длительность записи (сек)",
"prebuffer": "Предбуфер (сек, 0 = отключено)",
"motion_filter": "Фильтр обнаружения движения (сек, 0 = без фильтрации)",
"off_delay": "Задержка после пропадания движения (сек, 0 = без задержки)",
"retention_days": "Хранить записи (дней, 0 = не удалять)",
"log_level": "Уровень логирования"
},
"data_description": {
"filename_template": "Выберите формат даты и времени для имён файлов записей",
"max_duration": "Максимальное время одной записии (10-3600 секунд)",
"prebuffer": "Сколько секунд до обнаружения движения включить в запись",
"motion_filter": "Задержка перед началом записи для защиты от ложных срабатываний",
"off_delay": "Сколько секунд ждать после пропадания движения перед остановкой записи",
"retention_days": "Автоматически удалять записи старше указанного количества дней. 0 = не удалять никогда",
"log_level": "Уровень детализации логов"
}
}
}
},
"entity": {
"sensor": {
"motion_recorder_status": {
"name": "Статус",
"state": {
"idle": "Ожидание",
"detecting": "Обнаружение",
"recording": "Запись",
"delaying": "Задержка",
"finalizing": "Завершение",
"completed": "Завершено",
"error": "Ошибка",
"disabled": "Отключено"
}
}
},
"switch": {
"motion_recorder_enabled": {
"name": "Запись включена"
}
}
}
}
@@ -0,0 +1,85 @@
"""Switch platform for Motion Recorder."""
import logging
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.device_registry import DeviceInfo
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Motion Recorder switch."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([MotionRecorderEnabledSwitch(coordinator, entry)])
class MotionRecorderEnabledSwitch(CoordinatorEntity, SwitchEntity, RestoreEntity):
"""Representation of a Motion Recorder enabled switch."""
def __init__(self, coordinator, entry):
"""Initialize the switch."""
super().__init__(coordinator)
self.entry = entry
self._attr_unique_id = f"{entry.entry_id}_enabled"
self._attr_name = f"{entry.title} Запись"
self._attr_translation_key = "motion_recorder_enabled"
self._attr_icon = "mdi:video"
# Добавляем информацию об устройстве для привязки к Device Registry
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.entry_id)},
name=entry.title,
manufacturer="Motion Recorder",
model="Camera Recording Integration",
)
@property
def is_on(self) -> bool:
"""Return True if recording is enabled."""
return self.coordinator.is_enabled
async def async_turn_on(self, **kwargs) -> None:
"""Turn on recording."""
_LOGGER.info("Switch turned ON, enabling recording")
await self.coordinator.set_enabled(True)
self.async_write_ha_state() # Принудительно обновляем UI
async def async_turn_off(self, **kwargs) -> None:
"""Turn off recording."""
_LOGGER.info("Switch turned OFF, disabling recording")
await self.coordinator.set_enabled(False)
self.async_write_ha_state() # Принудительно обновляем UI
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
# Восстанавливаем последнее состояние из базы данных HA
last_state = await self.async_get_last_state()
if last_state is not None:
is_enabled = last_state.state == STATE_ON
_LOGGER.info("Restored switch state: %s", last_state.state)
# Синхронизируем состояние с координатором
if self.coordinator.is_enabled != is_enabled:
await self.coordinator.set_enabled(is_enabled)
# Принудительно обновляем UI после восстановления
self.async_write_ha_state()
else:
_LOGGER.info("Switch added for the first time, defaulting to enabled")
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.async_write_ha_state()