Загрузить файлы в «custom_components/motion_recorder»
This commit is contained in:
@@ -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()
|
||||||
Reference in New Issue
Block a user