Загрузить файлы в «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