Compare commits

..
88 Commits
Author SHA1 Message Date
semantic-release 7afe64d14c chore(release): v1.1.0-beta.1
Update Version / Update Version (push) Successful in 5s
2026-07-22 06:05:53 +00:00
sirmrak c366bc457a ci: add dev branch support for beta releases
Update Version / Update Version (push) Successful in 9s
2026-07-22 09:05:42 +03:00
sirmrak fa4d133d50 feat: add dev branch for beta releases 2026-07-22 09:04:38 +03:00
semantic-release f129191a48 chore(release): v1.0.17
Update Version / Update Version (push) Successful in 5s
2026-07-17 08:07:41 +00:00
sirmrak 31237e76d9 fix: Test
Update Version / Update Version (push) Successful in 6s
2026-07-17 10:58:44 +03:00
sirmrak fc59a786ed Обновить pyproject.toml
Update Version / Update Version (push) Successful in 5s
2026-07-17 10:57:53 +03:00
sirmrak 71cbd70f9e Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 8s
2026-07-17 10:57:16 +03:00
semantic-release f12371e519 chore(release): v1.0.16
Update Version / Update Version (push) Successful in 5s
2026-07-10 09:09:57 +00:00
sirmrak eae1c9cef2 fix: Test new secrets
Update Version / Update Version (push) Successful in 6s
2026-07-10 12:09:50 +03:00
semantic-release 8dff505bb4 chore(release): v1.0.15
Update Version / Update Version (push) Successful in 5s
2026-07-10 06:49:05 +00:00
sirmrak 342a98327d fix: lux_cooldown_active
Update Version / Update Version (push) Successful in 7s
2026-07-10 09:48:56 +03:00
sirmrak d10e83c750 Добавить LICENSE
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:28:37 +03:00
semantic-release 0deb61f920 chore(release): v1.0.14
Update Version / Update Version (push) Successful in 5s
2026-07-09 19:22:51 +00:00
sirmrak 563095cbcf fix: Удалены переводы атрибутов из custom_components/motion_light/translations
Update Version / Update Version (push) Successful in 6s
2026-07-09 22:22:43 +03:00
sirmrak 952feac482 fixЖ перевод атрибутов удален
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:20:23 +03:00
sirmrak 7c229b37f2 Обновить custom_components/motion_light/translations/ru.json
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:11:42 +03:00
sirmrak 19546a12e6 Обновить custom_components/motion_light/translations/en.json
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:09:15 +03:00
sirmrak 7b9b1030f2 Обновить custom_components/motion_light/translations/en.json
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:08:45 +03:00
sirmrak 13df970b21 Обновить custom_components/motion_light/manifest.json
Update Version / Update Version (push) Successful in 5s
2026-07-09 22:05:41 +03:00
semantic-release 8069a5d635 chore(release): v1.0.13
Update Version / Update Version (push) Successful in 5s
2026-07-09 19:00:28 +00:00
sirmrak 356b61e6e6 fix: Test validation
Update Version / Update Version (push) Successful in 6s
2026-07-09 22:00:19 +03:00
sirmrak 3b93a6a16b Добавление тестов
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:59:37 +03:00
semantic-release aa7eb0a9d9 chore(release): v1.0.12
Update Version / Update Version (push) Successful in 5s
2026-07-09 18:47:22 +00:00
sirmrak 0888c63b80 fix: test
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:47:14 +03:00
sirmrak c6e7b332cf Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:45:52 +03:00
semantic-release 7b4fdfe629 chore(release): v1.0.11
Update Version / Update Version (push) Successful in 5s
2026-07-09 18:35:55 +00:00
sirmrak 15dd7e3880 fix: Test
Update Version / Update Version (push) Failing after 6s
2026-07-09 21:35:49 +03:00
sirmrak 28811370e9 Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:35:26 +03:00
sirmrak a1fca2f4d1 Обновить pyproject.toml
Update Version / Update Version (push) Successful in 6s
2026-07-09 21:34:44 +03:00
semantic-release d479bdb583 chore(release): v1.0.10
Update Version / Update Version (push) Successful in 6s
2026-07-09 18:26:23 +00:00
sirmrak 010a1144c5 fix: Test
Update Version / Update Version (push) Failing after 7s
2026-07-09 21:26:14 +03:00
sirmrak 6cf92d83f0 Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:24:56 +03:00
sirmrak 9f7199beab Обновить pyproject.toml
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:23:53 +03:00
semantic-release a02ae4106b chore(release): v1.0.9
Update Version / Update Version (push) Successful in 4s
2026-07-09 18:08:02 +00:00
sirmrak 6a411cea91 fix: test
Update Version / Update Version (push) Failing after 7s
2026-07-09 21:07:53 +03:00
sirmrak 4ff72fdb07 Обновить pyproject.toml
Update Version / Update Version (push) Successful in 5s
2026-07-09 21:07:03 +03:00
semantic-release 93d1afd7e0 chore(release): v1.0.8
Update Version / Update Version (push) Successful in 5s
2026-07-09 17:49:53 +00:00
sirmrak 11bf31885e fix: Тестовый релиз
Update Version / Update Version (push) Failing after 6s
2026-07-09 20:49:43 +03:00
sirmrak 8b93ed6187 Удалить test.text
Update Version / Update Version (push) Successful in 5s
2026-07-09 20:48:31 +03:00
sirmrak 286f84fbb0 Добавить test.text
Update Version / Update Version (push) Successful in 5s
2026-07-09 20:47:26 +03:00
sirmrak 01a504f0ba Добавить .github/workflows/release.yml
Update Version / Update Version (push) Successful in 5s
2026-07-09 20:44:49 +03:00
semantic-release c97fc433bc chore(release): v1.0.7
Update Version / Update Version (push) Successful in 5s
2026-07-09 17:38:46 +00:00
sirmrak c5a78f998f fix: Тестим авторелиз
Update Version / Update Version (push) Failing after 6s
2026-07-09 20:38:37 +03:00
semantic-release eb106d7fe4 chore(release): v1.0.6
Update Version / Update Version (push) Successful in 5s
2026-07-09 17:15:57 +00:00
sirmrak 4ffa66c312 Удалить CHANGELOG.md
Update Version / Update Version (push) Failing after 6s
2026-07-09 20:15:51 +03:00
sirmrak 3ff545c6df Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 5s
2026-07-09 20:14:48 +03:00
sirmrak 458603e80c Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Failing after 5s
2026-07-09 20:05:55 +03:00
semantic-release 81dbc70b04 chore(release): v1.0.6
Update Version / Update Version (push) Successful in 6s
2026-07-09 16:56:33 +00:00
sirmrak 58ae700e0b fix: тестовое исправление для автоматического релиза
Update Version / Update Version (push) Successful in 6s
2026-07-09 19:56:24 +03:00
sirmrak 399f55c57b Обновить .gitea/workflows/update_version.yml
Update Version / Update Version (push) Successful in 6s
2026-07-09 19:55:24 +03:00
semantic-release 900c40c8a1 chore(release): v1.0.5
/ Update Version (push) Successful in 5s
2026-07-09 16:43:26 +00:00
sirmrak 860584ca79 Добавить .gitea/workflows/update_version.yml
/ Update Version (push) Successful in 7s
2026-07-09 19:43:19 +03:00
sirmrak d230ec6590 Удалить .gitea/workflows/release.yml 2026-07-09 19:40:38 +03:00
sirmrak e0868fae9b fix: Test
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Failing after 41s
2026-07-09 14:09:49 +03:00
sirmrak 59d3f9accd fix: Release 2026-07-09 14:06:50 +03:00
semantic-release ee6dde688c chore(release): v1.0.7
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Successful in 41s
2026-07-09 11:01:55 +00:00
sirmrak e10e3e7c19 fix: Run Semantic Release
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Failing after 43s
2026-07-09 14:01:05 +03:00
semantic-release 33756f1659 chore(release): v1.0.6
HA Integration Release / Validate HA Integration (push) Successful in 3s
HA Integration Release / Semantic Release (push) Successful in 41s
2026-07-09 10:58:00 +00:00
sirmrak bd34c9fd81 fix: release.yaml
HA Integration Release / Validate HA Integration (push) Successful in 3s
HA Integration Release / Semantic Release (push) Failing after 43s
2026-07-09 13:57:12 +03:00
semantic-release 590e110efa chore(release): v1.0.5
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Successful in 5s
2026-07-09 10:50:44 +00:00
sirmrak 92b9392149 fix: test commit for release
HA Integration Release / Validate HA Integration (push) Successful in 3s
HA Integration Release / Semantic Release (push) Failing after 6s
2026-07-09 13:50:30 +03:00
semantic-release fced8328d7 chore(release): v1.0.6
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Successful in 5s
2026-07-09 10:43:47 +00:00
sirmrak ae301f25db fix: disable SSL verification for semantic-release
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Failing after 6s
2026-07-09 13:43:33 +03:00
semantic-release fcc4de59c1 chore(release): v1.0.5
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Successful in 5s
2026-07-09 10:40:40 +00:00
sirmrak 4587148d70 fix: configure semantic-release for Gitea with SSL
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Failing after 5s
2026-07-09 13:40:26 +03:00
semantic-release 6c73814724 1.0.0
HA Integration Release / Validate HA Integration (push) Successful in 2s
HA Integration Release / Semantic Release (push) Successful in 6s
Automatically generated by python-semantic-release
2026-07-09 10:32:03 +00:00
sirmrak 8c4009dc82 Обновить .gitea/workflows/release.yml
HA Integration Release / Validate HA Integration (push) Successful in 3s
HA Integration Release / Semantic Release (push) Failing after 15s
2026-07-09 13:31:39 +03:00
sirmrak 37de17749e Обновить .gitea/workflows/release.yml
HA Integration Release / Validate HA Integration (push) Failing after 3s
HA Integration Release / Semantic Release (push) Has been skipped
2026-07-09 13:28:52 +03:00
sirmrak d4c2548498 fix: Изменил размер картинки
Test Runner / Test Job (push) Successful in 4s
HA Integration Release / Validate HA Integration (push) Failing after 4s
HA Integration Release / Semantic Release (push) Has been skipped
2026-07-09 13:17:44 +03:00
sirmrak f889f18cd9 Добавить .gitea/workflows/release.yml
HA Integration Release / Validate HA Integration (push) Failing after 16s
HA Integration Release / Semantic Release (push) Has been skipped
Test Runner / Test Job (push) Successful in 7s
2026-07-09 13:16:33 +03:00
sirmrak 9abb91c3f9 Добавить pyproject.toml
Test Runner / Test Job (push) Successful in 4s
2026-07-09 13:15:26 +03:00
sirmrak 76388df08c GIT_SSL_NO_VERIFY: true
Test Runner / Test Job (push) Successful in 18s
2026-07-09 13:07:24 +03:00
sirmrak 9626fafd2f Добавить .gitea/workflows/test.yml
Test Runner / Test Job (push) Failing after 55s
2026-07-09 12:52:39 +03:00
sirmrak 71eb3a7c86 Обновить custom_components/motion_light/manifest.json 2026-07-09 09:56:10 +03:00
sirmrak b9dd8bcad7 Обновить custom_components/motion_light/translations/en.json 2026-07-09 09:48:46 +03:00
sirmrak b1a5eca9a5 Обновить custom_components/motion_light/translations/ru.json 2026-07-09 09:47:31 +03:00
sirmrak 182a46cf92 Обновить custom_components/motion_light/manifest.json 2026-07-09 09:22:02 +03:00
sirmrak e5c10104b8 Обновить custom_components/motion_light/coordinator.py 2026-07-09 09:19:53 +03:00
sirmrak 541ea43435 Обновить custom_components/motion_light/coordinator.py 2026-07-08 15:43:45 +03:00
sirmrak 62d4641097 Обновить custom_components/motion_light/translations/en.json 2026-07-08 15:26:07 +03:00
sirmrak 153d5d479f Обновить custom_components/motion_light/translations/ru.json 2026-07-08 15:25:32 +03:00
sirmrak 7ea6821116 Обновить custom_components/motion_light/sensor.py 2026-07-08 15:22:31 +03:00
sirmrak b1747ef487 Обновить custom_components/motion_light/coordinator.py 2026-07-08 15:21:43 +03:00
sirmrak ff3434612b Обновить custom_components/motion_light/const.py 2026-07-08 15:20:18 +03:00
sirmrak 57bb21e5ba Обновить custom_components/motion_light/manifest.json 2026-07-08 14:48:49 +03:00
sirmrak 18b6d83af2 Обновить custom_components/motion_light/number.py 2026-07-08 14:42:07 +03:00
sirmrak f097f7dabb Обновить custom_components/motion_light/manifest.json 2026-07-08 14:32:32 +03:00
sirmrak 55488681b2 Не создаем сущности lux при отсутствии датчика освещенности 2026-07-08 14:26:08 +03:00
15 changed files with 843 additions and 355 deletions
+33
View File
@@ -0,0 +1,33 @@
name: Test Runner
on:
push:
branches:
- main
- master
env:
GIT_SSL_NO_VERIFY: true
jobs:
test:
name: Test Job
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Echo message
run: |
echo "Hello from Gitea Actions!"
echo "Runner is working correctly!"
date
pwd
ls -la
- name: Test Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Check Python version
run: python --version
+46
View File
@@ -0,0 +1,46 @@
name: Update Version
on:
push:
branches:
- main
- master
- dev
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: |
unset GITHUB_SERVER_URL
unset GITHUB_API_URL
unset GITHUB_REPOSITORY
semantic-release version
+132
View File
@@ -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 "✅ Релиз создан!"
+152
View File
@@ -0,0 +1,152 @@
# CHANGELOG
<!-- version list -->
## v1.1.0-beta.1 (2026-07-22)
### Features
- Add dev branch for beta releases
([`fa4d133`](https://git.darkmode.duckdns.org/sirmrak/motion_light/commit/fa4d133d50e896428883919c98ef830780aacce2))
## v1.0.17 (2026-07-17)
### Bug Fixes
- Test
([`31237e7`](https://git.darkmode.duckdns.org/sirmrak/motion_light/commit/31237e76d9c770dae4e04fad6d7a0196340a60c5))
## v1.0.16 (2026-07-10)
### Bug Fixes
- Test new secrets
([`eae1c9c`](https://192.168.200.208:3000/sirmrak/motion_light/commit/eae1c9cef21aa9285a6e5b2174949b6bf2942154))
## v1.0.15 (2026-07-10)
### Bug Fixes
- Lux_cooldown_active
([`342a983`](https://192.168.200.208:3000/sirmrak/motion_light/commit/342a98327d2297977323e12f84e0dc9bc8b3a49d))
## v1.0.14 (2026-07-09)
### Bug Fixes
- Удалены переводы атрибутов из custom_components/motion_light/translations
([`563095c`](https://192.168.200.208:3000/sirmrak/motion_light/commit/563095cbcf77d22d02a7176faf5aec6837d18b37))
## v1.0.13 (2026-07-09)
### Bug Fixes
- Test validation
([`356b61e`](https://192.168.200.208:3000/sirmrak/motion_light/commit/356b61e6e618a423503d44d2f0d9ef96fee0da0d))
## v1.0.12 (2026-07-09)
### Bug Fixes
- Test
([`0888c63`](https://192.168.200.208:3000/sirmrak/motion_light/commit/0888c63b804be1e5bedd7e72fbfd142dd4f88287))
## v1.0.11 (2026-07-09)
### Bug Fixes
- Test
([`15dd7e3`](https://192.168.200.208:3000/sirmrak/motion_light/commit/15dd7e3880dad782f502eb70db6aa8ca23f803bc))
## v1.0.10 (2026-07-09)
### Bug Fixes
- Test
([`010a114`](https://192.168.200.208:3000/sirmrak/motion_light/commit/010a1144c5dc5252831126bfc9592036a7ca2518))
## v1.0.9 (2026-07-09)
### Bug Fixes
- Test
([`6a411ce`](https://192.168.200.208:3000/sirmrak/motion_light/commit/6a411cea91047e9714519dbb190d5d92252b024a))
## v1.0.8 (2026-07-09)
### Bug Fixes
- Тестовый релиз
([`11bf318`](https://192.168.200.208:3000/sirmrak/motion_light/commit/11bf31885eb113d02cf5c1558446a4671c1dc202))
## v1.0.7 (2026-07-09)
### Bug Fixes
- Тестим авторелиз
([`c5a78f9`](https://192.168.200.208:3000/sirmrak/motion_light/commit/c5a78f998fda972f0dfe4f938f0855d3c1d28aee))
## v1.0.6 (2026-07-09)
### Bug Fixes
- Тестовое исправление для автоматического релиза
([`58ae700`](https://192.168.200.208:3000/sirmrak/motion_light/commit/58ae700e0ba31b664222eccd96aaa17d30d2eff4))
## v1.0.5 (2026-07-09)
### Bug Fixes
- Configure semantic-release for Gitea with SSL
([`4587148`](https://192.168.200.208:3000/sirmrak/motion_light/commit/4587148d70e362fdd2a47523df0da959499fba2f))
- Disable SSL verification for semantic-release
([`ae301f2`](https://192.168.200.208:3000/sirmrak/motion_light/commit/ae301f25db468e56f9378582215b4dac6979722e))
- Release
([`59d3f9a`](https://192.168.200.208:3000/sirmrak/motion_light/commit/59d3f9accd5d2cb3b8e1ef4f403c7445e7da7b46))
- Release.yaml
([`bd34c9f`](https://192.168.200.208:3000/sirmrak/motion_light/commit/bd34c9fd81002974c08a6f349f0234b4e78353aa))
- Run Semantic Release
([`e10e3e7`](https://192.168.200.208:3000/sirmrak/motion_light/commit/e10e3e7c19c2da63289cd499cca5a5eacee22157))
- Test
([`e0868fa`](https://192.168.200.208:3000/sirmrak/motion_light/commit/e0868fae9ba86cd76eb070c5057960741eecba1e))
- Test commit for release
([`92b9392`](https://192.168.200.208:3000/sirmrak/motion_light/commit/92b93921498c7afd21d1dceec51518894c1efdcd))
- Изменил размер картинки
([`d4c2548`](https://192.168.200.208:3000/sirmrak/motion_light/commit/d4c25484981a33ab4455027ffb4ef693256aa414))
## v1.0.4 (2026-07-09)
## v1.0.3 (2026-07-08)
## v1.0.2 (2026-07-08)
## v1.0.1 (2026-07-08)
## v1.0.0 (2026-07-08)
- Initial Release
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 sirmrak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1 -1
View File
@@ -1,5 +1,5 @@
[English](README.md) | Русский [English](README.md) | Русский
# <img src="custom_components/motion_light/brand/icon.png" width="64"> </img> Motion Light # <img src="custom_components/motion_light/brand/icon.png" width="32"> </img> Motion Light
Умная интеграция для Home Assistant на основе конечного автомата (FSM) для управления освещением по датчикам движения, освещенности и ручным переключениям. Умная интеграция для Home Assistant на основе конечного автомата (FSM) для управления освещением по датчикам движения, освещенности и ручным переключениям.
+2 -2
View File
@@ -1,4 +1,4 @@
"""The Motion Light integration.""" """The Motion Light integration. Test"""
import logging import logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@@ -44,4 +44,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update.""" """Handle options update."""
_LOGGER.info("Options updated, reloading Motion Light '%s'", entry.title) _LOGGER.info("Options updated, reloading Motion Light '%s'", entry.title)
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
+1
View File
@@ -46,5 +46,6 @@ STATE_ACTIVE = "active"
STATE_OFF = "off" STATE_OFF = "off"
STATE_MANUAL_ON = "manual_on" STATE_MANUAL_ON = "manual_on"
STATE_MANUAL_OFF_COOLDOWN = "manual_off_cooldown" STATE_MANUAL_OFF_COOLDOWN = "manual_off_cooldown"
STATE_FORCE_STOPPED = "force_stopped"
STATE_ERROR = "error" STATE_ERROR = "error"
STATE_DISABLED = "disabled" STATE_DISABLED = "disabled"
+84 -24
View File
@@ -28,6 +28,7 @@ from .const import (
STATE_OFF, STATE_OFF,
STATE_MANUAL_ON, STATE_MANUAL_ON,
STATE_MANUAL_OFF_COOLDOWN, STATE_MANUAL_OFF_COOLDOWN,
STATE_FORCE_STOPPED,
STATE_ERROR, STATE_ERROR,
STATE_DISABLED, STATE_DISABLED,
) )
@@ -110,6 +111,9 @@ class MotionLightCoordinator(DataUpdateCoordinator):
attrs["main_sensors"] = self.main_sensors attrs["main_sensors"] = self.main_sensors
attrs["extend_sensors"] = self.extend_sensors attrs["extend_sensors"] = self.extend_sensors
attrs["lux_sensor"] = self.lux_sensor attrs["lux_sensor"] = self.lux_sensor
attrs["force_stop_sensor"] = self.force_stop_sensor
attrs["force_stop_state"] = self.force_stop_state
attrs["force_stop_active"] = self._state == STATE_FORCE_STOPPED
attrs["active_main_sensors"] = self._get_active_main_sensors() attrs["active_main_sensors"] = self._get_active_main_sensors()
attrs["active_extend_sensors"] = self._get_active_extend_sensors() attrs["active_extend_sensors"] = self._get_active_extend_sensors()
attrs["lux_value"] = self._get_current_lux() attrs["lux_value"] = self._get_current_lux()
@@ -154,8 +158,6 @@ class MotionLightCoordinator(DataUpdateCoordinator):
def set_manual_idle_timeout(self, value: float) -> None: def set_manual_idle_timeout(self, value: float) -> None:
self.manual_idle_timeout = int(value) self.manual_idle_timeout = int(value)
self.logger.info("Manual idle timeout set to %d sec", self.manual_idle_timeout) self.logger.info("Manual idle timeout set to %d sec", self.manual_idle_timeout)
# Если таймер уже тикает, а мы изменили значение на 0, нужно его отменить
if self.manual_idle_timeout <= 0 and self._manual_idle_timer: if self.manual_idle_timeout <= 0 and self._manual_idle_timer:
self.logger.info("Manual idle timeout set to 0, cancelling active timer") self.logger.info("Manual idle timeout set to 0, cancelling active timer")
self._manual_idle_timer() self._manual_idle_timer()
@@ -175,6 +177,15 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.logger.info("Switches: %s, Main sensors: %s, Extend sensors: %s, Lux: %s", self.logger.info("Switches: %s, Main sensors: %s, Extend sensors: %s, Lux: %s",
self.switch_entities, self.main_sensors, self.extend_sensors, self.lux_sensor) self.switch_entities, self.main_sensors, self.extend_sensors, self.lux_sensor)
# Проверяем состояние force_stop_sensor при старте
if self.force_stop_sensor:
force_stop_state_obj = self.hass.states.get(self.force_stop_sensor)
if force_stop_state_obj and force_stop_state_obj.state == self.force_stop_state:
self.logger.info("Force stop sensor is already in stop state at startup")
await self._update_state(STATE_FORCE_STOPPED)
await self._start_listening()
return
any_on = any( any_on = any(
self.hass.states.get(s) and self.hass.states.get(s).state == "on" self.hass.states.get(s) and self.hass.states.get(s).state == "on"
for s in self.switch_entities for s in self.switch_entities
@@ -294,6 +305,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.logger.debug("Integration disabled, ignoring") self.logger.debug("Integration disabled, ignoring")
return return
# БЛОКИРОВЩИК: Игнорируем движение, если активна принудительная остановка
if self._state == STATE_FORCE_STOPPED:
self.logger.debug("Ignoring during force_stopped")
return
self.logger.debug("Main sensor ON by %s, current state: %s", triggered_by, self._state) self.logger.debug("Main sensor ON by %s, current state: %s", triggered_by, self._state)
if self._state == STATE_MANUAL_OFF_COOLDOWN: if self._state == STATE_MANUAL_OFF_COOLDOWN:
@@ -302,7 +318,6 @@ class MotionLightCoordinator(DataUpdateCoordinator):
if self._state == STATE_MANUAL_ON: if self._state == STATE_MANUAL_ON:
self.logger.debug("During manual_on, resetting idle timer") self.logger.debug("During manual_on, resetting idle timer")
# ИЗМЕНЕНИЕ: Сбрасываем таймер только если он включен (>0)
if self.manual_idle_timeout > 0: if self.manual_idle_timeout > 0:
self._reset_manual_idle_timer() self._reset_manual_idle_timer()
return return
@@ -394,6 +409,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.logger.debug("Integration disabled, ignoring") self.logger.debug("Integration disabled, ignoring")
return return
# БЛОКИРОВЩИК: Игнорируем движение, если активна принудительная остановка
if self._state == STATE_FORCE_STOPPED:
self.logger.debug("Ignoring during force_stopped")
return
self.logger.debug("Extend sensor ON by %s, current state: %s", triggered_by, self._state) self.logger.debug("Extend sensor ON by %s, current state: %s", triggered_by, self._state)
if self._state == STATE_IDLE: if self._state == STATE_IDLE:
@@ -406,7 +426,6 @@ class MotionLightCoordinator(DataUpdateCoordinator):
if self._state == STATE_MANUAL_ON: if self._state == STATE_MANUAL_ON:
self.logger.debug("During manual_on, resetting idle timer") self.logger.debug("During manual_on, resetting idle timer")
# ИЗМЕНЕНИЕ: Сбрасываем таймер только если он включен (>0)
if self.manual_idle_timeout > 0: if self.manual_idle_timeout > 0:
self._reset_manual_idle_timer() self._reset_manual_idle_timer()
return return
@@ -439,6 +458,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
"""Handle target switch state change.""" """Handle target switch state change."""
if not self.respect_manual: if not self.respect_manual:
return return
# БЛОКИРОВЩИК: Игнорируем ручные переключения, если активна принудительная остановка
if self._state == STATE_FORCE_STOPPED:
self.logger.debug("Ignoring manual change during force_stopped")
return
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
old_state = event.data.get("old_state") old_state = event.data.get("old_state")
@@ -485,9 +509,27 @@ class MotionLightCoordinator(DataUpdateCoordinator):
if not new_state: if not new_state:
return return
# Если датчик перешел в блокирующее состояние
if new_state.state == self.force_stop_state: if new_state.state == self.force_stop_state:
self.logger.info("Force stop triggered by %s", event.data.get("entity_id")) self.logger.info("Force stop triggered by %s", event.data.get("entity_id"))
self.hass.async_create_task(self._force_stop()) self.hass.async_create_task(self._enter_force_stop())
# Если датчик вышел из блокирующего состояния
else:
self.logger.info("Force stop released by %s", event.data.get("entity_id"))
self.hass.async_create_task(self._exit_force_stop())
async def _enter_force_stop(self):
"""Enter force_stopped state."""
self.logger.info("Entering force_stopped state")
self._cancel_all_timers()
if self._state in (STATE_ON, STATE_DELAYING, STATE_ACTIVE, STATE_MANUAL_ON):
await self._turn_off_switches()
await self._update_state(STATE_FORCE_STOPPED)
async def _exit_force_stop(self):
"""Exit force_stopped state to idle."""
self.logger.info("Exiting force_stopped state")
await self._update_state(STATE_IDLE)
def _is_any_main_sensor_active(self) -> bool: def _is_any_main_sensor_active(self) -> bool:
"""Check if any main sensor is currently in 'on' state.""" """Check if any main sensor is currently in 'on' state."""
@@ -561,6 +603,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
LUX_CHECK_INTERVAL, LUX_CHECK_INTERVAL,
self._check_lux_periodically, self._check_lux_periodically,
) )
self.async_set_updated_data({
"state": self._state,
"attributes": self.attributes,
})
def _stop_lux_check_timer(self): def _stop_lux_check_timer(self):
"""Stop periodic lux check timer.""" """Stop periodic lux check timer."""
@@ -568,7 +615,24 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.logger.debug("Stopping lux check timer") self.logger.debug("Stopping lux check timer")
self._lux_check_timer() self._lux_check_timer()
self._lux_check_timer = None self._lux_check_timer = None
self.async_set_updated_data({
"state": self._state,
"attributes": self.attributes,
})
def _cancel_lux_cooldown(self):
"""Cancel lux cooldown when light is turned on."""
if self._lux_cooldown_active:
self.logger.debug("Cancelling lux cooldown")
self._lux_cooldown_active = False
if self._lux_cooldown_timer:
self._lux_cooldown_timer()
self._lux_cooldown_timer = None
self.async_set_updated_data({
"state": self._state,
"attributes": self.attributes,
})
@callback @callback
def _check_lux_periodically(self, _=None): def _check_lux_periodically(self, _=None):
"""Check lux periodically.""" """Check lux periodically."""
@@ -605,7 +669,8 @@ class MotionLightCoordinator(DataUpdateCoordinator):
if not self._is_any_sensor_active(): if not self._is_any_sensor_active():
self.logger.debug("No sensors active, not turning on") self.logger.debug("No sensors active, not turning on")
return return
self._cancel_lux_cooldown()
triggered_by = self._attributes.get("triggered_by") triggered_by = self._attributes.get("triggered_by")
await self._turn_on_switches() await self._turn_on_switches()
await self._update_state(STATE_ON, triggered_by=triggered_by) await self._update_state(STATE_ON, triggered_by=triggered_by)
@@ -620,7 +685,7 @@ class MotionLightCoordinator(DataUpdateCoordinator):
await self._update_state(STATE_IDLE) await self._update_state(STATE_IDLE)
self._start_lux_check_timer() self._start_lux_check_timer()
return return
self._cancel_lux_cooldown()
triggered_by = self._attributes.get("triggered_by") triggered_by = self._attributes.get("triggered_by")
await self._turn_on_switches() await self._turn_on_switches()
await self._update_state(STATE_ON, triggered_by=triggered_by) await self._update_state(STATE_ON, triggered_by=triggered_by)
@@ -658,11 +723,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.hass.async_create_task(self._update_state(STATE_IDLE)) self.hass.async_create_task(self._update_state(STATE_IDLE))
async def _turn_on_switches(self) -> None: async def _turn_on_switches(self) -> None:
"""Turn on switches with our context.""" """Turn on switches/lights with our context."""
ctx = Context( ctx = Context(
id=f"motion_light_{self.entry.entry_id}_{dt_util.utcnow().timestamp()}" id=f"motion_light_{self.entry.entry_id}_{dt_util.utcnow().timestamp()}"
) )
self.logger.info("Turning ON switches: %s", self.switch_entities) self.logger.info("Turning ON entities: %s", self.switch_entities)
await self.hass.services.async_call( await self.hass.services.async_call(
"homeassistant", "homeassistant",
"turn_on", "turn_on",
@@ -671,11 +736,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
) )
async def _turn_off_switches(self) -> None: async def _turn_off_switches(self) -> None:
"""Turn off switches with our context.""" """Turn off switches/lights with our context."""
ctx = Context( ctx = Context(
id=f"motion_light_{self.entry.entry_id}_{dt_util.utcnow().timestamp()}" id=f"motion_light_{self.entry.entry_id}_{dt_util.utcnow().timestamp()}"
) )
self.logger.info("Turning OFF switches: %s", self.switch_entities) self.logger.info("Turning OFF entities: %s", self.switch_entities)
await self.hass.services.async_call( await self.hass.services.async_call(
"homeassistant", "homeassistant",
"turn_off", "turn_off",
@@ -685,8 +750,8 @@ class MotionLightCoordinator(DataUpdateCoordinator):
async def _enter_manual_on(self): async def _enter_manual_on(self):
"""Enter manual_on state.""" """Enter manual_on state."""
self._cancel_lux_cooldown()
await self._update_state(STATE_MANUAL_ON) await self._update_state(STATE_MANUAL_ON)
# ИЗМЕНЕНИЕ: Запускаем таймер только если он не отключен (не равен 0)
if self.manual_idle_timeout > 0: if self.manual_idle_timeout > 0:
self._reset_manual_idle_timer() self._reset_manual_idle_timer()
else: else:
@@ -694,7 +759,6 @@ class MotionLightCoordinator(DataUpdateCoordinator):
def _reset_manual_idle_timer(self): def _reset_manual_idle_timer(self):
"""Reset manual idle timer.""" """Reset manual idle timer."""
# ИЗМЕНЕНИЕ: Защита от создания таймера при значении 0
if self.manual_idle_timeout <= 0: if self.manual_idle_timeout <= 0:
return return
@@ -715,8 +779,8 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self.hass.async_create_task(self._turn_off_switches_manual()) self.hass.async_create_task(self._turn_off_switches_manual())
async def _turn_off_switches_manual(self): async def _turn_off_switches_manual(self):
"""Turn off switches in manual mode.""" """Turn off switches/lights in manual mode."""
self.logger.info("Turning OFF switches (manual timeout): %s", self.switch_entities) self.logger.info("Turning OFF entities (manual timeout): %s", self.switch_entities)
await self.hass.services.async_call( await self.hass.services.async_call(
"homeassistant", "homeassistant",
"turn_off", "turn_off",
@@ -755,15 +819,11 @@ class MotionLightCoordinator(DataUpdateCoordinator):
self._lux_cooldown_active = False self._lux_cooldown_active = False
self._lux_cooldown_timer = None self._lux_cooldown_timer = None
self.logger.info("Lux cooldown expired") self.logger.info("Lux cooldown expired")
self.async_set_updated_data({
async def _force_stop(self): "state": self._state,
"""Force stop — turn off switches.""" "attributes": self.attributes,
self.logger.info("Force stop called") })
self._cancel_all_timers()
await self._turn_off_switches()
await self._update_state(STATE_OFF)
async_call_later(self.hass, 1, self._transition_to_idle)
def _cancel_all_timers(self): def _cancel_all_timers(self):
"""Cancel all timers.""" """Cancel all timers."""
if self._motion_detect_timer: if self._motion_detect_timer:
+10 -10
View File
@@ -1,12 +1,12 @@
{ {
"domain": "motion_light", "domain": "motion_light",
"name": "Motion Light", "name": "Motion Light",
"version": "1.0.0", "codeowners": ["@sirmrak"],
"documentation": "https://github.com/sirmrak/motion_light", "config_flow": true,
"issue_tracker": "https://github.com/sirmrak/motion_light/issues", "dependencies": [],
"dependencies": [], "documentation": "https://github.com/sirmrak/motion_light",
"codeowners": ["@sirmrak"], "iot_class": "local_push",
"requirements": [], "issue_tracker": "https://github.com/sirmrak/motion_light/issues",
"iot_class": "local_push", "requirements": [],
"config_flow": true "version": "1.1.0-beta.1"
} }
+172 -164
View File
@@ -1,165 +1,173 @@
"""Number platform for Motion Light settings.""" """Number platform for Motion Light settings."""
import logging import logging
from homeassistant.components.number import NumberEntity from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTime from homeassistant.const import UnitOfTime
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from .const import ( from .const import (
DOMAIN, DOMAIN,
ENTITY_MOTION_FILTER, CONF_LUX_SENSOR,
ENTITY_OFF_DELAY, ENTITY_MOTION_FILTER,
ENTITY_LUX_THRESHOLD, ENTITY_OFF_DELAY,
ENTITY_LUX_COOLDOWN, ENTITY_LUX_THRESHOLD,
ENTITY_MANUAL_IDLE_TIMEOUT, ENTITY_LUX_COOLDOWN,
ENTITY_MANUAL_OFF_COOLDOWN, ENTITY_MANUAL_IDLE_TIMEOUT,
DEFAULT_MOTION_FILTER, ENTITY_MANUAL_OFF_COOLDOWN,
DEFAULT_OFF_DELAY, DEFAULT_MOTION_FILTER,
DEFAULT_LUX_THRESHOLD, DEFAULT_OFF_DELAY,
DEFAULT_LUX_COOLDOWN, DEFAULT_LUX_THRESHOLD,
DEFAULT_MANUAL_IDLE_TIMEOUT, DEFAULT_LUX_COOLDOWN,
DEFAULT_MANUAL_OFF_COOLDOWN, DEFAULT_MANUAL_IDLE_TIMEOUT,
) DEFAULT_MANUAL_OFF_COOLDOWN,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER = logging.getLogger(__name__)
class PrefixLoggerAdapter(logging.LoggerAdapter):
"""Logger adapter that adds a prefix to log messages.""" class PrefixLoggerAdapter(logging.LoggerAdapter):
"""Logger adapter that adds a prefix to log messages."""
def process(self, msg, kwargs):
"""Add prefix to the log message.""" def process(self, msg, kwargs):
return f"[{self.extra.get('name', 'Unknown')}] {msg}", kwargs """Add prefix to the log message."""
return f"[{self.extra.get('name', 'Unknown')}] {msg}", kwargs
TRANSLATION_KEYS = {
ENTITY_MOTION_FILTER: "motion_light_motion_filter", TRANSLATION_KEYS = {
ENTITY_OFF_DELAY: "motion_light_off_delay", ENTITY_MOTION_FILTER: "motion_light_motion_filter",
ENTITY_LUX_THRESHOLD: "motion_light_lux_threshold", ENTITY_OFF_DELAY: "motion_light_off_delay",
ENTITY_LUX_COOLDOWN: "motion_light_lux_cooldown", ENTITY_LUX_THRESHOLD: "motion_light_lux_threshold",
ENTITY_MANUAL_IDLE_TIMEOUT: "motion_light_manual_idle_timeout", ENTITY_LUX_COOLDOWN: "motion_light_lux_cooldown",
ENTITY_MANUAL_OFF_COOLDOWN: "motion_light_manual_off_cooldown", ENTITY_MANUAL_IDLE_TIMEOUT: "motion_light_manual_idle_timeout",
} ENTITY_MANUAL_OFF_COOLDOWN: "motion_light_manual_off_cooldown",
}
ICONS = {
ENTITY_MOTION_FILTER: "mdi:timer-outline", ICONS = {
ENTITY_OFF_DELAY: "mdi:timer-sand", ENTITY_MOTION_FILTER: "mdi:timer-outline",
ENTITY_LUX_THRESHOLD: "mdi:brightness-5", ENTITY_OFF_DELAY: "mdi:timer-sand",
ENTITY_LUX_COOLDOWN: "mdi:timer-pause", ENTITY_LUX_THRESHOLD: "mdi:brightness-5",
ENTITY_MANUAL_IDLE_TIMEOUT: "mdi:timer-alert", ENTITY_LUX_COOLDOWN: "mdi:timer-pause",
ENTITY_MANUAL_OFF_COOLDOWN: "mdi:timer-off", ENTITY_MANUAL_IDLE_TIMEOUT: "mdi:timer-alert",
} ENTITY_MANUAL_OFF_COOLDOWN: "mdi:timer-off",
}
UNITS = {
ENTITY_MOTION_FILTER: UnitOfTime.SECONDS, UNITS = {
ENTITY_OFF_DELAY: UnitOfTime.SECONDS, ENTITY_MOTION_FILTER: UnitOfTime.SECONDS,
ENTITY_LUX_THRESHOLD: "lx", ENTITY_OFF_DELAY: UnitOfTime.SECONDS,
ENTITY_LUX_COOLDOWN: UnitOfTime.SECONDS, ENTITY_LUX_THRESHOLD: "lx",
ENTITY_MANUAL_IDLE_TIMEOUT: UnitOfTime.SECONDS, ENTITY_LUX_COOLDOWN: UnitOfTime.SECONDS,
ENTITY_MANUAL_OFF_COOLDOWN: UnitOfTime.SECONDS, ENTITY_MANUAL_IDLE_TIMEOUT: UnitOfTime.SECONDS,
} ENTITY_MANUAL_OFF_COOLDOWN: UnitOfTime.SECONDS,
}
async def async_setup_entry(
hass: HomeAssistant, async def async_setup_entry(
entry: ConfigEntry, hass: HomeAssistant,
async_add_entities: AddEntitiesCallback, entry: ConfigEntry,
) -> None: async_add_entities: AddEntitiesCallback,
"""Set up Motion Light number entities.""" ) -> None:
coordinator = hass.data[DOMAIN][entry.entry_id] """Set up Motion Light number entities."""
entities = [ coordinator = hass.data[DOMAIN][entry.entry_id]
MotionLightNumber(coordinator, entry, ENTITY_MOTION_FILTER, 0, 10, 1, DEFAULT_MOTION_FILTER),
MotionLightNumber(coordinator, entry, ENTITY_OFF_DELAY, 0, 600, 5, DEFAULT_OFF_DELAY), lux_sensor = entry.options.get(CONF_LUX_SENSOR) or entry.data.get(CONF_LUX_SENSOR)
MotionLightNumber(coordinator, entry, ENTITY_LUX_THRESHOLD, 1, 1000, 10, DEFAULT_LUX_THRESHOLD),
MotionLightNumber(coordinator, entry, ENTITY_LUX_COOLDOWN, 0, 300, 5, DEFAULT_LUX_COOLDOWN), entities = [
MotionLightNumber(coordinator, entry, ENTITY_MANUAL_IDLE_TIMEOUT, 0, 3600, 60, DEFAULT_MANUAL_IDLE_TIMEOUT), MotionLightNumber(coordinator, entry, ENTITY_MOTION_FILTER, 0, 10, 1, DEFAULT_MOTION_FILTER),
MotionLightNumber(coordinator, entry, ENTITY_MANUAL_OFF_COOLDOWN, 0, 300, 5, DEFAULT_MANUAL_OFF_COOLDOWN), MotionLightNumber(coordinator, entry, ENTITY_OFF_DELAY, 0, 600, 5, DEFAULT_OFF_DELAY),
] MotionLightNumber(coordinator, entry, ENTITY_MANUAL_IDLE_TIMEOUT, 0, 3600, 30, DEFAULT_MANUAL_IDLE_TIMEOUT),
async_add_entities(entities) MotionLightNumber(coordinator, entry, ENTITY_MANUAL_OFF_COOLDOWN, 0, 300, 5, DEFAULT_MANUAL_OFF_COOLDOWN),
]
class MotionLightNumber(CoordinatorEntity, NumberEntity, RestoreEntity): if lux_sensor:
"""Representation of a Motion Light setting as a number entity.""" entities.extend([
_attr_has_entity_name = True MotionLightNumber(coordinator, entry, ENTITY_LUX_THRESHOLD, 1, 1000, 10, DEFAULT_LUX_THRESHOLD),
MotionLightNumber(coordinator, entry, ENTITY_LUX_COOLDOWN, 0, 300, 5, DEFAULT_LUX_COOLDOWN),
def __init__(self, coordinator, entry, entity_id, min_val, max_val, step, default): ])
"""Initialize the number entity."""
super().__init__(coordinator) async_add_entities(entities)
self.entry = entry
class MotionLightNumber(CoordinatorEntity, NumberEntity, RestoreEntity):
self.logger = PrefixLoggerAdapter(_LOGGER, {"name": entry.title}) """Representation of a Motion Light setting as a number entity."""
_attr_has_entity_name = True
self._entity_suffix = entity_id
self._attr_unique_id = f"{entry.entry_id}_{entity_id}" def __init__(self, coordinator, entry, entity_id, min_val, max_val, step, default):
self._attr_translation_key = TRANSLATION_KEYS[entity_id] """Initialize the number entity."""
self._attr_native_min_value = min_val super().__init__(coordinator)
self._attr_native_max_value = max_val self.entry = entry
self._attr_native_step = step
self._attr_native_unit_of_measurement = UNITS[entity_id] self.logger = PrefixLoggerAdapter(_LOGGER, {"name": entry.title})
self._attr_icon = ICONS[entity_id]
self._default_value = default self._entity_suffix = entity_id
self._attr_native_value = default self._attr_unique_id = f"{entry.entry_id}_{entity_id}"
self._attr_device_info = DeviceInfo( self._attr_translation_key = TRANSLATION_KEYS[entity_id]
identifiers={(DOMAIN, entry.entry_id)}, self._attr_native_min_value = min_val
name=entry.title, self._attr_native_max_value = max_val
manufacturer="Motion Light", self._attr_native_step = step
model="Switch Control Integration", self._attr_native_unit_of_measurement = UNITS[entity_id]
) self._attr_icon = ICONS[entity_id]
self._default_value = default
@property self._attr_native_value = default
def native_value(self) -> float: self._attr_device_info = DeviceInfo(
"""Return the current value.""" identifiers={(DOMAIN, entry.entry_id)},
return self._attr_native_value name=entry.title,
manufacturer="Motion Light",
async def async_set_native_value(self, value: float) -> None: model="Switch Control Integration",
"""Update the current value.""" )
self._attr_native_value = value
self.logger.info("Setting %s to %s", self._entity_suffix, value) @property
def native_value(self) -> float:
if self._entity_suffix == ENTITY_MOTION_FILTER: """Return the current value."""
self.coordinator.set_motion_filter(value) return self._attr_native_value
elif self._entity_suffix == ENTITY_OFF_DELAY:
self.coordinator.set_off_delay(value) async def async_set_native_value(self, value: float) -> None:
elif self._entity_suffix == ENTITY_LUX_THRESHOLD: """Update the current value."""
self.coordinator.set_lux_threshold(value) self._attr_native_value = value
elif self._entity_suffix == ENTITY_LUX_COOLDOWN: self.logger.info("Setting %s to %s", self._entity_suffix, value)
self.coordinator.set_lux_cooldown(value)
elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: if self._entity_suffix == ENTITY_MOTION_FILTER:
self.coordinator.set_manual_idle_timeout(value) self.coordinator.set_motion_filter(value)
elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: elif self._entity_suffix == ENTITY_OFF_DELAY:
self.coordinator.set_manual_off_cooldown(value) self.coordinator.set_off_delay(value)
elif self._entity_suffix == ENTITY_LUX_THRESHOLD:
self.async_write_ha_state() self.coordinator.set_lux_threshold(value)
elif self._entity_suffix == ENTITY_LUX_COOLDOWN:
async def async_added_to_hass(self) -> None: self.coordinator.set_lux_cooldown(value)
"""Handle entity which will be added.""" elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT:
await super().async_added_to_hass() self.coordinator.set_manual_idle_timeout(value)
last_state = await self.async_get_last_state() elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN:
if last_state is not None: self.coordinator.set_manual_off_cooldown(value)
try:
value = float(last_state.state) self.async_write_ha_state()
self._attr_native_value = value
self.logger.info("Restored %s: %s", self._entity_suffix, value) async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
if self._entity_suffix == ENTITY_MOTION_FILTER: await super().async_added_to_hass()
self.coordinator.set_motion_filter(value) last_state = await self.async_get_last_state()
elif self._entity_suffix == ENTITY_OFF_DELAY: if last_state is not None:
self.coordinator.set_off_delay(value) try:
elif self._entity_suffix == ENTITY_LUX_THRESHOLD: value = float(last_state.state)
self.coordinator.set_lux_threshold(value) self._attr_native_value = value
elif self._entity_suffix == ENTITY_LUX_COOLDOWN: self.logger.info("Restored %s: %s", self._entity_suffix, value)
self.coordinator.set_lux_cooldown(value)
elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT: if self._entity_suffix == ENTITY_MOTION_FILTER:
self.coordinator.set_manual_idle_timeout(value) self.coordinator.set_motion_filter(value)
elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN: elif self._entity_suffix == ENTITY_OFF_DELAY:
self.coordinator.set_manual_off_cooldown(value) self.coordinator.set_off_delay(value)
except (ValueError, TypeError): elif self._entity_suffix == ENTITY_LUX_THRESHOLD:
self.logger.warning("Could not restore %s, using default", self._entity_suffix) self.coordinator.set_lux_threshold(value)
self.async_write_ha_state() elif self._entity_suffix == ENTITY_LUX_COOLDOWN:
self.coordinator.set_lux_cooldown(value)
def _handle_coordinator_update(self) -> None: elif self._entity_suffix == ENTITY_MANUAL_IDLE_TIMEOUT:
"""Handle updated data from the coordinator.""" self.coordinator.set_manual_idle_timeout(value)
elif self._entity_suffix == ENTITY_MANUAL_OFF_COOLDOWN:
self.coordinator.set_manual_off_cooldown(value)
except (ValueError, TypeError):
self.logger.warning("Could not restore %s, using default", self._entity_suffix)
self.async_write_ha_state()
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.async_write_ha_state() self.async_write_ha_state()
+1
View File
@@ -45,6 +45,7 @@ class MotionLightStatusSensor(CoordinatorEntity, SensorEntity):
"off": "mdi:lightbulb-off-outline", "off": "mdi:lightbulb-off-outline",
"manual_on": "mdi:hand-back-left", "manual_on": "mdi:hand-back-left",
"manual_off_cooldown": "mdi:timer-pause", "manual_off_cooldown": "mdi:timer-pause",
"force_stopped": "mdi:stop-circle-outline",
"error": "mdi:alert-circle", "error": "mdi:alert-circle",
"disabled": "mdi:lightbulb-off", "disabled": "mdi:lightbulb-off",
} }
@@ -1,84 +1,85 @@
{ {
"config": { "config": {
"step": { "step": {
"user": { "user": {
"title": "Motion Light", "title": "Motion Light",
"description": "Motion-based lighting control setup", "description": "Motion-based lighting control setup",
"data": { "data": {
"name": "Name", "name": "Name",
"main_sensors": "Main triggers", "main_sensors": "Main triggers",
"extend_sensors": "Extend sensors (optional)", "extend_sensors": "Extend sensors (optional)",
"switch_entities": "Controlled switches", "switch_entities": "Controlled switches",
"lux_sensor": "Illuminance sensor (optional)", "lux_sensor": "Illuminance sensor (optional)",
"force_stop_sensor": "Force stop sensor (optional)", "force_stop_sensor": "Force stop sensor (optional)",
"force_stop_state": "Force stop sensor state", "force_stop_state": "Force stop sensor state",
"log_level": "Log level" "log_level": "Log level"
}
}
} }
}
}
},
"options": {
"step": {
"init": {
"title": "Motion Light Options",
"description": "Change integration parameters",
"data": {
"main_sensors": "Main triggers",
"extend_sensors": "Extend sensors (optional)",
"switch_entities": "Controlled switches",
"lux_sensor": "Illuminance sensor (optional)",
"force_stop_sensor": "Force stop sensor (optional)",
"force_stop_state": "Force stop sensor state",
"log_level": "Log level"
}
}
}
},
"entity": {
"switch": {
"motion_light_enabled": {
"name": "Control"
},
"motion_light_priority_manual": {
"name": "Manual priority"
}
}, },
"sensor": { "options": {
"motion_light_status": { "step": {
"name": "Status", "init": {
"state": { "title": "Motion Light Options",
"idle": "Idle", "description": "Change integration parameters",
"detecting": "Detecting", "data": {
"on": "On", "main_sensors": "Main triggers",
"delaying": "Delaying", "extend_sensors": "Extend sensors (optional)",
"active": "Extended", "switch_entities": "Controlled switches",
"off": "Off", "lux_sensor": "Illuminance sensor (optional)",
"manual_on": "Manual on", "force_stop_sensor": "Force stop sensor (optional)",
"manual_off_cooldown": "Manual off cooldown", "force_stop_state": "Force stop sensor state",
"error": "Error", "log_level": "Log level"
"disabled": "Disabled" }
}
} }
}
}, },
"number": { "entity": {
"motion_light_motion_filter": { "switch": {
"name": "Motion filter" "motion_light_enabled": {
}, "name": "Control"
"motion_light_off_delay": { },
"name": "Off delay" "motion_light_priority_manual": {
}, "name": "Manual priority"
"motion_light_lux_threshold": { }
"name": "Lux threshold" },
}, "sensor": {
"motion_light_lux_cooldown": { "motion_light_status": {
"name": "Lux cooldown" "name": "Status",
}, "state": {
"motion_light_manual_idle_timeout": { "idle": "Idle",
"name": "'Forgot to turn off' timer" "detecting": "Detecting",
}, "on": "On",
"motion_light_manual_off_cooldown": { "delaying": "Delaying",
"name": "Manual off cooldown" "active": "Extended",
} "off": "Off",
"manual_on": "Manual on",
"manual_off_cooldown": "Manual off cooldown",
"force_stopped": "Force stopped",
"error": "Error",
"disabled": "Disabled"
}
}
},
"number": {
"motion_light_motion_filter": {
"name": "Motion filter"
},
"motion_light_off_delay": {
"name": "Off delay"
},
"motion_light_lux_threshold": {
"name": "Lux threshold"
},
"motion_light_lux_cooldown": {
"name": "Lux cooldown"
},
"motion_light_manual_idle_timeout": {
"name": "'Forgot to turn off' timer"
},
"motion_light_manual_off_cooldown": {
"name": "Manual off cooldown"
}
}
} }
}
} }
@@ -1,84 +1,85 @@
{ {
"config": { "config": {
"step": { "step": {
"user": { "user": {
"title": "Motion Light", "title": "Motion Light",
"description": "Настройка управления освещением по движению", "description": "Настройка управления освещением по движению",
"data": { "data": {
"name": "Название", "name": "Название",
"main_sensors": "Главные триггеры", "main_sensors": "Главные триггеры",
"extend_sensors": "Датчики продления (опционально)", "extend_sensors": "Датчики продления (опционально)",
"switch_entities": "Управляемые выключатели", "switch_entities": "Управляемые выключатели",
"lux_sensor": "Датчик освещённости (опционально)", "lux_sensor": "Датчик освещённости (опционально)",
"force_stop_sensor": "Датчик принудительной остановки (опционально)", "force_stop_sensor": "Датчик принудительной остановки (опционально)",
"force_stop_state": "Состояние датчика принудительной остановки", "force_stop_state": "Состояние датчика принудительной остановки",
"log_level": "Уровень логирования" "log_level": "Уровень логирования"
}
}
} }
}
}
},
"options": {
"step": {
"init": {
"title": "Настройки Motion Light",
"description": "Изменение параметров интеграции",
"data": {
"main_sensors": "Главные триггеры",
"extend_sensors": "Датчики продления (опционально)",
"switch_entities": "Управляемые выключатели",
"lux_sensor": "Датчик освещённости (опционально)",
"force_stop_sensor": "Датчик принудительной остановки (опционально)",
"force_stop_state": "Состояние датчика принудительной остановки",
"log_level": "Уровень логирования"
}
}
}
},
"entity": {
"switch": {
"motion_light_enabled": {
"name": "Управление"
},
"motion_light_priority_manual": {
"name": "Приоритет ручного"
}
}, },
"sensor": { "options": {
"motion_light_status": { "step": {
"name": "Статус", "init": {
"state": { "title": "Настройки Motion Light",
"idle": "Ожидание", "description": "Изменение параметров интеграции",
"detecting": "Обнаружение", "data": {
"on": "Включено", "main_sensors": "Главные триггеры",
"delaying": "Задержка выключения", "extend_sensors": "Датчики продления (опционально)",
"active": "Продлено", "switch_entities": "Управляемые выключатели",
"off": "Выключено", "lux_sensor": "Датчик освещённости (опционально)",
"manual_on": "Ручное включение", "force_stop_sensor": "Датчик принудительной остановки (опционально)",
"manual_off_cooldown": "Пауза после ручного выкл.", "force_stop_state": "Состояние датчика принудительной остановки",
"error": "Ошибка", "log_level": "Уровень логирования"
"disabled": "Отключено" }
}
} }
}
}, },
"number": { "entity": {
"motion_light_motion_filter": { "switch": {
"name": "Фильтр движения" "motion_light_enabled": {
}, "name": "Управление"
"motion_light_off_delay": { },
"name": "Задержка выключения" "motion_light_priority_manual": {
}, "name": "Приоритет ручного"
"motion_light_lux_threshold": { }
"name": "Порог освещённости" },
}, "sensor": {
"motion_light_lux_cooldown": { "motion_light_status": {
"name": "Cooldown люкса" "name": "Статус",
}, "state": {
"motion_light_manual_idle_timeout": { "idle": "Ожидание",
"name": "Таймер 'забыл выключить'" "detecting": "Обнаружение",
}, "on": "Включено",
"motion_light_manual_off_cooldown": { "delaying": "Задержка выключения",
"name": "Cooldown ручного выкл." "active": "Продлено",
} "off": "Выключено",
"manual_on": "Ручное включение",
"manual_off_cooldown": "Пауза после ручного выкл.",
"force_stopped": "Принудительная остановка",
"error": "Ошибка",
"disabled": "Отключено"
}
}
},
"number": {
"motion_light_motion_filter": {
"name": "Фильтр движения"
},
"motion_light_off_delay": {
"name": "Задержка выключения"
},
"motion_light_lux_threshold": {
"name": "Порог освещённости"
},
"motion_light_lux_cooldown": {
"name": "Cooldown люкса"
},
"motion_light_manual_idle_timeout": {
"name": "Таймер 'забыл выключить'"
},
"motion_light_manual_off_cooldown": {
"name": "Cooldown ручного выкл."
}
}
} }
}
} }
+32
View File
@@ -0,0 +1,32 @@
[tool.semantic_release]
tag_format = "v{version}"
version_variables = [
"custom_components/motion_light/manifest.json:version",
]
commit_message = "chore(release): v{version}"
# Отключаем создание релиза через API Gitea (релиз будет создаваться в GitHub)
upload_to_vcs_release = false
[tool.semantic_release.remote]
type = "gitea"
domain = "git.darkmode.duckdns.org"
api_domain = "git.darkmode.duckdns.org"
# Настройки для stable релизов (ветка main)
[tool.semantic_release.branches.main]
match = "(main|master)"
prerelease = false
# Настройки для beta релизов (ветка dev)
[tool.semantic_release.branches.dev]
match = "dev"
prerelease = true
prerelease_token = "beta"
[tool.semantic_release.commit_parser_options]
allowed_tags = ["feat", "fix", "docs", "style", "refactor", "perf", "test", "chore", "revert"]
minor_tags = ["feat"]
patch_tags = ["fix", "perf"]