rustuya-local 0.1.0.dev5__tar.gz → 0.1.0.dev6__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.dev5 → rustuya_local-0.1.0.dev6}/.github/workflows/_test.yml +6 -6
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.github/workflows/release.yml +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/CHANGELOG.md +6 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/PKG-INFO +13 -13
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/README.md +10 -10
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/bridge_supervisor.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/const.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/manifest.json +3 -3
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/www/rustuya-panel.js +4 -4
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/docs/MIGRATING.md +4 -4
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/docs/ROADMAP.md +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/pyproject.toml +3 -3
- rustuya_local-0.1.0.dev6/release-notes.md +3 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/scripts/build_dropin.py +5 -5
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/__init__.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/bridge_client.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/config.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/__init__.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/devices.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/hub.py +9 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/il.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/overrides.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/pack.py +4 -4
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/rules.py +3 -3
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/runner.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/manager_plugin/__init__.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/manager_plugin/static/index.js +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/service.py +4 -4
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/devices.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_full_stack_devices.py +3 -3
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_manager_plugin.py +6 -6
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_init.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_cover_settings.py +1 -1
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_dropin.py +9 -9
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_host.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_hub.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_il_vectors.py +2 -2
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_pack.py +4 -4
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_rule_files.py +7 -7
- rustuya_local-0.1.0.dev5/release-notes.md +0 -8
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.github/dependabot.yml +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.github/workflows/ci.yml +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.github/workflows/hacs.yml +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.github/workflows/hassfest.yml +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/.gitignore +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/LICENSE +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/__init__.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon.png +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon.svg +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon@2x.png +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo.png +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo.svg +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo@2x.png +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/bridge_sync.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/broker.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/config_flow.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/manager_registry.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/manager_session.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/panel.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/panel_bridge.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/services.yaml +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/strings.json +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/translations/en.json +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/translations/ko.json +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/docs/REDESIGN.md +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/docs/STATUS.md +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/hacs.json +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/scripts/release_notes.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/__main__.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/async_tools.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/cli.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/files.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/memory.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/mqtt.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/host/transport.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/manager_plugin/api.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/validation.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/conftest.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_bridge_registration.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_chain_in_process.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_chain_mqtt.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_daemon.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_full_stack.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_full_stack_custom_templates.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_purge.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_service.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/conftest.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/fake_manager.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_bridge_probe.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_config_flow.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_manager_registry.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_manager_session.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_migrate.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/ha/test_panel.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_config.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_files.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_hazardous.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_link_watcher.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_panel_js.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_plugin_api.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/test_release_notes.py +0 -0
- {rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/unit/tuya_samples.py +0 -0
|
@@ -38,14 +38,14 @@ jobs:
|
|
|
38
38
|
repository: 3735943886/ildevice-homeassistant
|
|
39
39
|
path: ildevice-homeassistant
|
|
40
40
|
|
|
41
|
-
#
|
|
41
|
+
# tuya-interpreter is on PyPI (installed below via uv), so this isn't for dependency
|
|
42
42
|
# resolution -- it's so tests/e2e/test_full_stack_devices.py can reach its sibling
|
|
43
43
|
# checkout's tests/golden fixture data, which isn't shipped in the published package.
|
|
44
|
-
- name: Check out
|
|
44
|
+
- name: Check out tuya-interpreter
|
|
45
45
|
uses: actions/checkout@v7
|
|
46
46
|
with:
|
|
47
|
-
repository: 3735943886/
|
|
48
|
-
path:
|
|
47
|
+
repository: 3735943886/tuya-interpreter
|
|
48
|
+
path: tuya-interpreter
|
|
49
49
|
|
|
50
50
|
- name: Set up Python ${{ matrix.python-version }}
|
|
51
51
|
uses: actions/setup-python@v7
|
|
@@ -114,7 +114,7 @@ jobs:
|
|
|
114
114
|
"$RUNNER_TEMP/smoke/bin/python" -c "
|
|
115
115
|
import rustuya_local.cli, rustuya_local.manager_plugin, rustuya_local.service
|
|
116
116
|
from rustuya_local import register
|
|
117
|
-
import
|
|
118
|
-
|
|
117
|
+
import tuya_interpreter
|
|
118
|
+
tuya_interpreter.preload()
|
|
119
119
|
print('ok')"
|
|
120
120
|
"$RUNNER_TEMP/smoke/bin/rustuya-local" --help
|
|
@@ -96,7 +96,7 @@ jobs:
|
|
|
96
96
|
run: twine check --strict dist/* # --strict: a missing README (long description) fails the release
|
|
97
97
|
|
|
98
98
|
- name: Build the rustuya-manager drop-in zip
|
|
99
|
-
# The manager's plugin catalog installs this asset (it cannot pip-install): rustuya_local with the
|
|
99
|
+
# The manager's plugin catalog installs this asset (it cannot pip-install): rustuya_local with the tuya-interpreter
|
|
100
100
|
# the wheel resolves to vendored inside. Kept out of dist/ so it is not uploaded to PyPI.
|
|
101
101
|
run: |
|
|
102
102
|
python -m pip install dist/*.whl
|
|
@@ -8,6 +8,12 @@ stable release.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.1.0.dev6] — 2026-10-12
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Require tuya-interpreter 0.1.0a1 (tuya2ildevice renamed) in place of tuya2ildevice, in the package, the Home Assistant integration and the manager drop-in archive.
|
|
15
|
+
- Fetch builtin rule updates from tuya-interpreter's `master` branch.
|
|
16
|
+
|
|
11
17
|
## [0.1.0.dev5] — 2026-10-11
|
|
12
18
|
|
|
13
19
|
### Fixed
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rustuya-local
|
|
3
|
-
Version: 0.1.0.
|
|
4
|
-
Summary: Local Tuya control: rustuya-bridge ->
|
|
3
|
+
Version: 0.1.0.dev6
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/3735943886/rustuya-local/issues
|
|
@@ -10,7 +10,7 @@ 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:
|
|
13
|
+
Requires-Dist: tuya-interpreter>=0.1.0a1
|
|
14
14
|
Provides-Extra: fullstack
|
|
15
15
|
Requires-Dist: tuyamock; extra == 'fullstack'
|
|
16
16
|
Provides-Extra: manager
|
|
@@ -37,7 +37,7 @@ rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► il
|
|
|
37
37
|
BridgeClient + rustuya_local.host.Hub
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
- **[
|
|
40
|
+
- **[tuya-interpreter](https://github.com/3735943886/tuya-interpreter)** is the only place that interprets Tuya: Home Assistant core's `tuya` behaviour and
|
|
41
41
|
user overrides for non-standard devices, through its Sans-I/O Rust engine and Python wrapper.
|
|
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
|
|
@@ -62,7 +62,7 @@ Stopping the daemon only marks it `offline`; its devices stay in IL (retained) f
|
|
|
62
62
|
and run `rustuya-local purge --config config.json`: the retained descriptors, values and presence of its IL prefix and
|
|
63
63
|
source are cleared (other sources on the prefix are left alone), so IL consumers drop the devices.
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## tuya-interpreter host boundary
|
|
66
66
|
|
|
67
67
|
The shared Rust core interprets one device and has no MQTT or rule-file loading API.
|
|
68
68
|
`rustuya_local.host.Hub` owns the device registry and maps `TuyaDriver` outputs to IL MQTT wire data;
|
|
@@ -96,16 +96,16 @@ everything on an external bridge, are left as they are.
|
|
|
96
96
|
Tuya's cloud schemas are fragmented; fixes go in a `custom_converters/` directory (daemon: `custom_converters` in the
|
|
97
97
|
config; integration: `<config>/rustuya_converters`; manager plugin: its data dir), followed live:
|
|
98
98
|
|
|
99
|
-
- `*.json`:
|
|
99
|
+
- `*.json`: tuya-interpreter override blocks by product or device id: rename a dp, define one the schema lacks, map the
|
|
100
100
|
device's words to the standard ones (`remap.alias`), fix labels and classes, define a declarative converter. A cover's
|
|
101
101
|
direction is not set here but with its settings (below). Each file is a rule object with fixes under `overrides`, for example
|
|
102
102
|
`{"overrides": {"product_id": {"cover": {"invert_position": true}}}}`.
|
|
103
103
|
- Converters are JSON programs under `converters.declarative`; Python code plugins are not loaded.
|
|
104
104
|
|
|
105
|
-
See
|
|
106
|
-
ship with
|
|
105
|
+
See tuya-interpreter's [device customization guide](https://github.com/3735943886/tuya-interpreter/blob/master/docs/device-customization.md#user-overrides) for the format. Official builtin rules, including `00-default.json`,
|
|
106
|
+
ship with tuya-interpreter and update when that package is upgraded. They are applied before your own rule files.
|
|
107
107
|
|
|
108
|
-
Online updates fetch the latest [builtin rules](https://github.com/3735943886/
|
|
108
|
+
Online updates fetch the latest [builtin rules](https://github.com/3735943886/tuya-interpreter/tree/master/rules/builtin)
|
|
109
109
|
into `rules/builtin/` inside the overrides directory at start and daily. In the Rustuya panel, use *Update builtin rules*
|
|
110
110
|
to fetch them now or *Automatically update builtin rules* to control automatic updates. Opening the panel checks for
|
|
111
111
|
updates: with automatic updates on, available changes are installed immediately without a yellow dot. With automatic
|
|
@@ -158,7 +158,7 @@ language: Korean, or English.
|
|
|
158
158
|
## rustuya-manager plugin
|
|
159
159
|
|
|
160
160
|
Installed next to rustuya-manager, as a pip package (`pip install rustuya-local` in the manager's environment) or as
|
|
161
|
-
the drop-in zip each release carries (`rustuya_local-<version>-dropin.zip`,
|
|
161
|
+
the drop-in zip each release carries (`rustuya_local-<version>-dropin.zip`, tuya-interpreter binding, native library and builtin rules vendored inside, built by
|
|
162
162
|
`scripts/build_dropin.py`; unpack it into the manager's plugin directory, or install it from the manager's plugin
|
|
163
163
|
catalog once listed there), it appears as a **Tuya (IL)** tab and runs the service under the manager: the manager's broker and bridge root, the manager's device
|
|
164
164
|
list (followed). Its data dir (`rustuya-local/` next to the manager's plugins) holds `settings.json` (`il`,
|
|
@@ -166,7 +166,7 @@ list (followed). Its data dir (`rustuya-local/` next to the manager's plugins) h
|
|
|
166
166
|
converter files are picked up while it runs. A settings file it cannot use is shown on the tab until it is fixed.
|
|
167
167
|
|
|
168
168
|
The drop-in zip includes native code for the platform on which it was built; use a matching OS and architecture,
|
|
169
|
-
or install with pip to select the appropriate wheel. It freezes the
|
|
169
|
+
or install with pip to select the appropriate wheel. It freezes the tuya-interpreter it was built with: a tuya-interpreter fix reaches manager users with the next
|
|
170
170
|
rustuya-local release (bump the patch version and tag; the manager's catalog follows the release). Installed both ways
|
|
171
171
|
(pip and the plugin directory), the plugin runs once.
|
|
172
172
|
|
|
@@ -178,7 +178,7 @@ rustuya-local release (bump the patch version and tag; the manager's catalog fol
|
|
|
178
178
|
|
|
179
179
|
Everything here needs the sibling repos and, for the last two groups, `mosquitto`, `pyrustuya-bridge` and `tuyamock`
|
|
180
180
|
(`pip install -e ".[test,fullstack]"`); those are skipped when missing. Parity with Home Assistant core's fixtures lives in
|
|
181
|
-
`
|
|
181
|
+
`tuya-interpreter/tests/chain`, the wire and topic vectors in `ildevice/vectors`.
|
|
182
182
|
|
|
183
183
|
- `tests/unit/`: the configuration.
|
|
184
184
|
- `tests/e2e/test_service.py`: the service on in-memory brokers (overrides directory, a failed start).
|
|
@@ -188,7 +188,7 @@ Everything here needs the sibling repos and, for the last two groups, `mosquitto
|
|
|
188
188
|
- `tests/ha/`: the integration (config flow, setup).
|
|
189
189
|
- `tests/e2e/test_full_stack*.py`: the real rustuya-bridge (`pyrustuyabridge`, in process) and Tuya device emulators
|
|
190
190
|
(`tuyamock`) around the daemon: state, commands, rejections, a device dropping off and returning, a daemon restart, and a
|
|
191
|
-
differential check that 38 real device shapes come out of the whole chain exactly as `
|
|
191
|
+
differential check that 38 real device shapes come out of the whole chain exactly as `tuya-interpreter` computes them.
|
|
192
192
|
|
|
193
193
|
## Status
|
|
194
194
|
|
|
@@ -11,7 +11,7 @@ rustuya-bridge ──MQTT──► rustuya-local ──MQTT (il/…)──► il
|
|
|
11
11
|
BridgeClient + rustuya_local.host.Hub
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
- **[
|
|
14
|
+
- **[tuya-interpreter](https://github.com/3735943886/tuya-interpreter)** is the only place that interprets Tuya: Home Assistant core's `tuya` behaviour and
|
|
15
15
|
user overrides for non-standard devices, through its Sans-I/O Rust engine and Python wrapper.
|
|
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
|
|
@@ -36,7 +36,7 @@ Stopping the daemon only marks it `offline`; its devices stay in IL (retained) f
|
|
|
36
36
|
and run `rustuya-local purge --config config.json`: the retained descriptors, values and presence of its IL prefix and
|
|
37
37
|
source are cleared (other sources on the prefix are left alone), so IL consumers drop the devices.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## tuya-interpreter host boundary
|
|
40
40
|
|
|
41
41
|
The shared Rust core interprets one device and has no MQTT or rule-file loading API.
|
|
42
42
|
`rustuya_local.host.Hub` owns the device registry and maps `TuyaDriver` outputs to IL MQTT wire data;
|
|
@@ -70,16 +70,16 @@ everything on an external bridge, are left as they are.
|
|
|
70
70
|
Tuya's cloud schemas are fragmented; fixes go in a `custom_converters/` directory (daemon: `custom_converters` in the
|
|
71
71
|
config; integration: `<config>/rustuya_converters`; manager plugin: its data dir), followed live:
|
|
72
72
|
|
|
73
|
-
- `*.json`:
|
|
73
|
+
- `*.json`: tuya-interpreter override blocks by product or device id: rename a dp, define one the schema lacks, map the
|
|
74
74
|
device's words to the standard ones (`remap.alias`), fix labels and classes, define a declarative converter. A cover's
|
|
75
75
|
direction is not set here but with its settings (below). Each file is a rule object with fixes under `overrides`, for example
|
|
76
76
|
`{"overrides": {"product_id": {"cover": {"invert_position": true}}}}`.
|
|
77
77
|
- Converters are JSON programs under `converters.declarative`; Python code plugins are not loaded.
|
|
78
78
|
|
|
79
|
-
See
|
|
80
|
-
ship with
|
|
79
|
+
See tuya-interpreter's [device customization guide](https://github.com/3735943886/tuya-interpreter/blob/master/docs/device-customization.md#user-overrides) for the format. Official builtin rules, including `00-default.json`,
|
|
80
|
+
ship with tuya-interpreter and update when that package is upgraded. They are applied before your own rule files.
|
|
81
81
|
|
|
82
|
-
Online updates fetch the latest [builtin rules](https://github.com/3735943886/
|
|
82
|
+
Online updates fetch the latest [builtin rules](https://github.com/3735943886/tuya-interpreter/tree/master/rules/builtin)
|
|
83
83
|
into `rules/builtin/` inside the overrides directory at start and daily. In the Rustuya panel, use *Update builtin rules*
|
|
84
84
|
to fetch them now or *Automatically update builtin rules* to control automatic updates. Opening the panel checks for
|
|
85
85
|
updates: with automatic updates on, available changes are installed immediately without a yellow dot. With automatic
|
|
@@ -132,7 +132,7 @@ language: Korean, or English.
|
|
|
132
132
|
## rustuya-manager plugin
|
|
133
133
|
|
|
134
134
|
Installed next to rustuya-manager, as a pip package (`pip install rustuya-local` in the manager's environment) or as
|
|
135
|
-
the drop-in zip each release carries (`rustuya_local-<version>-dropin.zip`,
|
|
135
|
+
the drop-in zip each release carries (`rustuya_local-<version>-dropin.zip`, tuya-interpreter binding, native library and builtin rules vendored inside, built by
|
|
136
136
|
`scripts/build_dropin.py`; unpack it into the manager's plugin directory, or install it from the manager's plugin
|
|
137
137
|
catalog once listed there), it appears as a **Tuya (IL)** tab and runs the service under the manager: the manager's broker and bridge root, the manager's device
|
|
138
138
|
list (followed). Its data dir (`rustuya-local/` next to the manager's plugins) holds `settings.json` (`il`,
|
|
@@ -140,7 +140,7 @@ list (followed). Its data dir (`rustuya-local/` next to the manager's plugins) h
|
|
|
140
140
|
converter files are picked up while it runs. A settings file it cannot use is shown on the tab until it is fixed.
|
|
141
141
|
|
|
142
142
|
The drop-in zip includes native code for the platform on which it was built; use a matching OS and architecture,
|
|
143
|
-
or install with pip to select the appropriate wheel. It freezes the
|
|
143
|
+
or install with pip to select the appropriate wheel. It freezes the tuya-interpreter it was built with: a tuya-interpreter fix reaches manager users with the next
|
|
144
144
|
rustuya-local release (bump the patch version and tag; the manager's catalog follows the release). Installed both ways
|
|
145
145
|
(pip and the plugin directory), the plugin runs once.
|
|
146
146
|
|
|
@@ -152,7 +152,7 @@ rustuya-local release (bump the patch version and tag; the manager's catalog fol
|
|
|
152
152
|
|
|
153
153
|
Everything here needs the sibling repos and, for the last two groups, `mosquitto`, `pyrustuya-bridge` and `tuyamock`
|
|
154
154
|
(`pip install -e ".[test,fullstack]"`); those are skipped when missing. Parity with Home Assistant core's fixtures lives in
|
|
155
|
-
`
|
|
155
|
+
`tuya-interpreter/tests/chain`, the wire and topic vectors in `ildevice/vectors`.
|
|
156
156
|
|
|
157
157
|
- `tests/unit/`: the configuration.
|
|
158
158
|
- `tests/e2e/test_service.py`: the service on in-memory brokers (overrides directory, a failed start).
|
|
@@ -162,7 +162,7 @@ Everything here needs the sibling repos and, for the last two groups, `mosquitto
|
|
|
162
162
|
- `tests/ha/`: the integration (config flow, setup).
|
|
163
163
|
- `tests/e2e/test_full_stack*.py`: the real rustuya-bridge (`pyrustuyabridge`, in process) and Tuya device emulators
|
|
164
164
|
(`tuyamock`) around the daemon: state, commands, rejections, a device dropping off and returning, a daemon restart, and a
|
|
165
|
-
differential check that 38 real device shapes come out of the whole chain exactly as `
|
|
165
|
+
differential check that 38 real device shapes come out of the whole chain exactly as `tuya-interpreter` computes them.
|
|
166
166
|
|
|
167
167
|
## Status
|
|
168
168
|
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/bridge_supervisor.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""An embedded rustuya-bridge: `pyrustuyabridge.PyBridgeServer` run in a background thread, tied to this config
|
|
2
2
|
entry's lifecycle instead of a separate rustuya-bridge process. Same class our own end-to-end tests drive
|
|
3
|
-
(
|
|
3
|
+
(tuya-interpreter/rustuya-local tests/e2e/test_full_stack.py) — proven against a real broker and real Tuya-protocol
|
|
4
4
|
devices, just supervised here instead of by a test fixture.
|
|
5
5
|
|
|
6
6
|
The embedded bridge still needs a real MQTT broker to talk through (it manages its own native MQTT client); it does
|
|
@@ -28,7 +28,7 @@ CONF_DEVICES_PATH = "devices_path" # tuyadevices.json; the cloud wi
|
|
|
28
28
|
# --- entry options (changed any time through the options flow) -------------------------
|
|
29
29
|
CONF_ALLOW_HAZARDOUS = "allow_hazardous"
|
|
30
30
|
CONF_EXPOSE_UNUSED = "expose_unused"
|
|
31
|
-
CONF_PACK = "pack" # sync
|
|
31
|
+
CONF_PACK = "pack" # sync tuya-interpreter's override pack into CONVERTERS_DIR
|
|
32
32
|
CONF_PANEL = "panel" # the sidebar page (panel.py); Configure adds / removes it
|
|
33
33
|
|
|
34
34
|
DEFAULT_OPTIONS = {CONF_ALLOW_HAZARDOUS: False, CONF_EXPOSE_UNUSED: False, CONF_PACK: True}
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/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
|
-
"
|
|
12
|
+
"tuya-interpreter>=0.1.0a1",
|
|
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.dev6"
|
|
17
17
|
],
|
|
18
18
|
"single_config_entry": true,
|
|
19
|
-
"version": "0.1.0.
|
|
19
|
+
"version": "0.1.0.dev6"
|
|
20
20
|
}
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/www/rustuya-panel.js
RENAMED
|
@@ -195,9 +195,9 @@ const I18N = {
|
|
|
195
195
|
settings_unreadable: "Cannot read the settings: {error}", confirm_settings: "Save {names} and restart Rustuya?",
|
|
196
196
|
sync_now: "Update builtin rules", hide_title: "Remove this panel from the sidebar; the integration's Configure adds it back",
|
|
197
197
|
hide: "Hide panel", pack_title: "Builtin rules",
|
|
198
|
-
pack_intro: "Official rules included with
|
|
198
|
+
pack_intro: "Official rules included with tuya-interpreter. Fetch the latest rules without updating the integration.",
|
|
199
199
|
converters_title: "Custom converters",
|
|
200
|
-
converters_intro: "Rule files (*.json: override blocks and declarative converters) in rustuya_converters/. Saved files apply within seconds. See
|
|
200
|
+
converters_intro: "Rule files (*.json: override blocks and declarative converters) in rustuya_converters/. Saved files apply within seconds. See tuya-interpreter docs/device-customization.md, \"User overrides\". Drop files here to copy them in.",
|
|
201
201
|
new_file: "New file", save: "Save", delete: "Delete",
|
|
202
202
|
only_converters: "{name} (only *.json files are converters)",
|
|
203
203
|
confirm_replace_pack: "{name} comes from the override pack. Replaced, it is yours: the pack no longer updates or removes it.",
|
|
@@ -266,9 +266,9 @@ const I18N = {
|
|
|
266
266
|
settings_unreadable: "설정을 읽을 수 없습니다: {error}", confirm_settings: "{names}을(를) 저장하고 Rustuya를 재시작할까요?",
|
|
267
267
|
sync_now: "Builtin 규칙 업데이트", hide_title: "사이드바에서 이 패널을 없앱니다. 통합구성요소의 구성(Configure)에서 다시 추가할 수 있습니다",
|
|
268
268
|
hide: "패널 숨기기", pack_title: "Builtin 규칙",
|
|
269
|
-
pack_intro: "
|
|
269
|
+
pack_intro: "tuya-interpreter에 포함된 공식 규칙입니다. 통합구성요소 업데이트 없이 최신 규칙을 가져옵니다.",
|
|
270
270
|
converters_title: "커스텀 컨버터",
|
|
271
|
-
converters_intro: "rustuya_converters/ 안의 규칙 파일(*.json: 오버라이드 블록과 선언형 컨버터). 저장한 파일은 몇 초 안에 적용됩니다.
|
|
271
|
+
converters_intro: "rustuya_converters/ 안의 규칙 파일(*.json: 오버라이드 블록과 선언형 컨버터). 저장한 파일은 몇 초 안에 적용됩니다. tuya-interpreter docs/device-customization.md의 \"User overrides\"를 참고하세요. 여기에 파일을 끌어다 놓으면 복사됩니다.",
|
|
272
272
|
new_file: "새 파일", save: "저장", delete: "삭제",
|
|
273
273
|
only_converters: "{name} (*.json 파일만 컨버터입니다)",
|
|
274
274
|
confirm_replace_pack: "{name}은(는) 오버라이드 팩의 파일입니다. 바꾸면 내 파일이 되어 팩이 더 이상 갱신하거나 지우지 않습니다.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Moving to
|
|
1
|
+
# Moving to tuya-interpreter
|
|
2
2
|
|
|
3
3
|
The device file, MQTT configuration and IL topics stay the same. Custom files in the converters directory now use
|
|
4
4
|
the rule-layer format. Wrap an existing product/device override mapping in `{"overrides": ...}`. For example:
|
|
@@ -30,7 +30,7 @@ rustuya-manager plugin. It is retired. Its replacement is two pieces:
|
|
|
30
30
|
`ildevice`, and Home Assistant keeps the two apart. Automations, scripts and dashboards that name the old entities
|
|
31
31
|
need the new ids. If you clear the old discovery first (step 1), the old ids are free and you can rename the new
|
|
32
32
|
entities to them in Home Assistant.
|
|
33
|
-
- **Entities follow Home Assistant core's `tuya` integration** (
|
|
33
|
+
- **Entities follow Home Assistant core's `tuya` integration** (tuya-interpreter reproduces it), so a device can come out
|
|
34
34
|
with more, fewer or differently split entities than v1 made.
|
|
35
35
|
- **Availability** comes from rustuya-local's presence: when it stops, its devices show unavailable.
|
|
36
36
|
|
|
@@ -61,7 +61,7 @@ rustuya-manager plugin. It is retired. Its replacement is two pieces:
|
|
|
61
61
|
| daemon | `custom_converters` in its `config.json` |
|
|
62
62
|
|
|
63
63
|
- `*.json` files are rule layers, with product/device fixes under `overrides`
|
|
64
|
-
([User overrides](https://github.com/3735943886/
|
|
64
|
+
([User overrides](https://github.com/3735943886/tuya-interpreter/blob/master/docs/device-customization.md#user-overrides)); the old ones are not read, so rewrite them.
|
|
65
65
|
A property the tables do not build is defined from its dp in `props`.
|
|
66
66
|
- A curtain's `invert_position` / `invert_set_position`, reversed command words and derived state are not files any
|
|
67
67
|
more: each cover has configuration switches for them (README, *Cover settings*).
|
|
@@ -69,7 +69,7 @@ rustuya-manager plugin. It is retired. Its replacement is two pieces:
|
|
|
69
69
|
- `converters.cover_motion` and a cover's `infer_motion` are read as the cover's `state_source` (`inferred`, or
|
|
70
70
|
`none` for `infer_motion: false`), and a `cover_motion` `settle` as the cover's `settle`; its other keys are
|
|
71
71
|
dropped with a warning. `zz_settings.json` is rewritten in the new form the next time a setting is saved.
|
|
72
|
-
- Leave the old pack (`00_default.json`, `00_curtain.py`, `.rustuya_pack.json`) out. Official rules ship with
|
|
72
|
+
- Leave the old pack (`00_default.json`, `00_curtain.py`, `.rustuya_pack.json`) out. Official rules ship with tuya-interpreter; online updates arrive under
|
|
73
73
|
`rules/builtin/` automatically. User files are applied after all official rules.
|
|
74
74
|
|
|
75
75
|
Changes to the directory are picked up while running, no restart needed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 남은 일 (2026-10-
|
|
1
|
+
# 남은 일 (2026-10-12 갱신)
|
|
2
2
|
|
|
3
|
-
현재 작업 트리는 `rustuya-local 0.1.0.
|
|
3
|
+
현재 작업 트리는 `rustuya-local 0.1.0.dev6`이며 `tuya-interpreter>=0.1.0a1`, `pyrustuyabridge>=0.4`를 요구한다.
|
|
4
4
|
HA 통합과 매니저 플러그인 테스트는 `rustuya-manager>=0.2.2`를 사용한다. 변환은 Sans-I/O Rust 드라이버가 맡고,
|
|
5
5
|
기기 레지스트리·IL MQTT 어댑터·전송·타이머·규칙 파일 로딩은 `rustuya_local.host`로 옮겼다.
|
|
6
6
|
버전별 변경은 [CHANGELOG.md](../CHANGELOG.md)에 기록한다. 이 문서는 실제 배포 서버의 최신 상태를 확인하는 자료가 아니다.
|
|
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rustuya-local"
|
|
7
|
-
version = "0.1.0.
|
|
8
|
-
description = "Local Tuya control: rustuya-bridge ->
|
|
7
|
+
version = "0.1.0.dev6"
|
|
8
|
+
description = "Local Tuya control: rustuya-bridge -> tuya-interpreter -> IL (ildevice). HA-independent core."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
11
11
|
license = "MIT"
|
|
12
|
-
dependencies = ["
|
|
12
|
+
dependencies = ["tuya-interpreter>=0.1.0a1", "pyrustuyabridge>=0.4", "paho-mqtt>=2.0"]
|
|
13
13
|
# rustuya-manager (custom_components/rustuya's config flow) is optional at the package level (the HA manifest
|
|
14
14
|
# pulls it in); declared here too so the venv used for this repo's tests has it. pyrustuyabridge is a base dependency
|
|
15
15
|
# now (not just for the embedded bridge): rustuya-local's own bridge_client uses it to interpret rustuya-bridge's
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
The manager downloads the zip, checks its SHA-256 against its catalog, unpacks it into its plugin directory and calls
|
|
5
5
|
the top-level package's `register(ctx)`. A dropped-in plugin cannot pip-install, so the zip has one top-level entry,
|
|
6
|
-
`rustuya_local/`, with
|
|
6
|
+
`rustuya_local/`, with tuya-interpreter (Python binding, native library and rule pack) vendored under `rustuya_local/_vendor/`. paho-mqtt and
|
|
7
7
|
pyrustuyabridge are not vendored: the manager depends on both.
|
|
8
8
|
|
|
9
9
|
The bytes depend only on the file contents (sorted entries, fixed timestamps and modes, no compression), so the same
|
|
@@ -11,7 +11,7 @@ sources give the same SHA-256.
|
|
|
11
11
|
|
|
12
12
|
python scripts/build_dropin.py [--outdir dist]
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
tuya-interpreter is taken from the environment the script runs in (install rustuya-local first).
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
@@ -28,7 +28,7 @@ from pathlib import Path
|
|
|
28
28
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
29
|
PKG_DIR = REPO_ROOT / "src" / "rustuya_local"
|
|
30
30
|
FIXED_DATE_TIME = (1980, 1, 1, 0, 0, 0)
|
|
31
|
-
VENDORED = ("
|
|
31
|
+
VENDORED = ("tuya_interpreter",)
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def _files(root: Path) -> list[Path]:
|
|
@@ -46,10 +46,10 @@ def _members() -> list[tuple[str, Path]]:
|
|
|
46
46
|
sys.exit(f"error: {name} is not installed; install rustuya-local first")
|
|
47
47
|
root = Path(next(iter(spec.submodule_search_locations)))
|
|
48
48
|
out += [(f"rustuya_local/_vendor/{name}/{p.relative_to(root).as_posix()}", p) for p in _files(root)]
|
|
49
|
-
if name == "
|
|
49
|
+
if name == "tuya_interpreter":
|
|
50
50
|
# Editable checkouts keep build outputs and rule artifacts outside the Python package.
|
|
51
51
|
# Installed wheels already contain them; include equivalent assets when building locally.
|
|
52
|
-
from
|
|
52
|
+
from tuya_interpreter.native import library
|
|
53
53
|
|
|
54
54
|
from rustuya_local.host.rules import rule_files, rule_paths
|
|
55
55
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
# The rustuya-manager drop-in zip (scripts/build_dropin.py) carries
|
|
7
|
-
# pip-install. Appended, not prepended: an installed
|
|
6
|
+
# The rustuya-manager drop-in zip (scripts/build_dropin.py) carries tuya-interpreter here, since a dropped-in plugin cannot
|
|
7
|
+
# pip-install. Appended, not prepended: an installed tuya-interpreter wins.
|
|
8
8
|
_VENDOR = Path(__file__).parent / "_vendor"
|
|
9
9
|
if _VENDOR.is_dir() and str(_VENDOR) not in sys.path:
|
|
10
10
|
sys.path.append(str(_VENDOR))
|
|
@@ -30,7 +30,7 @@ from dataclasses import dataclass
|
|
|
30
30
|
from typing import Any
|
|
31
31
|
|
|
32
32
|
import pyrustuyabridge as pb
|
|
33
|
-
from
|
|
33
|
+
from tuya_interpreter import Connected, Disconnected, Message
|
|
34
34
|
|
|
35
35
|
from rustuya_local.host import BridgeCommand, Runner
|
|
36
36
|
from rustuya_local.host.transport import Message as TransportMessage
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
(overrides and declarative converters, see `rustuya_local.host.load_overrides`), followed the same
|
|
16
16
|
way; `options.overrides` is merged over it. A device's settings written through IL (a cover's direction switches) are
|
|
17
17
|
kept in that directory too; `options.device_settings: false` stops offering them. `pack` (on by default) copies
|
|
18
|
-
|
|
18
|
+
tuya-interpreter's override pack, fixes published between releases, into that directory daily. Every key but `devices`
|
|
19
19
|
has a default. The bridge's own topic templates are read from its retained `{root}/bridge/config` at start.
|
|
20
20
|
"""
|
|
21
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Run the host-owned `Hub` over
|
|
1
|
+
"""Run the host-owned `Hub` over tuya-interpreter drivers: MQTT (or in-process) transports, timers, Last Will presence, reconnects,
|
|
2
2
|
runtime device changes, following rustuya-manager's `tuyadevices.json` and a directory of user rule files. TuyaDriver is Sans-I/O; this package owns file loading, clocks and transport operations.
|
|
3
3
|
|
|
4
4
|
from rustuya_local.host import MqttTransport, Runner
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""The device list: `tuyadevices.json`, which rustuya-manager keeps (its QR-login wizard writes it, atomically). Each
|
|
2
|
-
entry is a Tuya cloud record with the schema
|
|
2
|
+
entry is a Tuya cloud record with the schema tuya-interpreter needs (`category`, `product_id`, `function`,
|
|
3
3
|
`status_range`, `local_strategy`). rustuya-local reads it and follows it as it changes; it never writes it."""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
@@ -7,8 +7,15 @@ from collections import Counter
|
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
|
|
10
|
+
from tuya_interpreter import (
|
|
11
|
+
Command,
|
|
12
|
+
Connected,
|
|
13
|
+
Disconnected,
|
|
14
|
+
Message,
|
|
15
|
+
Timer,
|
|
16
|
+
TuyaDriver,
|
|
17
|
+
)
|
|
18
|
+
from tuya_interpreter.native import register_rules, unregister_rules
|
|
12
19
|
|
|
13
20
|
from .il import (
|
|
14
21
|
IL,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""IL MQTT wire mapping and host actions. Tuya interpretation stays in
|
|
1
|
+
"""IL MQTT wire mapping and host actions. Tuya interpretation stays in tuya-interpreter."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from tuya_interpreter import (
|
|
9
9
|
Absent,
|
|
10
10
|
CancelTimer,
|
|
11
11
|
Descriptor,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""User rule files: a directory (or one `.json` file) of partial rule objects, loaded and followed as it changes.
|
|
2
2
|
|
|
3
3
|
Each `*.json` is a rule layer (product/device fixes under `overrides`), merged in filename order over the rules
|
|
4
|
-
|
|
4
|
+
tuya-interpreter ships and validated by its engine. `SETTINGS_FILE` (``zz_settings.json``) holds the settings written
|
|
5
5
|
through IL, a block per device (`OverrideWatcher.save_settings`); it can be edited like any other file.
|
|
6
6
|
|
|
7
7
|
Files starting with `.` or `_` are skipped. Nothing here raises for a bad file: a file that does
|
|
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from tuya_interpreter import OverrideError, merge_all
|
|
22
22
|
|
|
23
23
|
from rustuya_local.host.rules import load_rules_tolerant, rule_files, upgrade_overrides
|
|
24
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The override pack: fixes for non-standard devices published on
|
|
1
|
+
"""The override pack: fixes for non-standard devices published on tuya-interpreter's `master` branch (`rules/builtin/`) and copied into
|
|
2
2
|
the host's rule directory, so a fix reaches users without a release. `OverrideWatcher` then loads the copied files
|
|
3
3
|
like the user's own. The files are rule layers (JSON); fetching them is the host's, the engine only merges them.
|
|
4
4
|
|
|
@@ -37,10 +37,10 @@ from .overrides import SETTINGS_FILE
|
|
|
37
37
|
|
|
38
38
|
_LOGGER = logging.getLogger(__name__)
|
|
39
39
|
|
|
40
|
-
LEVEL = 4 # 2: `cover` settings; 3: `delta`; 4: rule-layer files (tuya2ildevice 0.4)
|
|
41
|
-
BASE_URL = "https://raw.githubusercontent.com/3735943886/
|
|
40
|
+
LEVEL = 4 # 2: `cover` settings; 3: `delta`; 4: rule-layer files (tuya2ildevice 0.4, tuya-interpreter)
|
|
41
|
+
BASE_URL = "https://raw.githubusercontent.com/3735943886/tuya-interpreter/master/rules/builtin/"
|
|
42
42
|
MANIFEST = "manifest.json"
|
|
43
|
-
LEDGER = ".tuya2ildevice_pack.json"
|
|
43
|
+
LEDGER = ".tuya2ildevice_pack.json" # kept across the rename: existing installs own their files by it
|
|
44
44
|
MAX_BYTES = 1024 * 1024
|
|
45
45
|
TIMEOUT = 15
|
|
46
46
|
_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*\.json$")
|
|
@@ -8,8 +8,8 @@ import logging
|
|
|
8
8
|
import os
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
from
|
|
11
|
+
import tuya_interpreter
|
|
12
|
+
from tuya_interpreter import merge_rules
|
|
13
13
|
|
|
14
14
|
_LOGGER = logging.getLogger(__name__)
|
|
15
15
|
|
|
@@ -63,7 +63,7 @@ def _upgrade_layer(data, where):
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def rule_paths(paths=(), *, bundled_path=None):
|
|
66
|
-
package = Path(
|
|
66
|
+
package = Path(tuya_interpreter.__file__).resolve().parent
|
|
67
67
|
bundled = package / 'rules' / 'builtin'
|
|
68
68
|
checkout = package.parents[1] / 'rules' / 'builtin'
|
|
69
69
|
location = bundled_path or (bundled if bundled.is_dir() else checkout)
|
|
@@ -13,8 +13,8 @@ import secrets
|
|
|
13
13
|
import time
|
|
14
14
|
from collections.abc import Awaitable, Callable, Sequence
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
16
|
+
from tuya_interpreter import Connected, Disconnected
|
|
17
|
+
from tuya_interpreter import Message as DriverInput
|
|
18
18
|
|
|
19
19
|
from rustuya_local.host.il import (
|
|
20
20
|
IL,
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/manager_plugin/__init__.py
RENAMED
|
@@ -15,7 +15,7 @@ its documented contract (api_version >= 4). What it takes from the manager:
|
|
|
15
15
|
{"il": {"prefix": "il", "source": "tuya"},
|
|
16
16
|
"options": {"allow_hazardous": false, "expose_unused": false, "use_quirks": true, "pack": true}}
|
|
17
17
|
|
|
18
|
-
`pack` copies
|
|
18
|
+
`pack` copies tuya-interpreter's override pack (fixes published between releases) into `custom_converters/` daily.
|
|
19
19
|
|
|
20
20
|
The tab shows what the service is doing (the plugin's state namespace) and edits the settings and the
|
|
21
21
|
`custom_converters/` files through `/api/rustuya-local/...` (`api.py`). A settings change restarts the service in place;
|
|
@@ -228,7 +228,7 @@ async function convertersBox(ctx) {
|
|
|
228
228
|
box.append(
|
|
229
229
|
el("p", { class: "text-sm text-gray-500 mb-2" },
|
|
230
230
|
"Rule files (*.json: override blocks and declarative converters) for non-standard devices. ",
|
|
231
|
-
"See
|
|
231
|
+
"See tuya-interpreter docs/device-customization.md, \"User overrides\". Drop files here to copy them in."),
|
|
232
232
|
list, warnings,
|
|
233
233
|
el("div", { class: "flex gap-2 mb-2 items-center" }, el("label", {}, "File ", name),
|
|
234
234
|
el("button", { class: BTN, onclick: save }, "Save"), el("button", { class: BTN, onclick: remove }, "Delete"),
|
|
@@ -16,10 +16,10 @@ integration, the rustuya-manager plugin or a daemon) serves them, since every de
|
|
|
16
16
|
`online` that nobody answers for (a Last Will lost with the broker, or a producer on tuya2ildevice < 0.3.5) does not
|
|
17
17
|
stop the start.
|
|
18
18
|
|
|
19
|
-
With `pack` on, the override pack (
|
|
19
|
+
With `pack` on, the override pack (tuya-interpreter's `rules/builtin/`) is copied into the overrides directory in the background at
|
|
20
20
|
start and every `pack_interval` seconds; the directory watcher loads what it brings. Manual requests work with `pack` off.
|
|
21
21
|
|
|
22
|
-
A device's settings (a cover's direction toggles,
|
|
22
|
+
A device's settings (a cover's direction toggles, tuya-interpreter's `device_settings`) are IL properties like any other;
|
|
23
23
|
writing one saves it into the overrides directory (`rustuya_local.host.SETTINGS_FILE`), which the watcher applies at
|
|
24
24
|
once. Without an overrides directory there is nowhere to keep them, so the Hub does not offer them.
|
|
25
25
|
"""
|
|
@@ -36,7 +36,7 @@ from dataclasses import dataclass, field
|
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
from typing import Any
|
|
38
38
|
|
|
39
|
-
from
|
|
39
|
+
from tuya_interpreter import preload
|
|
40
40
|
|
|
41
41
|
from rustuya_local.host import (
|
|
42
42
|
BridgeCommand,
|
|
@@ -117,7 +117,7 @@ class Settings:
|
|
|
117
117
|
watch_interval: float = 5.0
|
|
118
118
|
pack: bool = False # automatically sync rules; manual sync remains available
|
|
119
119
|
pack_interval: float = 86400.0
|
|
120
|
-
pack_url: str | None = None # the pack's base URL;
|
|
120
|
+
pack_url: str | None = None # the pack's base URL; tuya-interpreter's `master` when None
|
|
121
121
|
hub_options: dict[str, Any] = field(default_factory=dict)
|
|
122
122
|
"""`rustuya_local.host.Hub` keyword arguments: `allow_hazardous`, `expose_unused`, `use_quirks`, `device_settings` (on
|
|
123
123
|
by default, and only with an overrides directory), `overrides` (inline, merged over the directory's), `rules`."""
|
|
@@ -48,7 +48,7 @@ def status_reply(ids, offset=0, has_more=False, total=None):
|
|
|
48
48
|
"returned": len(ids), "has_more": has_more, **({"device_count": total} if total is not None else {})})
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
GOLDEN = pathlib.Path(__file__).resolve().parents[3] / "
|
|
51
|
+
GOLDEN = pathlib.Path(__file__).resolve().parents[3] / "tuya-interpreter/tests/golden" # the sibling checkout
|
|
52
52
|
WIRE = {"Boolean": "Boolean", "Integer": "Integer", "Enum": "Enum", "String": "String", "Raw": "Raw", "Json": "Json",
|
|
53
53
|
"Bitmap": "Bitmap"}
|
|
54
54
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Differential test over the real bridge: many real device shapes (Home Assistant core's tuya fixtures) each get a
|
|
2
2
|
tuyamock device with the fixture's dps, go through the real rustuya-bridge and the rustuya-local daemon, and the IL values
|
|
3
|
-
that come out must equal what
|
|
3
|
+
that come out must equal what tuya-interpreter produces when it is fed the same dps directly.
|
|
4
4
|
|
|
5
5
|
What this catches that the in-process tests cannot: how the bridge serialises each Tuya type on the wire (booleans,
|
|
6
6
|
scaled integers, enums, strings, raw/base64, json), and anything the daemon does to a message on the way through.
|
|
@@ -20,12 +20,12 @@ import pytest
|
|
|
20
20
|
pyrustuyabridge = pytest.importorskip("pyrustuyabridge")
|
|
21
21
|
tuyamock = pytest.importorskip("tuyamock")
|
|
22
22
|
|
|
23
|
-
GOLDEN = pathlib.Path(__file__).resolve().parents[3] / "
|
|
23
|
+
GOLDEN = pathlib.Path(__file__).resolve().parents[3] / "tuya-interpreter/tests/golden" # the sibling checkout
|
|
24
24
|
sys.path.insert(0, str(GOLDEN))
|
|
25
25
|
import fixtures
|
|
26
26
|
from devices import fixture_record as record
|
|
27
27
|
from test_full_stack import Observer, wait
|
|
28
|
-
from
|
|
28
|
+
from tuya_interpreter import Connected, Message, TuyaDriver, Value
|
|
29
29
|
|
|
30
30
|
from rustuya_local.host.il import encode_value
|
|
31
31
|
from rustuya_local.host.rules import rules
|
|
@@ -99,9 +99,9 @@ plugin_dir = Path(sys.argv[1])
|
|
|
99
99
|
vendor = plugin_dir / "rustuya_local" / "_vendor"
|
|
100
100
|
|
|
101
101
|
class OnlyVendored:
|
|
102
|
-
#
|
|
102
|
+
# tuya-interpreter may be installed here; a manager's environment has none, so resolve it from the zip only
|
|
103
103
|
def find_spec(self, name, path=None, target=None):
|
|
104
|
-
if name.split(".")[0] != "
|
|
104
|
+
if name.split(".")[0] != "tuya_interpreter" or path is not None:
|
|
105
105
|
return None
|
|
106
106
|
spec = importlib.machinery.PathFinder.find_spec(name, [str(vendor)])
|
|
107
107
|
if spec is None:
|
|
@@ -118,12 +118,12 @@ ctx = plugins.PluginContext(plugins.PluginRegistry(), state=state, data_root=Pat
|
|
|
118
118
|
bridge_client=BridgeClient("mqtt://127.0.0.1:1", "r", state))
|
|
119
119
|
for r in registers:
|
|
120
120
|
r(ctx)
|
|
121
|
-
import rustuya_local,
|
|
121
|
+
import rustuya_local, tuya_interpreter
|
|
122
122
|
from rustuya_local.service import Service
|
|
123
123
|
from rustuya_local.host import MqttTransport
|
|
124
124
|
assert Path(rustuya_local.__file__).is_relative_to(plugin_dir), rustuya_local.__file__
|
|
125
|
-
assert Path(
|
|
126
|
-
from
|
|
125
|
+
assert Path(tuya_interpreter.__file__).is_relative_to(vendor), tuya_interpreter.__file__
|
|
126
|
+
from tuya_interpreter import TuyaDriver, Connected, Command, SendMessage
|
|
127
127
|
from rustuya_local.host.rules import rules
|
|
128
128
|
check = TuyaDriver({"id": "vendored", "category": "kg",
|
|
129
129
|
"function": {"switch_1": {"type": "Boolean", "values": {}}}},
|
|
@@ -135,7 +135,7 @@ print(len(registers), [p["id"] for p in ctx._registry.pages], len(ctx._registry.
|
|
|
135
135
|
"""
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def
|
|
138
|
+
def test_the_drop_in_zip_loads_in_the_manager_with_its_vendored_tuya_interpreter(tmp_path):
|
|
139
139
|
import importlib.util
|
|
140
140
|
import subprocess
|
|
141
141
|
import sys
|
|
@@ -162,7 +162,7 @@ async def test_driving_the_first_device_does_no_blocking_io_in_the_event_loop(ha
|
|
|
162
162
|
monkeypatch):
|
|
163
163
|
"""The host reads rule files and the binding loads its engine: the service preloads them in a worker
|
|
164
164
|
thread, so Home Assistant sees no blocking call when the first device's driver is made."""
|
|
165
|
-
from
|
|
165
|
+
from tuya_interpreter import native
|
|
166
166
|
|
|
167
167
|
from rustuya_local.host.rules import rules
|
|
168
168
|
|
|
@@ -175,7 +175,7 @@ async def test_driving_the_first_device_does_no_blocking_io_in_the_event_loop(ha
|
|
|
175
175
|
real_read = pathlib.Path.read_text
|
|
176
176
|
|
|
177
177
|
def read_text(self, *args, **kwargs): # what Home Assistant's blocking-call detector flags, recorded instead
|
|
178
|
-
if threading.current_thread() is loop_thread and "
|
|
178
|
+
if threading.current_thread() is loop_thread and "tuya_interpreter" in str(self):
|
|
179
179
|
in_loop.append(self.name)
|
|
180
180
|
return real_read(self, *args, **kwargs)
|
|
181
181
|
monkeypatch.setattr(pathlib.Path, "read_text", read_text)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The rustuya-manager drop-in zip: one top-level package with
|
|
1
|
+
"""The rustuya-manager drop-in zip: one top-level package with tuya-interpreter vendored, byte-identical rebuilds."""
|
|
2
2
|
import hashlib
|
|
3
3
|
import importlib.util
|
|
4
4
|
import os
|
|
@@ -14,15 +14,15 @@ sys.modules["build_dropin"] = build_dropin
|
|
|
14
14
|
spec.loader.exec_module(build_dropin)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def test_one_package_with_tuya_interpreter_inside(tmp_path):
|
|
18
18
|
zf = zipfile.ZipFile(build_dropin.build(tmp_path))
|
|
19
19
|
names = zf.namelist()
|
|
20
20
|
assert {n.split("/", 1)[0] for n in names} == {"rustuya_local"} # the manager imports each top-level entry
|
|
21
21
|
for needed in ("rustuya_local/__init__.py", "rustuya_local/manager_plugin/static/index.js",
|
|
22
|
-
"rustuya_local/_vendor/
|
|
22
|
+
"rustuya_local/_vendor/tuya_interpreter/__init__.py", "rustuya_local/_vendor/tuya_interpreter/native.py"):
|
|
23
23
|
assert needed in names
|
|
24
|
-
assert "rustuya_local/_vendor/
|
|
25
|
-
assert any(n.startswith("rustuya_local/_vendor/
|
|
24
|
+
assert "rustuya_local/_vendor/tuya_interpreter/rules/builtin/00-default.json" in names
|
|
25
|
+
assert any(n.startswith("rustuya_local/_vendor/tuya_interpreter/") and n.endswith((".so", ".dll", ".dylib"))
|
|
26
26
|
for n in names)
|
|
27
27
|
assert not any("__pycache__" in n or n.startswith("rustuya_local/_vendor/paho") for n in names)
|
|
28
28
|
|
|
@@ -33,7 +33,7 @@ def test_rebuilds_are_byte_identical(tmp_path):
|
|
|
33
33
|
assert hashlib.sha256(a).digest() == hashlib.sha256(b).digest()
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def test_archive_runs_driver_without_installed_tuya_interpreter(tmp_path):
|
|
37
37
|
with zipfile.ZipFile(build_dropin.build(tmp_path)) as archive:
|
|
38
38
|
archive.extractall(tmp_path)
|
|
39
39
|
# -I -S excludes editable installs and site-packages: both native code and rules
|
|
@@ -43,9 +43,9 @@ import sys
|
|
|
43
43
|
from pathlib import Path
|
|
44
44
|
sys.path.insert(0, sys.argv[1])
|
|
45
45
|
from rustuya_local.host import Hub, BridgeCommand
|
|
46
|
-
from
|
|
47
|
-
import
|
|
48
|
-
assert Path(
|
|
46
|
+
from tuya_interpreter import Connected, Message
|
|
47
|
+
import tuya_interpreter
|
|
48
|
+
assert Path(tuya_interpreter.__file__).is_relative_to(Path(sys.argv[1]))
|
|
49
49
|
device = {'id': 'd', 'category': 'kg',
|
|
50
50
|
'function': {'switch_1': {'type': 'Boolean', 'values': {}}},
|
|
51
51
|
'local_strategy': {'1': {'value_convert': 'default', 'status_code': 'switch_1',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The host-owned Hub around
|
|
1
|
+
"""The host-owned Hub around tuya-interpreter drivers (`rustuya_local.host`): Runner on the IL transport, the device file, the rule
|
|
2
2
|
directory, and paho against a real broker (mosquitto, skipped when it is not installed). The bridge side is simulated
|
|
3
3
|
with `runner.on_bridge_message`; `BridgeCommand` outputs are captured via the injected `on_bridge_command` callback."""
|
|
4
4
|
import asyncio
|
|
@@ -9,7 +9,7 @@ import subprocess
|
|
|
9
9
|
import time
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
|
-
from
|
|
12
|
+
from tuya_interpreter import Connected, Message, OverrideError
|
|
13
13
|
|
|
14
14
|
from rustuya_local.host import (
|
|
15
15
|
BridgeCommand,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
-
from
|
|
4
|
+
from tuya_interpreter import Connected, Disconnected, Message
|
|
5
5
|
|
|
6
6
|
from rustuya_local.host import BridgeCommand, Hub, Publish
|
|
7
7
|
from tests.unit.tuya_samples import curtain, light
|
|
@@ -75,7 +75,7 @@ def test_a_multi_segment_il_prefix_still_parses_writes():
|
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
def test_reload_overrides_republishes_and_clears_removed_props():
|
|
78
|
-
from
|
|
78
|
+
from tuya_interpreter import OverrideError
|
|
79
79
|
|
|
80
80
|
hub = Hub([curtain()])
|
|
81
81
|
hub.start()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""The host wire mapping follows ildevice conformance vectors (../ildevice/vectors, or $ILDEVICE): payloads on the
|
|
2
|
-
wire and topics. (Command checks and descriptor schemas are exercised by
|
|
2
|
+
wire and topics. (Command checks and descriptor schemas are exercised by tuya-interpreter.)"""
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
-
from
|
|
8
|
+
from tuya_interpreter import Rejected, check_command
|
|
9
9
|
|
|
10
10
|
from rustuya_local.host import Hub, IlTopics
|
|
11
11
|
from rustuya_local.host.il import decode_write, encode_value
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The override pack: `host.pack.sync` against a pack served from a directory (file://), and the one
|
|
1
|
+
"""The override pack: `host.pack.sync` against a pack served from a directory (file://), and the one tuya-interpreter
|
|
2
2
|
publishes (its sibling checkout, when there is one)."""
|
|
3
3
|
|
|
4
4
|
import hashlib
|
|
@@ -10,7 +10,7 @@ import pytest
|
|
|
10
10
|
from rustuya_local.host import load_overrides, pack
|
|
11
11
|
from rustuya_local.host.pack import LEDGER, PackError, read_ledger, sync
|
|
12
12
|
|
|
13
|
-
PUBLISHED = Path(__file__).resolve().parents[3] / "
|
|
13
|
+
PUBLISHED = Path(__file__).resolve().parents[3] / "tuya-interpreter" / "rules" / "builtin" # the sibling checkout
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def sha(text: str) -> str:
|
|
@@ -104,7 +104,7 @@ def test_a_bad_file_is_skipped_and_the_copy_there_stays(served):
|
|
|
104
104
|
|
|
105
105
|
def test_files_are_bounded_by_level(served):
|
|
106
106
|
src, dest = served
|
|
107
|
-
src.put("00_pack_new.json", FIX, requires=pack.LEVEL + 1) # needs a newer
|
|
107
|
+
src.put("00_pack_new.json", FIX, requires=pack.LEVEL + 1) # needs a newer tuya-interpreter
|
|
108
108
|
src.put("00_pack_old.json", FIX, until=pack.LEVEL) # built into this one
|
|
109
109
|
src.put("00_pack_now.json", FIX, requires=pack.LEVEL, until=pack.LEVEL + 1)
|
|
110
110
|
assert sync(dest, base_url=src.url).added == ["00_pack_now.json"]
|
|
@@ -130,7 +130,7 @@ def test_unsafe_names_in_the_manifest_are_ignored(served):
|
|
|
130
130
|
assert not r.changed and not (dest.parent / "escape.json").exists()
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
@pytest.mark.skipif(not PUBLISHED.is_dir(), reason="no
|
|
133
|
+
@pytest.mark.skipif(not PUBLISHED.is_dir(), reason="no tuya-interpreter checkout next to this one")
|
|
134
134
|
def test_the_published_pack_loads(tmp_path):
|
|
135
135
|
r = sync(tmp_path / "d", base_url=PUBLISHED.as_uri() + "/")
|
|
136
136
|
assert not r.failed
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import weakref
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
|
-
from
|
|
8
|
+
from tuya_interpreter import Connected, TuyaDriver, merge_rules
|
|
9
9
|
|
|
10
10
|
from rustuya_local.host import Hub, OverrideWatcher, load_overrides
|
|
11
11
|
from rustuya_local.host.hub import _pack_users
|
|
@@ -84,7 +84,7 @@ def test_inline_overrides_with_older_keys_are_upgraded():
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def test_a_rule_pack_the_hub_moves_off_is_released_once_no_hub_uses_it():
|
|
87
|
-
from
|
|
87
|
+
from tuya_interpreter.native import unregister_rules
|
|
88
88
|
|
|
89
89
|
own, other = labelled('only this test'), labelled('Other')
|
|
90
90
|
first, second = Hub([dict(DEVICE)], rules=own), Hub([dict(DEVICE, id='e')], rules=own)
|
|
@@ -101,7 +101,7 @@ def labelled(label):
|
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def test_close_releases_only_the_last_owner_and_is_idempotent():
|
|
104
|
-
from
|
|
104
|
+
from tuya_interpreter.native import unregister_rules
|
|
105
105
|
|
|
106
106
|
pack = labelled('shared close regression')
|
|
107
107
|
first, second = Hub([DEVICE], rules=pack), Hub([DEVICE], rules=pack)
|
|
@@ -121,7 +121,7 @@ def test_close_releases_only_the_last_owner_and_is_idempotent():
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
def test_a_discarded_hub_releases_its_pack():
|
|
124
|
-
from
|
|
124
|
+
from tuya_interpreter.native import unregister_rules
|
|
125
125
|
|
|
126
126
|
hub = Hub([DEVICE], rules=labelled('garbage collection regression'))
|
|
127
127
|
key, reference = hub._rules_id, weakref.ref(hub)
|
|
@@ -132,8 +132,8 @@ def test_a_discarded_hub_releases_its_pack():
|
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
def test_failed_construction_releases_its_pack():
|
|
135
|
-
from
|
|
136
|
-
from
|
|
135
|
+
from tuya_interpreter import OverrideError
|
|
136
|
+
from tuya_interpreter.native import register_rules, unregister_rules
|
|
137
137
|
|
|
138
138
|
pack = labelled('failed construction regression')
|
|
139
139
|
key = register_rules(pack)
|
|
@@ -145,7 +145,7 @@ def test_failed_construction_releases_its_pack():
|
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
def test_failed_adoption_releases_the_candidate_and_keeps_the_existing_drivers(monkeypatch):
|
|
148
|
-
from
|
|
148
|
+
from tuya_interpreter.native import register_rules, unregister_rules
|
|
149
149
|
|
|
150
150
|
hub = Hub([DEVICE, dict(DEVICE, id='e')], rules=labelled('before failed adoption'))
|
|
151
151
|
pack = labelled('failed adoption regression')
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
### Fixed
|
|
2
|
-
- Share the running service's complete bridge registry and MQTT command connection with the panel, so panel listing and edits do not open a second registry requester. Require complete contiguous registry pages for standalone setup sessions.
|
|
3
|
-
- Keep explicit offline connection errors from being overwritten by replayed device state snapshots in the panel.
|
|
4
|
-
|
|
5
|
-
### Changed
|
|
6
|
-
- Split the HA panel synced filter into online and offline, with live sub-device/gateway state and migration of saved filters.
|
|
7
|
-
- Check builtin rules when opening the HA panel; automatically install available updates when enabled, or enable the manual update button with a yellow dot when automatic updates are off.
|
|
8
|
-
- Allow manual builtin rule updates while automatic updates are disabled.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon.png
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon.svg
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/icon@2x.png
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo.png
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo.svg
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/brand/logo@2x.png
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/bridge_sync.py
RENAMED
|
File without changes
|
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/config_flow.py
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/manager_registry.py
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/manager_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/panel_bridge.py
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/services.yaml
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/strings.json
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/translations/en.json
RENAMED
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/custom_components/rustuya/translations/ko.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/src/rustuya_local/manager_plugin/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustuya_local-0.1.0.dev5 → rustuya_local-0.1.0.dev6}/tests/e2e/test_full_stack_custom_templates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|