Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6d87cb049 | ||
|
|
dc85e69e28 | ||
|
|
495fbe5dce | ||
|
|
6ccca63270 | ||
|
|
f9cbcfe7f6 | ||
|
|
cdba65f5f7 | ||
|
|
c817a47e30 | ||
|
|
96ff3d4285 | ||
|
|
457ff1fa02 |
@@ -0,0 +1,69 @@
|
|||||||
|
name: Update Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
GIT_SSL_NO_VERIFY: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-version:
|
||||||
|
name: Update Version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.actor != 'gitea-actions[bot]' && github.actor != 'Gitea Actions Bot'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.PAT_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install python-semantic-release
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --local user.email "actions@gitea.local"
|
||||||
|
git config --local user.name "Gitea Actions Bot"
|
||||||
|
|
||||||
|
- name: Run semantic-release
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "🧹 Сбрасываем конфликтующие переменные Gitea Actions..."
|
||||||
|
unset GITHUB_SERVER_URL
|
||||||
|
unset GITHUB_API_URL
|
||||||
|
unset GITHUB_REPOSITORY
|
||||||
|
|
||||||
|
echo "🔐 Записываем оригинальный Root CA от mkcert из секретов..."
|
||||||
|
echo "${{ secrets.LOCAL_ROOT_CA }}" > /tmp/rootCA.crt
|
||||||
|
|
||||||
|
# Проверка на случай, если секрет забыли настроить
|
||||||
|
if [ ! -s /tmp/rootCA.crt ]; then
|
||||||
|
echo "❌ Ошибка: Секрет LOCAL_ROOT_CA пуст. Проверьте настройки репозитория!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Root CA успешно прочитан."
|
||||||
|
|
||||||
|
# Находим, где конкретно Python (библиотека certifi) хранит свои сертификаты
|
||||||
|
CERTIFI_PATH=$(python3 -c "import certifi; print(certifi.where())")
|
||||||
|
echo "Путь к bundle Python: $CERTIFI_PATH"
|
||||||
|
|
||||||
|
# Внедряем корневой сертификат mkcert в конец доверенного списка Python
|
||||||
|
cat /tmp/rootCA.crt >> "$CERTIFI_PATH"
|
||||||
|
echo "✅ Root CA от mkcert добавлен в доверенные для Python!"
|
||||||
|
|
||||||
|
echo "🚀 Запускаем semantic-release..."
|
||||||
|
semantic-release version
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
name: Test and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch: # Позволяет запускать вручную
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Шаг 1: Валидация структуры HA интеграции
|
||||||
|
validate:
|
||||||
|
name: Validate Integration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Hassfest Validation
|
||||||
|
uses: home-assistant/actions/hassfest@master
|
||||||
|
|
||||||
|
- name: HACS Validation
|
||||||
|
uses: hacs/action@main
|
||||||
|
with:
|
||||||
|
category: "integration"
|
||||||
|
|
||||||
|
# Шаг 2: Запуск тестов (если есть)
|
||||||
|
test:
|
||||||
|
name: Run Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pytest pytest-homeassistant-custom-component
|
||||||
|
if [ -f requirements_test.txt ]; then pip install -r requirements_test.txt; fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
if [ -d "tests" ]; then
|
||||||
|
pytest tests/ --cov=custom_components/motion_light --cov-report=xml
|
||||||
|
else
|
||||||
|
echo "No tests directory found, skipping tests"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Шаг 3: Проверка стиля кода
|
||||||
|
lint:
|
||||||
|
name: Code Quality
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install linters
|
||||||
|
run: |
|
||||||
|
pip install black ruff mypy
|
||||||
|
|
||||||
|
- name: Check code formatting with black
|
||||||
|
run: |
|
||||||
|
black --check custom_components/motion_light/ || echo "Code formatting issues found"
|
||||||
|
|
||||||
|
- name: Check code with ruff
|
||||||
|
run: |
|
||||||
|
ruff check custom_components/motion_light/ || echo "Ruff issues found"
|
||||||
|
|
||||||
|
# Шаг 4: Создание релиза (только если все проверки пройдены)
|
||||||
|
release:
|
||||||
|
name: Create Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [validate, test, lint]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get tag info
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
TAG=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$PREV_TAG" ]; then
|
||||||
|
COMMITS=$(git log --pretty=format:"- %s" --reverse)
|
||||||
|
else
|
||||||
|
COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s" --reverse)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "commits<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$COMMITS" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
TAG="${{ steps.tag.outputs.tag }}"
|
||||||
|
COMMITS="${{ steps.tag.outputs.commits }}"
|
||||||
|
|
||||||
|
echo "🚀 Создаём релиз $TAG в GitHub..."
|
||||||
|
|
||||||
|
cat > /tmp/release_notes.md << EOF
|
||||||
|
## Изменения в версии $TAG
|
||||||
|
|
||||||
|
$COMMITS
|
||||||
|
EOF
|
||||||
|
|
||||||
|
gh release create "$TAG" \
|
||||||
|
--title "Release $TAG" \
|
||||||
|
--notes-file /tmp/release_notes.md \
|
||||||
|
--target main
|
||||||
|
|
||||||
|
echo "✅ Релиз создан!"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
<!-- version list -->
|
||||||
|
|
||||||
|
## v1.1.2 (2026-07-11)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- FST
|
||||||
|
([`dc85e69`](https://192.168.200.208:3000/sirmrak/motion_recorder/commit/dc85e69e289cb2f13ddb0e51a98bfc207c73a9d6))
|
||||||
|
|
||||||
|
|
||||||
|
## v1.1.1 (2026-07-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Оформление
|
||||||
|
([`6ccca63`](https://192.168.200.208:3000/sirmrak/motion_recorder/commit/6ccca6327021b53a8cd46e62bb0d740f0a609043))
|
||||||
|
|
||||||
|
|
||||||
|
## v1.1.0 (2026-07-05)
|
||||||
|
|
||||||
|
|
||||||
|
## v1.0.0 (2026-07-03)
|
||||||
|
|
||||||
|
- Initial Release
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Motion Recorder for Home Assistant
|
# <img src="custom_components/motion_recorder/brand/icon.png" width="64"> </img> Motion Recorder for Home Assistant
|
||||||
English | [Русский](README.ru.md)
|
English | [Русский](README.ru.md)
|
||||||
|
|
||||||
## A powerful Home Assistant integration for recording camera streams based on motion detection with advanced filtering and automated management.
|
## A powerful Home Assistant integration for recording camera streams based on motion detection with advanced filtering and automated management.
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# Motion Recorder для Home Assistant
|
# <img src="custom_components/motion_recorder/brand/icon.png" width="64"> </img> Motion Recorder для Home Assistant
|
||||||
[English](README.md) | Русский
|
[English](README.md) | Русский
|
||||||
|
|
||||||
Мощная интеграция Home Assistant для записи камер по датчику движения с продвинутой фильтрацией и автоматическим управлением.
|
Мощная интеграция Home Assistant для записи камер по датчику движения с продвинутой фильтрацией и автоматическим управлением.
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
# Enabled state
|
# Enabled state
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
|
|
||||||
|
self._force_stopped = False
|
||||||
|
|
||||||
# Listeners
|
# Listeners
|
||||||
self._unsub_motion_listeners = []
|
self._unsub_motion_listeners = []
|
||||||
self._unsub_force_stop_listener = None
|
self._unsub_force_stop_listener = None
|
||||||
@@ -146,6 +148,7 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
attrs["recordings_count"] = self._recordings_count
|
attrs["recordings_count"] = self._recordings_count
|
||||||
attrs["total_duration"] = self._total_duration
|
attrs["total_duration"] = self._total_duration
|
||||||
attrs["total_size"] = self._total_size
|
attrs["total_size"] = self._total_size
|
||||||
|
attrs["force_stopped"] = self._force_stopped
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -159,17 +162,39 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
_LOGGER.info("Recording %s", "enabled" if enabled else "disabled")
|
_LOGGER.info("Recording %s", "enabled" if enabled else "disabled")
|
||||||
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
await self._cancel_all_timers()
|
||||||
|
|
||||||
|
# Останавливаем запись если идёт
|
||||||
if self._state == STATE_RECORDING:
|
if self._state == STATE_RECORDING:
|
||||||
await self._stop_recording()
|
await self._stop_recording()
|
||||||
|
|
||||||
|
# Переходим в disabled
|
||||||
await self._update_state(STATE_DISABLED)
|
await self._update_state(STATE_DISABLED)
|
||||||
else:
|
else:
|
||||||
await self._update_state(STATE_IDLE)
|
# Включаем только если не заблокированы force_stop
|
||||||
|
if self._force_stopped:
|
||||||
|
_LOGGER.info("Cannot enable: force stop is active")
|
||||||
|
await self._update_state(STATE_DISABLED)
|
||||||
|
else:
|
||||||
|
await self._update_state(STATE_IDLE)
|
||||||
|
|
||||||
self.async_set_updated_data({
|
self.async_set_updated_data({
|
||||||
"state": self._state,
|
"state": self._state,
|
||||||
"attributes": self.attributes
|
"attributes": self.attributes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async def _cancel_all_timers(self) -> None:
|
||||||
|
"""Отменить все активные таймеры."""
|
||||||
|
if self._motion_detect_timer:
|
||||||
|
_LOGGER.debug("Cancelling motion detect timer")
|
||||||
|
self._motion_detect_timer()
|
||||||
|
self._motion_detect_timer = None
|
||||||
|
|
||||||
|
if self._off_delay_timer:
|
||||||
|
_LOGGER.debug("Cancelling off-delay timer")
|
||||||
|
self._off_delay_timer()
|
||||||
|
self._off_delay_timer = None
|
||||||
|
|
||||||
async def async_setup(self):
|
async def async_setup(self):
|
||||||
"""Set up the coordinator."""
|
"""Set up the coordinator."""
|
||||||
_LOGGER.info("Setting up Motion Recorder coordinator for %s", self.camera_entity)
|
_LOGGER.info("Setting up Motion Recorder coordinator for %s", self.camera_entity)
|
||||||
@@ -364,9 +389,43 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
if not new_state:
|
if not new_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_state.state == self.force_stop_state:
|
new_s = new_state.state
|
||||||
_LOGGER.info("Force stop triggered by %s", event.data.get("entity_id"))
|
sensor_entity = event.data.get("entity_id")
|
||||||
self.hass.async_create_task(self._force_stop_recording())
|
|
||||||
|
_LOGGER.debug("Force stop sensor %s changed to: %s", sensor_entity, new_s)
|
||||||
|
|
||||||
|
if new_s == self.force_stop_state:
|
||||||
|
_LOGGER.info("Force stop ACTIVATED by %s", sensor_entity)
|
||||||
|
self.hass.async_create_task(self._activate_force_stop())
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Force stop DEACTIVATED by %s", sensor_entity)
|
||||||
|
self.hass.async_create_task(self._deactivate_force_stop())
|
||||||
|
|
||||||
|
async def _activate_force_stop(self) -> None:
|
||||||
|
"""Активировать force stop блокировку."""
|
||||||
|
self._force_stopped = True
|
||||||
|
_LOGGER.info("Force stop blocking activated")
|
||||||
|
|
||||||
|
# Отменяем все таймеры
|
||||||
|
await self._cancel_all_timers()
|
||||||
|
|
||||||
|
# Останавливаем запись если идёт
|
||||||
|
if self._state == STATE_RECORDING:
|
||||||
|
await self._stop_recording()
|
||||||
|
|
||||||
|
# Переходим в disabled
|
||||||
|
await self._update_state(STATE_DISABLED)
|
||||||
|
|
||||||
|
async def _deactivate_force_stop(self) -> None:
|
||||||
|
"""Деактивировать force stop блокировку."""
|
||||||
|
self._force_stopped = False
|
||||||
|
_LOGGER.info("Force stop blocking deactivated")
|
||||||
|
|
||||||
|
# Возвращаемся в idle только если запись включена
|
||||||
|
if self._enabled:
|
||||||
|
await self._update_state(STATE_IDLE)
|
||||||
|
else:
|
||||||
|
await self._update_state(STATE_DISABLED)
|
||||||
|
|
||||||
def _is_any_motion_active(self) -> bool:
|
def _is_any_motion_active(self) -> bool:
|
||||||
"""Check if any motion sensor is currently in 'on' state."""
|
"""Check if any motion sensor is currently in 'on' state."""
|
||||||
@@ -382,6 +441,10 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
_LOGGER.debug("Recording disabled, ignoring motion")
|
_LOGGER.debug("Recording disabled, ignoring motion")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self._force_stopped:
|
||||||
|
_LOGGER.debug("Force stop active, ignoring motion")
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Motion detected by %s, current state: %s", triggered_by, self._state)
|
_LOGGER.debug("Motion detected by %s, current state: %s", triggered_by, self._state)
|
||||||
|
|
||||||
if self._state == STATE_RECORDING:
|
if self._state == STATE_RECORDING:
|
||||||
@@ -423,6 +486,12 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _start_recording_after_filter(self, _=None):
|
async def _start_recording_after_filter(self, _=None):
|
||||||
"""Start recording after motion filter."""
|
"""Start recording after motion filter."""
|
||||||
|
if not self._enabled or self._force_stopped:
|
||||||
|
_LOGGER.debug("Cannot start recording: enabled=%s, force_stopped=%s",
|
||||||
|
self._enabled, self._force_stopped)
|
||||||
|
await self._update_state(STATE_IDLE if self._enabled else STATE_DISABLED)
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Starting recording after filter")
|
_LOGGER.debug("Starting recording after filter")
|
||||||
self._motion_detect_timer = None
|
self._motion_detect_timer = None
|
||||||
triggered_by = self._attributes.get("triggered_by")
|
triggered_by = self._attributes.get("triggered_by")
|
||||||
@@ -430,6 +499,10 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _handle_motion_stopped(self):
|
async def _handle_motion_stopped(self):
|
||||||
"""Handle motion stopped with off-delay."""
|
"""Handle motion stopped with off-delay."""
|
||||||
|
if self._force_stopped:
|
||||||
|
_LOGGER.debug("Force stop active, ignoring motion stopped")
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Motion stopped, current state: %s", self._state)
|
_LOGGER.debug("Motion stopped, current state: %s", self._state)
|
||||||
|
|
||||||
if self._is_any_motion_active():
|
if self._is_any_motion_active():
|
||||||
@@ -465,10 +538,19 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
_LOGGER.debug("Cancelling motion filter timer")
|
_LOGGER.debug("Cancelling motion filter timer")
|
||||||
self._motion_detect_timer()
|
self._motion_detect_timer()
|
||||||
self._motion_detect_timer = None
|
self._motion_detect_timer = None
|
||||||
await self._update_state(STATE_IDLE)
|
|
||||||
|
if not self._enabled:
|
||||||
|
await self._update_state(STATE_DISABLED)
|
||||||
|
else:
|
||||||
|
await self._update_state(STATE_IDLE)
|
||||||
|
|
||||||
async def _stop_recording_after_delay(self, _=None):
|
async def _stop_recording_after_delay(self, _=None):
|
||||||
"""Stop recording after off-delay."""
|
"""Stop recording after off-delay."""
|
||||||
|
if self._force_stopped or not self._enabled:
|
||||||
|
_LOGGER.debug("Cannot stop recording: enabled=%s, force_stopped=%s",
|
||||||
|
self._enabled, self._force_stopped)
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("Off-delay timer expired")
|
_LOGGER.debug("Off-delay timer expired")
|
||||||
self._off_delay_timer = None
|
self._off_delay_timer = None
|
||||||
|
|
||||||
@@ -482,6 +564,12 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _start_recording(self, triggered_by):
|
async def _start_recording(self, triggered_by):
|
||||||
"""Start recording the camera."""
|
"""Start recording the camera."""
|
||||||
|
if not self._enabled or self._force_stopped:
|
||||||
|
_LOGGER.warning("Cannot start recording: enabled=%s, force_stopped=%s",
|
||||||
|
self._enabled, self._force_stopped)
|
||||||
|
await self._update_state(STATE_IDLE if self._enabled else STATE_DISABLED)
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._current_stream:
|
if self._current_stream:
|
||||||
recorder_output = self._current_stream.outputs().get(_RECORDER_PROVIDER)
|
recorder_output = self._current_stream.outputs().get(_RECORDER_PROVIDER)
|
||||||
@@ -578,13 +666,13 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
_LOGGER.error("Error stopping recording: %s", err, exc_info=True)
|
_LOGGER.error("Error stopping recording: %s", err, exc_info=True)
|
||||||
await self._update_state(STATE_ERROR, error_message=str(err))
|
await self._update_state(STATE_ERROR, error_message=str(err))
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
if not self._enabled:
|
if not self._enabled or self._force_stopped:
|
||||||
await self._update_state(STATE_DISABLED)
|
await self._update_state(STATE_DISABLED)
|
||||||
else:
|
else:
|
||||||
await self._update_state(STATE_IDLE)
|
await self._update_state(STATE_IDLE)
|
||||||
|
|
||||||
async def _force_stop_recording(self):
|
async def _force_stop_recording(self):
|
||||||
"""Force stop recording."""
|
"""Force stop recording (вызывается при активации force_stop)."""
|
||||||
_LOGGER.info("Force stop recording called")
|
_LOGGER.info("Force stop recording called")
|
||||||
await self._stop_recording()
|
await self._stop_recording()
|
||||||
|
|
||||||
@@ -628,9 +716,8 @@ class MotionRecorderCoordinator(DataUpdateCoordinator):
|
|||||||
self._recording_start_time = None
|
self._recording_start_time = None
|
||||||
self._current_filename = None
|
self._current_filename = None
|
||||||
|
|
||||||
# Проверяем, не был ли выключен переключатель во время записи
|
if not self._enabled or self._force_stopped:
|
||||||
if not self._enabled:
|
_LOGGER.debug("Recording disabled or force stopped during finalization, transitioning to disabled")
|
||||||
_LOGGER.debug("Recording disabled during finalization, transitioning to disabled")
|
|
||||||
await self._update_state(STATE_DISABLED)
|
await self._update_state(STATE_DISABLED)
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Transitioning to idle")
|
_LOGGER.debug("Transitioning to idle")
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"domain": "motion_recorder",
|
"domain": "motion_recorder",
|
||||||
"name": "Motion Recorder",
|
"name": "Motion Recorder",
|
||||||
"version": "1.1.0",
|
|
||||||
"documentation": "https://github.com/sirmrak/motion_recorder",
|
|
||||||
"issue_tracker": "https://github.com/sirmrak/motion_recorder/issues",
|
|
||||||
"dependencies": ["camera", "stream"],
|
|
||||||
"codeowners": ["@sirmrak"],
|
"codeowners": ["@sirmrak"],
|
||||||
"requirements": [],
|
"config_flow": true,
|
||||||
|
"dependencies": ["camera", "stream"],
|
||||||
|
"documentation": "https://github.com/sirmrak/motion_recorder",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"config_flow": true
|
"issue_tracker": "https://github.com/sirmrak/motion_recorder/issues",
|
||||||
|
"requirements": [],
|
||||||
|
"version": "1.1.2"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[tool.semantic_release]
|
||||||
|
tag_format = "v{version}"
|
||||||
|
|
||||||
|
version_variables = [
|
||||||
|
"custom_components/motion_recorder/manifest.json:version",
|
||||||
|
]
|
||||||
|
|
||||||
|
commit_message = "chore(release): v{version}"
|
||||||
|
|
||||||
|
# ВАЖНО: не создаём релиз в Gitea (релиз будет в GitHub)
|
||||||
|
upload_to_vcs_release = false
|
||||||
|
|
||||||
|
[tool.semantic_release.remote]
|
||||||
|
type = "gitea"
|
||||||
|
domain = "192.168.200.208:3000"
|
||||||
|
api_domain = "192.168.200.208:3000"
|
||||||
|
|
||||||
|
[tool.semantic_release.branches.main]
|
||||||
|
match = "(main|master)"
|
||||||
|
prerelease = false
|
||||||
|
|
||||||
|
[tool.semantic_release.commit_parser_options]
|
||||||
|
allowed_tags = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "chore", "revert"]
|
||||||
|
minor_tags = ["feat"]
|
||||||
|
patch_tags = ["fix", "perf"]
|
||||||
Reference in New Issue
Block a user