rustuya-local 0.1.0.dev6__tar.gz → 0.1.0.dev8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/workflows/_test.yml +4 -4
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/CHANGELOG.md +23 -2
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/PKG-INFO +53 -20
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/README.md +50 -17
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/__init__.py +14 -8
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/manifest.json +3 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/panel.py +56 -50
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/strings.json +3 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/translations/en.json +3 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/translations/ko.json +3 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/www/rustuya-panel.js +2 -2
- rustuya_local-0.1.0.dev8/docs/ARCHITECTURE.md +78 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/docs/MIGRATING.md +16 -9
- rustuya_local-0.1.0.dev8/docs/REDESIGN.md +385 -0
- rustuya_local-0.1.0.dev8/docs/REVIEW.md +108 -0
- rustuya_local-0.1.0.dev8/docs/ROADMAP.md +38 -0
- rustuya_local-0.1.0.dev8/docs/STATUS.md +41 -0
- rustuya_local-0.1.0.dev8/docs/history/ROADMAP-2026-10-12.md +111 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/pyproject.toml +3 -3
- rustuya_local-0.1.0.dev8/release-notes.md +12 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/__init__.py +1 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/mqtt.py +49 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/pack.py +27 -5
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/runner.py +1 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/transport.py +10 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/manager_plugin/__init__.py +1 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/manager_plugin/static/index.js +2 -2
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_chain_in_process.py +1 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_chain_mqtt.py +1 -1
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_init.py +2 -2
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_panel.py +72 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_host.py +4 -2
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_hub.py +1 -1
- rustuya_local-0.1.0.dev8/tests/unit/test_mqtt_lifecycle.py +110 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_pack.py +50 -0
- rustuya_local-0.1.0.dev6/docs/REDESIGN.md +0 -327
- rustuya_local-0.1.0.dev6/docs/ROADMAP.md +0 -102
- rustuya_local-0.1.0.dev6/docs/STATUS.md +0 -6
- rustuya_local-0.1.0.dev6/release-notes.md +0 -3
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/dependabot.yml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/workflows/ci.yml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/workflows/hacs.yml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/workflows/hassfest.yml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.github/workflows/release.yml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/.gitignore +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/LICENSE +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/icon.png +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/icon.svg +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/icon@2x.png +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/logo.png +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/logo.svg +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/brand/logo@2x.png +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/bridge_supervisor.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/bridge_sync.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/broker.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/config_flow.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/const.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/manager_registry.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/manager_session.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/panel_bridge.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/services.yaml +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/hacs.json +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/scripts/build_dropin.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/scripts/release_notes.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/__main__.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/async_tools.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/bridge_client.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/cli.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/config.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/files.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/__init__.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/devices.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/hub.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/il.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/memory.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/overrides.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/host/rules.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/manager_plugin/api.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/service.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/src/rustuya_local/validation.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/conftest.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/devices.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_bridge_registration.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_daemon.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_full_stack.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_full_stack_custom_templates.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_full_stack_devices.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_manager_plugin.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_purge.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/e2e/test_service.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/conftest.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/fake_manager.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_bridge_probe.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_config_flow.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_manager_registry.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_manager_session.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/ha/test_migrate.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_config.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_cover_settings.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_dropin.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_files.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_hazardous.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_il_vectors.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_link_watcher.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_panel_js.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_plugin_api.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_release_notes.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/test_rule_files.py +0 -0
- {rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/tests/unit/tuya_samples.py +0 -0
|
@@ -3,7 +3,7 @@ name: Test (reusable)
|
|
|
3
3
|
# Single source of truth for rustuya-local's pre-merge / pre-publish checks. Called from ci.yml
|
|
4
4
|
# on push/PR and as a gate from release.yml, so a commit that fails these can't reach PyPI.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# ildevice-homeassistant is not on PyPI yet, so check out ildevice-homeassistant and explicitly
|
|
7
7
|
# install that checkout together with rustuya-local's test extras.
|
|
8
8
|
# Other package dependencies are installed from PyPI.
|
|
9
9
|
|
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
strategy:
|
|
21
21
|
fail-fast: false
|
|
22
22
|
matrix:
|
|
23
|
-
#
|
|
23
|
+
# ildevice-homeassistant (the test/fullstack sibling dep) requires Python>=3.14; rustuya-local's own
|
|
24
24
|
# requires-python floor (3.13, in pyproject.toml) stays lower since only the test extras
|
|
25
25
|
# need 3.14, not the base package.
|
|
26
26
|
python-version: ["3.14"]
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
with:
|
|
32
32
|
path: rustuya-local
|
|
33
33
|
|
|
34
|
-
#
|
|
34
|
+
# Install the integration checkout as the test consumer package.
|
|
35
35
|
- name: Check out ildevice-homeassistant
|
|
36
36
|
uses: actions/checkout@v7
|
|
37
37
|
with:
|
|
@@ -91,7 +91,7 @@ jobs:
|
|
|
91
91
|
run: pytest -q
|
|
92
92
|
|
|
93
93
|
package:
|
|
94
|
-
# The test job uses an
|
|
94
|
+
# The test job uses an ildevice-homeassistant checkout; this one checks what a user gets: the wheel in a fresh
|
|
95
95
|
# environment, every dependency from PyPI, importing and starting the CLI.
|
|
96
96
|
name: installs from PyPI alone
|
|
97
97
|
runs-on: ubuntu-latest
|
|
@@ -3,11 +3,32 @@
|
|
|
3
3
|
All notable changes to this project are documented in this file. A release's GitHub Release notes are its section
|
|
4
4
|
here (the release workflow reads it, and refuses to publish a version without one).
|
|
5
5
|
|
|
6
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
7
|
-
|
|
6
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The current development series uses
|
|
7
|
+
`0.1.0.devN`; the earlier `0.0.x` releases remain below as history.
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.1.0.dev8] — 2026-10-13
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Always stop the embedded bridge during Home Assistant unload, including service-stop failure or cancellation.
|
|
15
|
+
- Close MQTT callback tasks, ignore queued dispatch after shutdown, retrieve asynchronous handler errors and stop
|
|
16
|
+
the paho thread outside the event loop, including when a connection attempt fails.
|
|
17
|
+
- Preserve concurrent partial Home Assistant settings/options saves by locking reads, merges and restart together.
|
|
18
|
+
- Reject unsafe pack ownership paths, malformed hashes and bounds; preserve host settings and symlink targets.
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
- Separate current status, architecture and outstanding work from the historical redesign record.
|
|
22
|
+
- Document the 2026-10-13 code review, verification results and remaining reliability issues.
|
|
23
|
+
- Correct daemon setup, test prerequisites and saved cover settings precedence.
|
|
24
|
+
- Translate all Korean Markdown documents into English, including archived design and roadmap history.
|
|
25
|
+
|
|
26
|
+
## [0.1.0.dev7] — 2026-10-12
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Use ildevice-homeassistant consistently in documentation, UI text and code comments.
|
|
30
|
+
- Require tuya-interpreter 0.1.0a2 in the package and the Home Assistant integration.
|
|
31
|
+
|
|
11
32
|
## [0.1.0.dev6] — 2026-10-12
|
|
12
33
|
|
|
13
34
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rustuya-local
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev8
|
|
4
4
|
Summary: Local Tuya control: rustuya-bridge -> tuya-interpreter -> IL (ildevice). HA-independent core.
|
|
5
5
|
Project-URL: Homepage, https://github.com/3735943886/rustuya-local
|
|
6
6
|
Project-URL: Repository, https://github.com/3735943886/rustuya-local
|
|
@@ -10,14 +10,14 @@ License-File: LICENSE
|
|
|
10
10
|
Requires-Python: >=3.13
|
|
11
11
|
Requires-Dist: paho-mqtt>=2.0
|
|
12
12
|
Requires-Dist: pyrustuyabridge>=0.4
|
|
13
|
-
Requires-Dist: tuya-interpreter>=0.1.
|
|
13
|
+
Requires-Dist: tuya-interpreter>=0.1.0a2
|
|
14
14
|
Provides-Extra: fullstack
|
|
15
15
|
Requires-Dist: tuyamock; extra == 'fullstack'
|
|
16
16
|
Provides-Extra: manager
|
|
17
17
|
Requires-Dist: rustuya-manager[web]>=0.2.2; extra == 'manager'
|
|
18
18
|
Provides-Extra: test
|
|
19
19
|
Requires-Dist: httpx2; extra == 'test'
|
|
20
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: ildevice-homeassistant; extra == 'test'
|
|
21
21
|
Requires-Dist: pytest; extra == 'test'
|
|
22
22
|
Requires-Dist: pytest-asyncio; extra == 'test'
|
|
23
23
|
Provides-Extra: test-ha
|
|
@@ -32,7 +32,7 @@ Local control of Tuya devices through [rustuya-bridge](https://github.com/373594
|
|
|
32
32
|
([ildevice](https://github.com/3735943886/ildevice)) devices, and in Home Assistant. It replaces rustuya-homeassistant.
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──►
|
|
35
|
+
rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► ildevice-homeassistant (Home Assistant integration), or any IL consumer
|
|
36
36
|
raw LAN dps Service:
|
|
37
37
|
BridgeClient + rustuya_local.host.Hub
|
|
38
38
|
```
|
|
@@ -42,15 +42,33 @@ rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► il
|
|
|
42
42
|
- **rustuya-local** is one `Service` (`src/rustuya_local/service.py`) and three thin shells that run it:
|
|
43
43
|
the `rustuya-local run` daemon, the `custom_components/rustuya` Home Assistant integration, and a rustuya-manager
|
|
44
44
|
plugin. Its `host` package owns the runner, transports and file watchers. It only produces IL and knows nothing of Home Assistant entities.
|
|
45
|
-
- **[
|
|
45
|
+
- **[ildevice-homeassistant](https://github.com/3735943886/ildevice-homeassistant)** turns IL descriptors into Home Assistant entities.
|
|
46
46
|
|
|
47
47
|
## Daemon
|
|
48
48
|
|
|
49
|
+
```bash
|
|
50
|
+
uv venv --python 3.13
|
|
51
|
+
uv pip install -e .
|
|
52
|
+
.venv/bin/rustuya-local run --config config.json
|
|
49
53
|
```
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
|
|
55
|
+
Create `config.json` beside the device file before running:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"bridge": {"host": "localhost", "port": 1883, "root": "rustuya"},
|
|
60
|
+
"il": {"host": "localhost", "port": 1883, "prefix": "il", "source": "tuya"},
|
|
61
|
+
"devices": "tuyadevices.json",
|
|
62
|
+
"custom_converters": "custom_converters",
|
|
63
|
+
"pack": true,
|
|
64
|
+
"watch_interval": 5,
|
|
65
|
+
"options": {"allow_hazardous": false, "expose_unused": false}
|
|
66
|
+
}
|
|
52
67
|
```
|
|
53
68
|
|
|
69
|
+
The package requires Python 3.13 or newer. Device and converter paths are relative to the configuration file.
|
|
70
|
+
`watch_interval: 0` disables polling; use a positive interval to apply file edits and downloaded rules while running.
|
|
71
|
+
|
|
54
72
|
`config.json` (see `src/rustuya_local/config.py`): the bridge and IL brokers, the IL prefix, the device list, a
|
|
55
73
|
`custom_converters` directory, and options for the Hub (`allow_hazardous`, `expose_unused`, `overrides`).
|
|
56
74
|
The device list is the `tuyadevices.json` that [rustuya-manager](https://github.com/3735943886/rustuya-manager) keeps (its QR-login wizard writes
|
|
@@ -75,7 +93,7 @@ MQTT and in-process transport tests exercise the same adapter and the bidirectio
|
|
|
75
93
|
|
|
76
94
|
Coming from rustuya-homeassistant: [docs/MIGRATING.md](docs/MIGRATING.md).
|
|
77
95
|
|
|
78
|
-
Install
|
|
96
|
+
Install ildevice-homeassistant, and run one IL producer per bridge: the daemon, the integration below, or the manager plugin. A second
|
|
79
97
|
one with the same IL prefix and source refuses to start while the first runs (every device would show twice): the
|
|
80
98
|
daemon exits, the integration is retried by Home Assistant, the plugin's tab shows it and retries. To move from one to
|
|
81
99
|
another, stop the old one first.
|
|
@@ -87,7 +105,7 @@ bridge mode, the broker and the IL prefix (it checks an external bridge answers
|
|
|
87
105
|
is not on the default one), then offers to fetch the devices from Tuya Cloud (a saved login is reused; a QR login
|
|
88
106
|
only when there is none), which closing its window skips (it is in *Configure* later, and in the panel); the rest
|
|
89
107
|
starts at its defaults and is changed in the panel's *Settings*. Deleting the integration takes its devices out of IL
|
|
90
|
-
(so
|
|
108
|
+
(so ildevice-homeassistant removes them); with the embedded bridge it also clears that bridge's retained topics and its state file.
|
|
91
109
|
The device list and Tuya login it kept under `.storage/rustuya/` are deleted; a device file you pointed elsewhere, and
|
|
92
110
|
everything on an external bridge, are left as they are.
|
|
93
111
|
|
|
@@ -134,7 +152,9 @@ device settings are enabled, unless an override selects another source. A settin
|
|
|
134
152
|
the setting is saved as the device's block in `zz_settings.json` in the converters directory and applied at once, so it
|
|
135
153
|
survives a restart and can be edited there too. Without a converters directory (a daemon with no
|
|
136
154
|
`custom_converters`) the switches are not offered; `"options": {"device_settings": false}` turns them off.
|
|
137
|
-
A block with the same `cover` keys in your own file or the builtin rules sets a model's starting point
|
|
155
|
+
A block with the same `cover` keys in your own file or the builtin rules sets a model's starting point.
|
|
156
|
+
User files, including `zz_settings.json`, are applied in filename order; a later file can override saved settings.
|
|
157
|
+
The daemon's inline `options.overrides` is applied after file layers.
|
|
138
158
|
Older `cover.infer_motion` and `converters.cover_motion` blocks are upgraded on loading to `cover.state_source`
|
|
139
159
|
(`inferred`, or `none` for `infer_motion: false`) and `cover.settle`; see [migration notes](docs/MIGRATING.md).
|
|
140
160
|
|
|
@@ -172,17 +192,24 @@ rustuya-local release (bump the patch version and tag; the manager's catalog fol
|
|
|
172
192
|
|
|
173
193
|
## Tests
|
|
174
194
|
|
|
175
|
-
```
|
|
176
|
-
|
|
195
|
+
```bash
|
|
196
|
+
uv venv --python 3.14
|
|
197
|
+
uv pip install -e ../ildevice-homeassistant -e ".[test,test-ha,fullstack,manager]"
|
|
198
|
+
.venv/bin/python -m pytest -q
|
|
199
|
+
ruff check src tests custom_components scripts
|
|
177
200
|
```
|
|
178
201
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
202
|
+
The complete suite uses Python 3.14 because the consumer and Home Assistant test dependencies require it.
|
|
203
|
+
Install `mosquitto` for broker tests and Node.js for panel JavaScript tests. The command above installs the consumer
|
|
204
|
+
from a sibling checkout and other dependencies from the package index; CI follows the same approach.
|
|
205
|
+
Missing base or test dependencies can fail collection; optional broker, emulator and fixture tests skip when their
|
|
206
|
+
prerequisites are unavailable. The full-stack device comparison uses `../tuya-interpreter/tests/golden`; IL wire and
|
|
207
|
+
topic vectors use `../ildevice/vectors` (or the `ILDEVICE` environment variable). Keep those checkouts for these checks.
|
|
182
208
|
|
|
183
|
-
- `tests/unit/`:
|
|
209
|
+
- `tests/unit/`: configuration validation, host lifecycle, IL vectors, rule loading, settings persistence,
|
|
210
|
+
pack ownership, converter APIs, panel JavaScript and release packaging.
|
|
184
211
|
- `tests/e2e/test_service.py`: the service on in-memory brokers (overrides directory, a failed start).
|
|
185
|
-
- `tests/e2e/test_chain_*.py`, `test_daemon.py`: the runner with
|
|
212
|
+
- `tests/e2e/test_chain_*.py`, `test_daemon.py`: the runner with ildevice-homeassistant's consumer model on the other side, in one process
|
|
186
213
|
and over a real broker; the daemon as a subprocess.
|
|
187
214
|
- `tests/e2e/test_manager_plugin.py`: the plugin under rustuya-manager's real plugin host (skipped without it).
|
|
188
215
|
- `tests/ha/`: the integration (config flow, setup).
|
|
@@ -190,8 +217,14 @@ Everything here needs the sibling repos and, for the last two groups, `mosquitto
|
|
|
190
217
|
(`tuyamock`) around the daemon: state, commands, rejections, a device dropping off and returning, a daemon restart, and a
|
|
191
218
|
differential check that 38 real device shapes come out of the whole chain exactly as `tuya-interpreter` computes them.
|
|
192
219
|
|
|
193
|
-
##
|
|
220
|
+
## Documentation
|
|
194
221
|
|
|
195
|
-
|
|
222
|
+
- [Current status](docs/STATUS.md): version, document index and verification boundaries.
|
|
223
|
+
- [Architecture](docs/ARCHITECTURE.md): current components, lifecycle and rule precedence.
|
|
224
|
+
- [Migration](docs/MIGRATING.md): upgrading old converters and rustuya-homeassistant installations.
|
|
225
|
+
- [Code review](docs/REVIEW.md): findings and local verification from 2026-10-13.
|
|
226
|
+
- [Roadmap](docs/ROADMAP.md): outstanding work and real-device checks.
|
|
227
|
+
- [Design history](docs/REDESIGN.md): the September redesign record; historical APIs and versions.
|
|
228
|
+
- [Changelog](CHANGELOG.md): changes grouped by release.
|
|
196
229
|
|
|
197
|
-
Licence:
|
|
230
|
+
Licence: [MIT](LICENSE).
|
|
@@ -6,7 +6,7 @@ Local control of Tuya devices through [rustuya-bridge](https://github.com/373594
|
|
|
6
6
|
([ildevice](https://github.com/3735943886/ildevice)) devices, and in Home Assistant. It replaces rustuya-homeassistant.
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──►
|
|
9
|
+
rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► ildevice-homeassistant (Home Assistant integration), or any IL consumer
|
|
10
10
|
raw LAN dps Service:
|
|
11
11
|
BridgeClient + rustuya_local.host.Hub
|
|
12
12
|
```
|
|
@@ -16,15 +16,33 @@ rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► il
|
|
|
16
16
|
- **rustuya-local** is one `Service` (`src/rustuya_local/service.py`) and three thin shells that run it:
|
|
17
17
|
the `rustuya-local run` daemon, the `custom_components/rustuya` Home Assistant integration, and a rustuya-manager
|
|
18
18
|
plugin. Its `host` package owns the runner, transports and file watchers. It only produces IL and knows nothing of Home Assistant entities.
|
|
19
|
-
- **[
|
|
19
|
+
- **[ildevice-homeassistant](https://github.com/3735943886/ildevice-homeassistant)** turns IL descriptors into Home Assistant entities.
|
|
20
20
|
|
|
21
21
|
## Daemon
|
|
22
22
|
|
|
23
|
+
```bash
|
|
24
|
+
uv venv --python 3.13
|
|
25
|
+
uv pip install -e .
|
|
26
|
+
.venv/bin/rustuya-local run --config config.json
|
|
23
27
|
```
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
Create `config.json` beside the device file before running:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"bridge": {"host": "localhost", "port": 1883, "root": "rustuya"},
|
|
34
|
+
"il": {"host": "localhost", "port": 1883, "prefix": "il", "source": "tuya"},
|
|
35
|
+
"devices": "tuyadevices.json",
|
|
36
|
+
"custom_converters": "custom_converters",
|
|
37
|
+
"pack": true,
|
|
38
|
+
"watch_interval": 5,
|
|
39
|
+
"options": {"allow_hazardous": false, "expose_unused": false}
|
|
40
|
+
}
|
|
26
41
|
```
|
|
27
42
|
|
|
43
|
+
The package requires Python 3.13 or newer. Device and converter paths are relative to the configuration file.
|
|
44
|
+
`watch_interval: 0` disables polling; use a positive interval to apply file edits and downloaded rules while running.
|
|
45
|
+
|
|
28
46
|
`config.json` (see `src/rustuya_local/config.py`): the bridge and IL brokers, the IL prefix, the device list, a
|
|
29
47
|
`custom_converters` directory, and options for the Hub (`allow_hazardous`, `expose_unused`, `overrides`).
|
|
30
48
|
The device list is the `tuyadevices.json` that [rustuya-manager](https://github.com/3735943886/rustuya-manager) keeps (its QR-login wizard writes
|
|
@@ -49,7 +67,7 @@ MQTT and in-process transport tests exercise the same adapter and the bidirectio
|
|
|
49
67
|
|
|
50
68
|
Coming from rustuya-homeassistant: [docs/MIGRATING.md](docs/MIGRATING.md).
|
|
51
69
|
|
|
52
|
-
Install
|
|
70
|
+
Install ildevice-homeassistant, and run one IL producer per bridge: the daemon, the integration below, or the manager plugin. A second
|
|
53
71
|
one with the same IL prefix and source refuses to start while the first runs (every device would show twice): the
|
|
54
72
|
daemon exits, the integration is retried by Home Assistant, the plugin's tab shows it and retries. To move from one to
|
|
55
73
|
another, stop the old one first.
|
|
@@ -61,7 +79,7 @@ bridge mode, the broker and the IL prefix (it checks an external bridge answers
|
|
|
61
79
|
is not on the default one), then offers to fetch the devices from Tuya Cloud (a saved login is reused; a QR login
|
|
62
80
|
only when there is none), which closing its window skips (it is in *Configure* later, and in the panel); the rest
|
|
63
81
|
starts at its defaults and is changed in the panel's *Settings*. Deleting the integration takes its devices out of IL
|
|
64
|
-
(so
|
|
82
|
+
(so ildevice-homeassistant removes them); with the embedded bridge it also clears that bridge's retained topics and its state file.
|
|
65
83
|
The device list and Tuya login it kept under `.storage/rustuya/` are deleted; a device file you pointed elsewhere, and
|
|
66
84
|
everything on an external bridge, are left as they are.
|
|
67
85
|
|
|
@@ -108,7 +126,9 @@ device settings are enabled, unless an override selects another source. A settin
|
|
|
108
126
|
the setting is saved as the device's block in `zz_settings.json` in the converters directory and applied at once, so it
|
|
109
127
|
survives a restart and can be edited there too. Without a converters directory (a daemon with no
|
|
110
128
|
`custom_converters`) the switches are not offered; `"options": {"device_settings": false}` turns them off.
|
|
111
|
-
A block with the same `cover` keys in your own file or the builtin rules sets a model's starting point
|
|
129
|
+
A block with the same `cover` keys in your own file or the builtin rules sets a model's starting point.
|
|
130
|
+
User files, including `zz_settings.json`, are applied in filename order; a later file can override saved settings.
|
|
131
|
+
The daemon's inline `options.overrides` is applied after file layers.
|
|
112
132
|
Older `cover.infer_motion` and `converters.cover_motion` blocks are upgraded on loading to `cover.state_source`
|
|
113
133
|
(`inferred`, or `none` for `infer_motion: false`) and `cover.settle`; see [migration notes](docs/MIGRATING.md).
|
|
114
134
|
|
|
@@ -146,17 +166,24 @@ rustuya-local release (bump the patch version and tag; the manager's catalog fol
|
|
|
146
166
|
|
|
147
167
|
## Tests
|
|
148
168
|
|
|
149
|
-
```
|
|
150
|
-
|
|
169
|
+
```bash
|
|
170
|
+
uv venv --python 3.14
|
|
171
|
+
uv pip install -e ../ildevice-homeassistant -e ".[test,test-ha,fullstack,manager]"
|
|
172
|
+
.venv/bin/python -m pytest -q
|
|
173
|
+
ruff check src tests custom_components scripts
|
|
151
174
|
```
|
|
152
175
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
176
|
+
The complete suite uses Python 3.14 because the consumer and Home Assistant test dependencies require it.
|
|
177
|
+
Install `mosquitto` for broker tests and Node.js for panel JavaScript tests. The command above installs the consumer
|
|
178
|
+
from a sibling checkout and other dependencies from the package index; CI follows the same approach.
|
|
179
|
+
Missing base or test dependencies can fail collection; optional broker, emulator and fixture tests skip when their
|
|
180
|
+
prerequisites are unavailable. The full-stack device comparison uses `../tuya-interpreter/tests/golden`; IL wire and
|
|
181
|
+
topic vectors use `../ildevice/vectors` (or the `ILDEVICE` environment variable). Keep those checkouts for these checks.
|
|
156
182
|
|
|
157
|
-
- `tests/unit/`:
|
|
183
|
+
- `tests/unit/`: configuration validation, host lifecycle, IL vectors, rule loading, settings persistence,
|
|
184
|
+
pack ownership, converter APIs, panel JavaScript and release packaging.
|
|
158
185
|
- `tests/e2e/test_service.py`: the service on in-memory brokers (overrides directory, a failed start).
|
|
159
|
-
- `tests/e2e/test_chain_*.py`, `test_daemon.py`: the runner with
|
|
186
|
+
- `tests/e2e/test_chain_*.py`, `test_daemon.py`: the runner with ildevice-homeassistant's consumer model on the other side, in one process
|
|
160
187
|
and over a real broker; the daemon as a subprocess.
|
|
161
188
|
- `tests/e2e/test_manager_plugin.py`: the plugin under rustuya-manager's real plugin host (skipped without it).
|
|
162
189
|
- `tests/ha/`: the integration (config flow, setup).
|
|
@@ -164,8 +191,14 @@ Everything here needs the sibling repos and, for the last two groups, `mosquitto
|
|
|
164
191
|
(`tuyamock`) around the daemon: state, commands, rejections, a device dropping off and returning, a daemon restart, and a
|
|
165
192
|
differential check that 38 real device shapes come out of the whole chain exactly as `tuya-interpreter` computes them.
|
|
166
193
|
|
|
167
|
-
##
|
|
194
|
+
## Documentation
|
|
168
195
|
|
|
169
|
-
|
|
196
|
+
- [Current status](docs/STATUS.md): version, document index and verification boundaries.
|
|
197
|
+
- [Architecture](docs/ARCHITECTURE.md): current components, lifecycle and rule precedence.
|
|
198
|
+
- [Migration](docs/MIGRATING.md): upgrading old converters and rustuya-homeassistant installations.
|
|
199
|
+
- [Code review](docs/REVIEW.md): findings and local verification from 2026-10-13.
|
|
200
|
+
- [Roadmap](docs/ROADMAP.md): outstanding work and real-device checks.
|
|
201
|
+
- [Design history](docs/REDESIGN.md): the September redesign record; historical APIs and versions.
|
|
202
|
+
- [Changelog](CHANGELOG.md): changes grouped by release.
|
|
170
203
|
|
|
171
|
-
Licence:
|
|
204
|
+
Licence: [MIT](LICENSE).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Rustuya as a Home Assistant integration: runs the same `rustuya_local.service.Service` the standalone
|
|
2
2
|
`rustuya-local` daemon does, tied to this config entry, with an optional embedded rustuya-bridge (`pyrustuyabridge`)
|
|
3
|
-
and user overrides from `<config>/rustuya_converters`. It creates no entities and does not depend on
|
|
4
|
-
*producer* — install
|
|
3
|
+
and user overrides from `<config>/rustuya_converters`. It creates no entities and does not depend on ildevice-homeassistant: it is only an IL
|
|
4
|
+
*producer* — install ildevice-homeassistant (or any IL consumer) separately to turn its devices into entities.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -105,7 +105,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
|
|
105
105
|
|
|
106
106
|
def _tuya_id(hass: HomeAssistant, service: Any, device_id: str) -> str:
|
|
107
107
|
"""`device_id` as the Tuya device id: as given when the service drives it, else a Home Assistant device's. An IL
|
|
108
|
-
consumer registers a device with the IL id (the Tuya id) among its identifiers —
|
|
108
|
+
consumer registers a device with the IL id (the Tuya id) among its identifiers — ildevice-homeassistant as `("ildevice", id)`, under
|
|
109
109
|
whatever platform name it runs as — so any identifier value the service drives is taken, when there is exactly one.
|
|
110
110
|
Unresolved, it is returned as given (and refused as an unknown device)."""
|
|
111
111
|
from homeassistant.helpers import device_registry as dr
|
|
@@ -252,19 +252,25 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
252
252
|
# setup that follows puts it back anyway; only disabling the entry (or deleting it, below) removes it
|
|
253
253
|
if entry.disabled_by is not None:
|
|
254
254
|
panel.async_remove(hass)
|
|
255
|
-
runtime: RuntimeData = hass.data[DOMAIN]
|
|
255
|
+
runtime: RuntimeData = hass.data[DOMAIN][entry.entry_id]
|
|
256
256
|
restarting = getattr(runtime, "restarting", False) and entry.disabled_by is None
|
|
257
|
-
|
|
257
|
+
try:
|
|
258
|
+
await runtime.service.stop(offline=not restarting)
|
|
259
|
+
finally:
|
|
260
|
+
try:
|
|
261
|
+
if runtime.embedded_bridge:
|
|
262
|
+
await runtime.embedded_bridge.stop()
|
|
263
|
+
finally:
|
|
264
|
+
# A stopped service cannot be reused, even when draining its commands failed.
|
|
265
|
+
hass.data[DOMAIN].pop(entry.entry_id, None)
|
|
258
266
|
if restarting:
|
|
259
267
|
hass.data[RESUME] = True
|
|
260
|
-
if runtime.embedded_bridge:
|
|
261
|
-
await runtime.embedded_bridge.stop()
|
|
262
268
|
return True
|
|
263
269
|
|
|
264
270
|
|
|
265
271
|
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
266
272
|
"""Deleted, not just unloaded (which only says `offline`, keeping everything for the next start). Clears what this
|
|
267
|
-
entry leaves behind: its IL devices (retained descriptors and values, so
|
|
273
|
+
entry leaves behind: its IL devices (retained descriptors and values, so ildevice-homeassistant and other IL consumers drop them),
|
|
268
274
|
with the embedded bridge also that bridge's retained topics and its state file, and the files it kept in its own
|
|
269
275
|
storage directory (the device list and the Tuya login). A path the user pointed elsewhere is left alone, as is
|
|
270
276
|
everything on an external bridge. A broker that cannot be reached only leaves the retained topics behind; the
|
{rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/manifest.json
RENAMED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"iot_class": "local_push",
|
|
10
10
|
"issue_tracker": "https://github.com/3735943886/rustuya-local/issues",
|
|
11
11
|
"requirements": [
|
|
12
|
-
"tuya-interpreter>=0.1.
|
|
12
|
+
"tuya-interpreter>=0.1.0a2",
|
|
13
13
|
"paho-mqtt>=2.0",
|
|
14
14
|
"rustuya-manager>=0.2.2",
|
|
15
15
|
"pyrustuyabridge>=0.4",
|
|
16
|
-
"rustuya-local>=0.1.0.
|
|
16
|
+
"rustuya-local>=0.1.0.dev8"
|
|
17
17
|
],
|
|
18
18
|
"single_config_entry": true,
|
|
19
|
-
"version": "0.1.0.
|
|
19
|
+
"version": "0.1.0.dev8"
|
|
20
20
|
}
|
|
@@ -221,11 +221,12 @@ class OptionsView(_View):
|
|
|
221
221
|
or not all(isinstance(v, bool) for v in body.values())):
|
|
222
222
|
return self.json_message(f"the options are {sorted(DEFAULT_OPTIONS)}, each true or false",
|
|
223
223
|
HTTPStatus.BAD_REQUEST)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
async with _settings_lock(request.app["hass"]):
|
|
225
|
+
options = {**entry.options, **body}
|
|
226
|
+
if options == dict(entry.options):
|
|
227
|
+
return self.json({"restarting": False})
|
|
228
|
+
await _restart(request.app["hass"], entry, options=options) # answered once it has restarted
|
|
229
|
+
return self.json({"restarting": True})
|
|
229
230
|
|
|
230
231
|
|
|
231
232
|
def settings_of(data: Any) -> dict[str, Any]:
|
|
@@ -275,24 +276,30 @@ class SettingsView(_View):
|
|
|
275
276
|
@_entry_view
|
|
276
277
|
async def put(self, request: web.Request, entry: Any, runtime: Any) -> web.Response:
|
|
277
278
|
hass = request.app["hass"]
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
279
|
+
body = await _json_body(request)
|
|
280
|
+
async with _settings_lock(hass):
|
|
281
|
+
try:
|
|
282
|
+
new = validate_settings(hass, entry.data, body)
|
|
283
|
+
except ValueError as e:
|
|
284
|
+
return self.json_message(str(e), HTTPStatus.BAD_REQUEST)
|
|
285
|
+
old = dict(entry.data)
|
|
286
|
+
if new == settings_of(old):
|
|
287
|
+
return self.json({"restarting": False})
|
|
288
|
+
external = old.get(CONF_BRIDGE_MODE) != BRIDGE_EMBEDDED
|
|
289
|
+
if external and new[CONF_BRIDGE_ROOT] != old[CONF_BRIDGE_ROOT]:
|
|
290
|
+
from .config_flow import _probe_bridge
|
|
291
|
+
|
|
292
|
+
error = await _probe_bridge(hass, {**old, **new})
|
|
293
|
+
if error is not None:
|
|
294
|
+
return self.json_message(
|
|
295
|
+
f"no rustuya-bridge answers on {new[CONF_BRIDGE_ROOT]}" if error == "bridge_not_found"
|
|
296
|
+
else "cannot connect to the broker", HTTPStatus.BAD_REQUEST)
|
|
297
|
+
await _apply(hass, entry, old, {**old, **new}) # answered once it has restarted
|
|
298
|
+
return self.json({"restarting": True})
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _settings_lock(hass: HomeAssistant) -> asyncio.Lock:
|
|
302
|
+
return hass.data.setdefault(f"{DOMAIN}_settings_lock", asyncio.Lock())
|
|
296
303
|
|
|
297
304
|
|
|
298
305
|
async def _apply(hass: HomeAssistant, entry: Any, old: dict[str, Any], data: dict[str, Any]) -> None:
|
|
@@ -318,34 +325,33 @@ async def _restart(hass: HomeAssistant, entry: Any, *, data: dict[str, Any] | No
|
|
|
318
325
|
"""Stop the entry, run `between`, store the new data / options, start it again — all awaited, so the panel's save
|
|
319
326
|
answers once the restart is over (the page then reloads onto the restarted entry, not onto one about to go).
|
|
320
327
|
Stopped, the entry has no update listener, so storing does not start a second reload. An options-only restart
|
|
321
|
-
keeps the IL presence online across it (`mark_restart`); new data may move the broker or the IL topics.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
keeps the IL presence online across it (`mark_restart`); new data may move the broker or the IL topics.
|
|
329
|
+
The request handler holds `_settings_lock` from reading the current entry through this restart."""
|
|
330
|
+
old_data, old_options = dict(entry.data), dict(entry.options)
|
|
331
|
+
if data is None:
|
|
332
|
+
from . import mark_restart
|
|
333
|
+
|
|
334
|
+
mark_restart(hass, entry)
|
|
335
|
+
if not await hass.config_entries.async_unload(entry.entry_id):
|
|
336
|
+
raise RestartFailed("Could not stop the integration; settings were not changed")
|
|
337
|
+
try:
|
|
338
|
+
if between is not None:
|
|
339
|
+
await between()
|
|
340
|
+
changes = {k: v for k, v in (("data", data), ("options", options)) if v is not None}
|
|
341
|
+
hass.config_entries.async_update_entry(entry, **changes)
|
|
342
|
+
if not await hass.config_entries.async_setup(entry.entry_id):
|
|
343
|
+
raise RestartFailed("The integration could not start with these settings")
|
|
344
|
+
except Exception as e:
|
|
345
|
+
# Failed setup leaves SETUP_ERROR/SETUP_RETRY, which cannot be set up
|
|
346
|
+
# directly. Unload also cancels any scheduled retry with the bad data.
|
|
347
|
+
await hass.config_entries.async_unload(entry.entry_id)
|
|
348
|
+
hass.config_entries.async_update_entry(entry, data=old_data, options=old_options)
|
|
331
349
|
try:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
raise RestartFailed("The integration could not start with these settings")
|
|
338
|
-
except Exception as e:
|
|
339
|
-
# Failed setup leaves SETUP_ERROR/SETUP_RETRY, which cannot be set up
|
|
340
|
-
# directly. Unload also cancels any scheduled retry with the bad data.
|
|
341
|
-
await hass.config_entries.async_unload(entry.entry_id)
|
|
342
|
-
hass.config_entries.async_update_entry(entry, data=old_data, options=old_options)
|
|
343
|
-
try:
|
|
344
|
-
restored = await hass.config_entries.async_setup(entry.entry_id)
|
|
345
|
-
except Exception: # noqa: BLE001 -- keep the recovery API usable even if rollback startup fails
|
|
346
|
-
restored = False
|
|
347
|
-
suffix = "Previous settings restored" if restored else "Previous settings restored, but startup still fails"
|
|
348
|
-
raise RestartFailed(f"{e}. {suffix}; settings remain editable") from e
|
|
350
|
+
restored = await hass.config_entries.async_setup(entry.entry_id)
|
|
351
|
+
except Exception: # noqa: BLE001 -- keep the recovery API usable even if rollback startup fails
|
|
352
|
+
restored = False
|
|
353
|
+
suffix = "Previous settings restored" if restored else "Previous settings restored, but startup still fails"
|
|
354
|
+
raise RestartFailed(f"{e}. {suffix}; settings remain editable") from e
|
|
349
355
|
|
|
350
356
|
|
|
351
357
|
class PackView(_View):
|
{rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/strings.json
RENAMED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"il_prefix": "IL topic prefix"
|
|
20
20
|
},
|
|
21
21
|
"data_description": {
|
|
22
|
-
"il_prefix": "Where the devices are published for
|
|
22
|
+
"il_prefix": "Where the devices are published for ildevice-homeassistant (or any IL consumer); ildevice-homeassistant reads il by default."
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"embedded": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"il_prefix": "IL topic prefix"
|
|
34
34
|
},
|
|
35
35
|
"data_description": {
|
|
36
|
-
"il_prefix": "Where the devices are published for
|
|
36
|
+
"il_prefix": "Where the devices are published for ildevice-homeassistant (or any IL consumer); ildevice-homeassistant reads il by default."
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"onboard": {
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"fields": {
|
|
153
153
|
"device_id": {
|
|
154
154
|
"name": "Device ID",
|
|
155
|
-
"description": "The Tuya device ID (as in the device list), or the Home Assistant device an IL consumer such as
|
|
155
|
+
"description": "The Tuya device ID (as in the device list), or the Home Assistant device an IL consumer such as ildevice-homeassistant made of it."
|
|
156
156
|
},
|
|
157
157
|
"dp": {
|
|
158
158
|
"name": "DP",
|
{rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/translations/en.json
RENAMED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"il_prefix": "IL topic prefix"
|
|
20
20
|
},
|
|
21
21
|
"data_description": {
|
|
22
|
-
"il_prefix": "Where the devices are published for
|
|
22
|
+
"il_prefix": "Where the devices are published for ildevice-homeassistant (or any IL consumer); ildevice-homeassistant reads il by default."
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"embedded": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"il_prefix": "IL topic prefix"
|
|
34
34
|
},
|
|
35
35
|
"data_description": {
|
|
36
|
-
"il_prefix": "Where the devices are published for
|
|
36
|
+
"il_prefix": "Where the devices are published for ildevice-homeassistant (or any IL consumer); ildevice-homeassistant reads il by default."
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"onboard": {
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"fields": {
|
|
153
153
|
"device_id": {
|
|
154
154
|
"name": "Device ID",
|
|
155
|
-
"description": "The Tuya device ID (as in the device list), or the Home Assistant device an IL consumer such as
|
|
155
|
+
"description": "The Tuya device ID (as in the device list), or the Home Assistant device an IL consumer such as ildevice-homeassistant made of it."
|
|
156
156
|
},
|
|
157
157
|
"dp": {
|
|
158
158
|
"name": "DP",
|
{rustuya_local-0.1.0.dev6 → rustuya_local-0.1.0.dev8}/custom_components/rustuya/translations/ko.json
RENAMED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"il_prefix": "IL 토픽 prefix"
|
|
20
20
|
},
|
|
21
21
|
"data_description": {
|
|
22
|
-
"il_prefix": "
|
|
22
|
+
"il_prefix": "ildevice-homeassistant(또는 다른 IL 소비자)가 읽을 기기를 게시하는 곳입니다. ildevice-homeassistant는 기본으로 il을 읽습니다."
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"embedded": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"il_prefix": "IL 토픽 prefix"
|
|
34
34
|
},
|
|
35
35
|
"data_description": {
|
|
36
|
-
"il_prefix": "
|
|
36
|
+
"il_prefix": "ildevice-homeassistant(또는 다른 IL 소비자)가 읽을 기기를 게시하는 곳입니다. ildevice-homeassistant는 기본으로 il을 읽습니다."
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"onboard": {
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"fields": {
|
|
153
153
|
"device_id": {
|
|
154
154
|
"name": "기기 ID",
|
|
155
|
-
"description": "Tuya 기기 ID (기기 목록의 것), 또는
|
|
155
|
+
"description": "Tuya 기기 ID (기기 목록의 것), 또는 ildevice-homeassistant 같은 IL consumer가 만든 Home Assistant 기기 ID."
|
|
156
156
|
},
|
|
157
157
|
"dp": {
|
|
158
158
|
"name": "DP",
|