python-rako-2025 0.5.0__tar.gz → 0.5.2__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.
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.github/dependabot.yml +4 -6
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/CHANGELOG.md +30 -1
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/PKG-INFO +1 -1
- python_rako_2025-0.5.2/python_rako/__version__.py +1 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/state.py +19 -3
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/resources/rako3.xml +80 -80
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_state.py +73 -1
- python_rako_2025-0.5.0/python_rako/__version__.py +0 -1
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.coveragerc +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.editorconfig +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.flake8 +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.gitattributes +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.github/workflows/ci.yml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.github/workflows/release.yml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.gitignore +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.isort.cfg +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.pre-commit-config.yaml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/.yamllint +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/LICENSE +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/Makefile +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/README.md +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/RELEASING.md +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/catalog-info.yaml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/conftest.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/doc/accessing-the-rako-bridge.pdf +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/discover_all_devices.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/example.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/listen_status.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/state_snapshot.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/ventilation_example.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/verified_commands.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/examples/view_updates.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/pyproject.toml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/__init__.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/bridge.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/commands.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/const.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/exceptions.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/helpers.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/listener.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/model.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/pacing.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/protocol.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/python_rako/py.typed +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/scripts/bump_version.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/scripts/latency.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/scripts/listen.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/scripts/measure_interval.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/scripts/snapshot.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/README_TESTING.md +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/resources/rako.xml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/resources/rako2.xml +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_async_lock.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_bridge.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_bridge_async_parsing.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_bridge_state.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_command_queue.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_commands.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_discovery.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_edge_cases.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_helpers.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_listener.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_measure_interval.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_model.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_protocol.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests/test_xml_parsing.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/__init__.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/conftest.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_bridge_info_live.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_command_live.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_discovery_live.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_listener_live.py +0 -0
- {python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_state_live.py +0 -0
|
@@ -4,12 +4,10 @@ updates:
|
|
|
4
4
|
- package-ecosystem: pip
|
|
5
5
|
directory: "/"
|
|
6
6
|
schedule:
|
|
7
|
-
interval:
|
|
8
|
-
|
|
9
|
-
open-pull-requests-limit: 10
|
|
7
|
+
interval: weekly
|
|
8
|
+
open-pull-requests-limit: 5
|
|
10
9
|
- package-ecosystem: "github-actions"
|
|
11
10
|
directory: "/"
|
|
12
11
|
schedule:
|
|
13
|
-
interval:
|
|
14
|
-
|
|
15
|
-
open-pull-requests-limit: 10
|
|
12
|
+
interval: weekly
|
|
13
|
+
open-pull-requests-limit: 5
|
|
@@ -9,6 +9,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
Nothing yet.
|
|
11
11
|
|
|
12
|
+
## [0.5.2] - 2026-11-04
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- The `tests/resources/rako3.xml` fixture is anonymised: room titles, channel
|
|
17
|
+
names and scene names are now `Room <id>`, `Channel <id>` and `Scene <id>`.
|
|
18
|
+
Ids, levels and structure are unchanged, so every test runs against the same
|
|
19
|
+
topology. The fixture ships in the sdist, so releases from this one on carry
|
|
20
|
+
only the generic names.
|
|
21
|
+
|
|
22
|
+
## [0.5.1] - 2026-11-04
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `BridgeStateSnapshot.reconcile()` no longer wipes rooms that are missing
|
|
27
|
+
from the scene cache. `from_caches()` lists every room in the level table,
|
|
28
|
+
so a missing room arrived with `scene=None`, which `reconcile()` read as a
|
|
29
|
+
missed scene change, and reset the room and its channels to unknown. This is
|
|
30
|
+
the opposite of the documented rule that absence is not evidence. A bridge
|
|
31
|
+
that has just restarted refills its cache one scene selection at a time
|
|
32
|
+
(occupancy-sensor rooms within minutes), so the first non-empty read after a
|
|
33
|
+
restart reset every other room to unknown. Rooms missing from the cache now
|
|
34
|
+
only fill in rooms and channels nothing has reported yet. They keep a faded
|
|
35
|
+
room's `faded_from` guard, which the wipe used to discard, letting a later
|
|
36
|
+
stale cache entry bring back the pre-fade scene. The test for this rule
|
|
37
|
+
passed only because it used an empty level table; it now uses a real one.
|
|
38
|
+
|
|
12
39
|
## [0.5.0] - 2026-08-30
|
|
13
40
|
|
|
14
41
|
### Added
|
|
@@ -110,7 +137,9 @@ e.g.:
|
|
|
110
137
|
- ...
|
|
111
138
|
-->
|
|
112
139
|
|
|
113
|
-
[Unreleased]: https://github.com/SimonLeigh/python-rako/compare/v0.5.
|
|
140
|
+
[Unreleased]: https://github.com/SimonLeigh/python-rako/compare/v0.5.2...master
|
|
141
|
+
[0.5.2]: https://github.com/SimonLeigh/python-rako/compare/v0.5.1...v0.5.2
|
|
142
|
+
[0.5.1]: https://github.com/SimonLeigh/python-rako/compare/v0.5.0...v0.5.1
|
|
114
143
|
[0.5.0]: https://github.com/SimonLeigh/python-rako/releases/tag/v0.5.0
|
|
115
144
|
|
|
116
145
|
<!--
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-rako-2025
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Asynchronous Python client for Rako Controls Lighting
|
|
5
5
|
Project-URL: Homepage, https://github.com/SimonLeigh/python-rako
|
|
6
6
|
Project-URL: Repository, https://github.com/SimonLeigh/python-rako
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.2"
|
|
@@ -328,9 +328,16 @@ class BridgeStateSnapshot:
|
|
|
328
328
|
Had the cache said scene 3, the scene change would have been one we
|
|
329
329
|
missed, and the room would be rebuilt from the cache instead.
|
|
330
330
|
|
|
331
|
-
Rooms absent from
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
Rooms absent from the scene cache are left exactly as they are, whether
|
|
332
|
+
they are missing from ``fresh`` altogether or -- as
|
|
333
|
+
:meth:`from_caches` lists every room in the level table -- present with
|
|
334
|
+
``scene=None``. Absence is not evidence of anything: the bridge
|
|
335
|
+
deletes fade-controlled rooms from its scene cache by design, and after
|
|
336
|
+
a restart its cache starts empty and refills one scene selection at a
|
|
337
|
+
time, so a read taken then lists only the rooms used since. Treating
|
|
338
|
+
``None`` as a scene change would wipe every other room to unknown.
|
|
339
|
+
Such a room only fills in rooms and channels we have never heard of,
|
|
340
|
+
as unknown; it never overwrites anything already tracked.
|
|
334
341
|
|
|
335
342
|
**Fades get one extra guard.** A fade sets the room's scene to
|
|
336
343
|
``None``, which would otherwise always "differ" from a cached scene and
|
|
@@ -351,6 +358,15 @@ class BridgeStateSnapshot:
|
|
|
351
358
|
rc: state for rc, state in fresh.channels.items() if rc.room_id == room
|
|
352
359
|
}
|
|
353
360
|
|
|
361
|
+
if fresh_room.scene is None:
|
|
362
|
+
# Not in the scene cache: no news. Keep everything we know --
|
|
363
|
+
# including a faded room's faded_from guard -- and only fill in
|
|
364
|
+
# rooms and channels we have never heard of, as unknown.
|
|
365
|
+
rooms.setdefault(room, replace(fresh_room, updated_at=now))
|
|
366
|
+
for room_channel, state in fresh_channels.items():
|
|
367
|
+
channels.setdefault(room_channel, replace(state, updated_at=now))
|
|
368
|
+
continue
|
|
369
|
+
|
|
354
370
|
if tracked is not None and tracked.is_faded:
|
|
355
371
|
if fresh_room.scene == tracked.faded_from:
|
|
356
372
|
_LOGGER.debug(
|
|
@@ -17,365 +17,365 @@
|
|
|
17
17
|
<rooms>
|
|
18
18
|
<Room id="0">
|
|
19
19
|
<Type>Lights</Type>
|
|
20
|
-
<Title>
|
|
20
|
+
<Title>Room 0</Title>
|
|
21
21
|
</Room>
|
|
22
22
|
<Room id="1">
|
|
23
23
|
<Type>Lights</Type>
|
|
24
|
-
<Title>
|
|
24
|
+
<Title>Room 1</Title>
|
|
25
25
|
<mode>Named+OFF</mode>
|
|
26
26
|
<Scene id="1">
|
|
27
|
-
<Name>
|
|
27
|
+
<Name>Scene 1</Name>
|
|
28
28
|
</Scene>
|
|
29
29
|
<Scene id="2">
|
|
30
|
-
<Name>
|
|
30
|
+
<Name>Scene 2</Name>
|
|
31
31
|
</Scene>
|
|
32
32
|
</Room>
|
|
33
33
|
<Room id="13">
|
|
34
34
|
<Type>Lights</Type>
|
|
35
|
-
<Title>
|
|
35
|
+
<Title>Room 13</Title>
|
|
36
36
|
<Channel id="1">
|
|
37
37
|
<type>Slider</type>
|
|
38
|
-
<Name>
|
|
38
|
+
<Name>Channel 1</Name>
|
|
39
39
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
40
40
|
</Channel>
|
|
41
41
|
</Room>
|
|
42
42
|
<Room id="65">
|
|
43
43
|
<Type>Lights</Type>
|
|
44
|
-
<Title>
|
|
44
|
+
<Title>Room 65</Title>
|
|
45
45
|
<Channel id="1">
|
|
46
46
|
<type>Slider</type>
|
|
47
|
-
<Name>
|
|
47
|
+
<Name>Channel 1</Name>
|
|
48
48
|
<Levels>67000000000000000000000000000000</Levels>
|
|
49
49
|
</Channel>
|
|
50
50
|
<Channel id="2">
|
|
51
51
|
<type>Slider</type>
|
|
52
|
-
<Name>
|
|
52
|
+
<Name>Channel 2</Name>
|
|
53
53
|
<Levels>FF005DFF000000000000000000000000</Levels>
|
|
54
54
|
</Channel>
|
|
55
55
|
<Channel id="3">
|
|
56
56
|
<type>Slider</type>
|
|
57
|
-
<Name>
|
|
57
|
+
<Name>Channel 3</Name>
|
|
58
58
|
<Levels>0058FF00000000000000000000000000</Levels>
|
|
59
59
|
</Channel>
|
|
60
60
|
</Room>
|
|
61
61
|
<Room id="153">
|
|
62
62
|
<Type>Lights</Type>
|
|
63
|
-
<Title>
|
|
63
|
+
<Title>Room 153</Title>
|
|
64
64
|
<Channel id="1">
|
|
65
65
|
<type>Default</type>
|
|
66
|
-
<Name>
|
|
66
|
+
<Name>Channel 1</Name>
|
|
67
67
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
68
68
|
</Channel>
|
|
69
69
|
</Room>
|
|
70
70
|
<Room id="14">
|
|
71
71
|
<Type>Lights</Type>
|
|
72
|
-
<Title>
|
|
72
|
+
<Title>Room 14</Title>
|
|
73
73
|
<Channel id="1">
|
|
74
74
|
<type>Default</type>
|
|
75
|
-
<Name>
|
|
75
|
+
<Name>Channel 1</Name>
|
|
76
76
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
77
77
|
</Channel>
|
|
78
78
|
<Channel id="2">
|
|
79
79
|
<type>Default</type>
|
|
80
|
-
<Name>
|
|
80
|
+
<Name>Channel 2</Name>
|
|
81
81
|
<Levels>00BF7F3F000000000000000000000000</Levels>
|
|
82
82
|
</Channel>
|
|
83
83
|
<Channel id="3">
|
|
84
84
|
<type>Default</type>
|
|
85
|
-
<Name>
|
|
85
|
+
<Name>Channel 3</Name>
|
|
86
86
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
87
87
|
</Channel>
|
|
88
88
|
</Room>
|
|
89
89
|
<Room id="5">
|
|
90
90
|
<Type>Lights</Type>
|
|
91
|
-
<Title>
|
|
91
|
+
<Title>Room 5</Title>
|
|
92
92
|
<Channel id="1">
|
|
93
93
|
<type>switch</type>
|
|
94
|
-
<Name>
|
|
94
|
+
<Name>Channel 1</Name>
|
|
95
95
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
96
96
|
</Channel>
|
|
97
97
|
<Channel id="2">
|
|
98
98
|
<type>switch</type>
|
|
99
|
-
<Name>
|
|
99
|
+
<Name>Channel 2</Name>
|
|
100
100
|
<Levels>00007F3F000000000000000000000000</Levels>
|
|
101
101
|
</Channel>
|
|
102
102
|
<Channel id="3">
|
|
103
103
|
<type>switch</type>
|
|
104
|
-
<Name>
|
|
104
|
+
<Name>Channel 3</Name>
|
|
105
105
|
<Levels>FFFF7F3F000000000000000000000000</Levels>
|
|
106
106
|
</Channel>
|
|
107
107
|
<Channel id="4">
|
|
108
108
|
<type>Slider</type>
|
|
109
|
-
<Name>
|
|
109
|
+
<Name>Channel 4</Name>
|
|
110
110
|
<Levels>FF007F3F000000000000000000000000</Levels>
|
|
111
111
|
</Channel>
|
|
112
112
|
<Channel id="5">
|
|
113
113
|
<type>Slider</type>
|
|
114
|
-
<Name>
|
|
114
|
+
<Name>Channel 5</Name>
|
|
115
115
|
<Levels>FFFF7F3F000000000000000000000000</Levels>
|
|
116
116
|
</Channel>
|
|
117
117
|
</Room>
|
|
118
118
|
<Room id="6">
|
|
119
119
|
<Type>Lights</Type>
|
|
120
|
-
<Title>
|
|
120
|
+
<Title>Room 6</Title>
|
|
121
121
|
<Channel id="1">
|
|
122
122
|
<type>Default</type>
|
|
123
|
-
<Name>
|
|
123
|
+
<Name>Channel 1</Name>
|
|
124
124
|
<Levels>00000000000000000000000000000000</Levels>
|
|
125
125
|
</Channel>
|
|
126
126
|
<Channel id="2">
|
|
127
127
|
<type>switch</type>
|
|
128
|
-
<Name>
|
|
128
|
+
<Name>Channel 2</Name>
|
|
129
129
|
<Levels>00BF00FF000000000000000000000000</Levels>
|
|
130
130
|
</Channel>
|
|
131
131
|
<Channel id="3">
|
|
132
132
|
<type>Slider</type>
|
|
133
|
-
<Name>
|
|
133
|
+
<Name>Channel 3</Name>
|
|
134
134
|
<Levels>C0BFFF00000000000000000000000000</Levels>
|
|
135
135
|
</Channel>
|
|
136
136
|
</Room>
|
|
137
137
|
<Room id="7">
|
|
138
138
|
<Type>Lights</Type>
|
|
139
|
-
<Title>
|
|
139
|
+
<Title>Room 7</Title>
|
|
140
140
|
<Channel id="1">
|
|
141
141
|
<type>Default</type>
|
|
142
|
-
<Name>
|
|
142
|
+
<Name>Channel 1</Name>
|
|
143
143
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
144
144
|
</Channel>
|
|
145
145
|
<Channel id="2">
|
|
146
146
|
<type>Default</type>
|
|
147
|
-
<Name>
|
|
147
|
+
<Name>Channel 2</Name>
|
|
148
148
|
<Levels>00BF7F3F000000000000000000000000</Levels>
|
|
149
149
|
</Channel>
|
|
150
150
|
</Room>
|
|
151
151
|
<Room id="145">
|
|
152
152
|
<Type>Lights</Type>
|
|
153
|
-
<Title>
|
|
153
|
+
<Title>Room 145</Title>
|
|
154
154
|
<Channel id="1">
|
|
155
155
|
<type>Slider</type>
|
|
156
|
-
<Name>
|
|
156
|
+
<Name>Channel 1</Name>
|
|
157
157
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
158
158
|
</Channel>
|
|
159
159
|
</Room>
|
|
160
160
|
<Room id="9">
|
|
161
161
|
<Type>Lights</Type>
|
|
162
|
-
<Title>
|
|
162
|
+
<Title>Room 9</Title>
|
|
163
163
|
<Channel id="1">
|
|
164
164
|
<type>switch</type>
|
|
165
|
-
<Name>
|
|
165
|
+
<Name>Channel 1</Name>
|
|
166
166
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
167
167
|
</Channel>
|
|
168
168
|
<Channel id="2">
|
|
169
169
|
<type>Default</type>
|
|
170
|
-
<Name>
|
|
170
|
+
<Name>Channel 2</Name>
|
|
171
171
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
172
172
|
</Channel>
|
|
173
173
|
<Channel id="3">
|
|
174
174
|
<type>Slider</type>
|
|
175
|
-
<Name>
|
|
175
|
+
<Name>Channel 3</Name>
|
|
176
176
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
177
177
|
</Channel>
|
|
178
178
|
</Room>
|
|
179
179
|
<Room id="10">
|
|
180
180
|
<Type>Lights</Type>
|
|
181
|
-
<Title>
|
|
181
|
+
<Title>Room 10</Title>
|
|
182
182
|
<Channel id="1">
|
|
183
183
|
<type>Default</type>
|
|
184
|
-
<Name>
|
|
184
|
+
<Name>Channel 1</Name>
|
|
185
185
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
186
186
|
</Channel>
|
|
187
187
|
</Room>
|
|
188
188
|
<Room id="17">
|
|
189
189
|
<Type>Lights</Type>
|
|
190
|
-
<Title>
|
|
190
|
+
<Title>Room 17</Title>
|
|
191
191
|
<Channel id="1">
|
|
192
192
|
<type>Default</type>
|
|
193
|
-
<Name>
|
|
193
|
+
<Name>Channel 1</Name>
|
|
194
194
|
<Levels>FFFF7F00000000000000000000000000</Levels>
|
|
195
195
|
</Channel>
|
|
196
196
|
<Channel id="2">
|
|
197
197
|
<type>Default</type>
|
|
198
|
-
<Name>
|
|
198
|
+
<Name>Channel 2</Name>
|
|
199
199
|
<Levels>00007F3F000000000000000000000000</Levels>
|
|
200
200
|
</Channel>
|
|
201
201
|
</Room>
|
|
202
202
|
<Room id="18">
|
|
203
203
|
<Type>Lights</Type>
|
|
204
|
-
<Title>
|
|
204
|
+
<Title>Room 18</Title>
|
|
205
205
|
<Channel id="1">
|
|
206
206
|
<type>Default</type>
|
|
207
|
-
<Name>
|
|
207
|
+
<Name>Channel 1</Name>
|
|
208
208
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
209
209
|
</Channel>
|
|
210
210
|
</Room>
|
|
211
211
|
<Room id="19">
|
|
212
212
|
<Type>Lights</Type>
|
|
213
|
-
<Title>
|
|
213
|
+
<Title>Room 19</Title>
|
|
214
214
|
<Channel id="1">
|
|
215
215
|
<type>Default</type>
|
|
216
|
-
<Name>
|
|
216
|
+
<Name>Channel 1</Name>
|
|
217
217
|
<Levels>FF2F7F3F000000000000000000000000</Levels>
|
|
218
218
|
</Channel>
|
|
219
219
|
</Room>
|
|
220
220
|
<Room id="73">
|
|
221
221
|
<Type>Lights</Type>
|
|
222
|
-
<Title>
|
|
222
|
+
<Title>Room 73</Title>
|
|
223
223
|
<Channel id="1">
|
|
224
224
|
<type>Slider</type>
|
|
225
|
-
<Name>
|
|
225
|
+
<Name>Channel 1</Name>
|
|
226
226
|
<Levels>FF00FFFF000000000000000000000000</Levels>
|
|
227
227
|
</Channel>
|
|
228
228
|
<Channel id="2">
|
|
229
229
|
<type>Default</type>
|
|
230
|
-
<Name>
|
|
230
|
+
<Name>Channel 2</Name>
|
|
231
231
|
<Levels>FFFF0000000000000000000000000000</Levels>
|
|
232
232
|
</Channel>
|
|
233
233
|
</Room>
|
|
234
234
|
<Room id="137">
|
|
235
235
|
<Type>Lights</Type>
|
|
236
|
-
<Title>
|
|
236
|
+
<Title>Room 137</Title>
|
|
237
237
|
<Channel id="1">
|
|
238
238
|
<type>Default</type>
|
|
239
|
-
<Name>
|
|
239
|
+
<Name>Channel 1</Name>
|
|
240
240
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
241
241
|
</Channel>
|
|
242
242
|
</Room>
|
|
243
243
|
<Room id="15">
|
|
244
244
|
<Type>Lights</Type>
|
|
245
|
-
<Title>
|
|
245
|
+
<Title>Room 15</Title>
|
|
246
246
|
<Channel id="1">
|
|
247
247
|
<type>Default</type>
|
|
248
|
-
<Name>
|
|
248
|
+
<Name>Channel 1</Name>
|
|
249
249
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
250
250
|
</Channel>
|
|
251
251
|
<Channel id="2">
|
|
252
252
|
<type>Default</type>
|
|
253
|
-
<Name>
|
|
253
|
+
<Name>Channel 2</Name>
|
|
254
254
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
255
255
|
</Channel>
|
|
256
256
|
</Room>
|
|
257
257
|
<Room id="89">
|
|
258
258
|
<Type>Lights</Type>
|
|
259
|
-
<Title>
|
|
259
|
+
<Title>Room 89</Title>
|
|
260
260
|
<Channel id="1">
|
|
261
261
|
<type>Default</type>
|
|
262
|
-
<Name>
|
|
262
|
+
<Name>Channel 1</Name>
|
|
263
263
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
264
264
|
</Channel>
|
|
265
265
|
</Room>
|
|
266
266
|
<Room id="113">
|
|
267
267
|
<Type>Lights</Type>
|
|
268
|
-
<Title>
|
|
268
|
+
<Title>Room 113</Title>
|
|
269
269
|
<Channel id="1">
|
|
270
270
|
<type>Default</type>
|
|
271
|
-
<Name>
|
|
271
|
+
<Name>Channel 1</Name>
|
|
272
272
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
273
273
|
</Channel>
|
|
274
274
|
</Room>
|
|
275
275
|
<Room id="105">
|
|
276
276
|
<Type>Lights</Type>
|
|
277
|
-
<Title>
|
|
277
|
+
<Title>Room 105</Title>
|
|
278
278
|
<Channel id="1">
|
|
279
279
|
<type>Default</type>
|
|
280
|
-
<Name>
|
|
280
|
+
<Name>Channel 1</Name>
|
|
281
281
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
282
282
|
</Channel>
|
|
283
283
|
</Room>
|
|
284
284
|
<Room id="97">
|
|
285
285
|
<Type>Lights</Type>
|
|
286
|
-
<Title>
|
|
286
|
+
<Title>Room 97</Title>
|
|
287
287
|
<Channel id="1">
|
|
288
288
|
<type>Default</type>
|
|
289
|
-
<Name>
|
|
289
|
+
<Name>Channel 1</Name>
|
|
290
290
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
291
291
|
</Channel>
|
|
292
292
|
</Room>
|
|
293
293
|
<Room id="121">
|
|
294
294
|
<Type>Lights</Type>
|
|
295
|
-
<Title>
|
|
295
|
+
<Title>Room 121</Title>
|
|
296
296
|
<Channel id="1">
|
|
297
297
|
<type>Default</type>
|
|
298
|
-
<Name>
|
|
298
|
+
<Name>Channel 1</Name>
|
|
299
299
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
300
300
|
</Channel>
|
|
301
301
|
</Room>
|
|
302
302
|
<Room id="156">
|
|
303
303
|
<Type>Lights</Type>
|
|
304
|
-
<Title>
|
|
304
|
+
<Title>Room 156</Title>
|
|
305
305
|
<mode>Named+OFF</mode>
|
|
306
306
|
<Scene id="1">
|
|
307
|
-
<Name>
|
|
307
|
+
<Name>Scene 1</Name>
|
|
308
308
|
</Scene>
|
|
309
309
|
</Room>
|
|
310
310
|
<Room id="157">
|
|
311
311
|
<Type>Lights</Type>
|
|
312
|
-
<Title>
|
|
312
|
+
<Title>Room 157</Title>
|
|
313
313
|
<Channel id="1">
|
|
314
314
|
<type>switch</type>
|
|
315
|
-
<Name>
|
|
315
|
+
<Name>Channel 1</Name>
|
|
316
316
|
<Levels>FF00FF00000000000000000000000000</Levels>
|
|
317
317
|
</Channel>
|
|
318
318
|
<Channel id="2">
|
|
319
319
|
<type>Slider</type>
|
|
320
|
-
<Name>
|
|
320
|
+
<Name>Channel 2</Name>
|
|
321
321
|
<Levels>FF000000000000000000000000000000</Levels>
|
|
322
322
|
</Channel>
|
|
323
323
|
<Channel id="3">
|
|
324
324
|
<type>switch</type>
|
|
325
|
-
<Name>
|
|
325
|
+
<Name>Channel 3</Name>
|
|
326
326
|
<Levels>0000FFFF000000000000000000000000</Levels>
|
|
327
327
|
</Channel>
|
|
328
328
|
<Channel id="5">
|
|
329
329
|
<type>switch</type>
|
|
330
|
-
<Name>
|
|
330
|
+
<Name>Channel 5</Name>
|
|
331
331
|
<Levels>FFFFFF00000000000000000000000000</Levels>
|
|
332
332
|
</Channel>
|
|
333
333
|
<Channel id="7">
|
|
334
334
|
<type>switch</type>
|
|
335
|
-
<Name>
|
|
335
|
+
<Name>Channel 7</Name>
|
|
336
336
|
<Levels>FFBFFF00000000000000000000000000</Levels>
|
|
337
337
|
</Channel>
|
|
338
338
|
</Room>
|
|
339
339
|
<Room id="158">
|
|
340
340
|
<Type>Lights</Type>
|
|
341
|
-
<Title>
|
|
341
|
+
<Title>Room 158</Title>
|
|
342
342
|
<Channel id="1">
|
|
343
343
|
<type>switch</type>
|
|
344
|
-
<Name>
|
|
344
|
+
<Name>Channel 1</Name>
|
|
345
345
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
346
346
|
</Channel>
|
|
347
347
|
<Channel id="2">
|
|
348
348
|
<type>switch</type>
|
|
349
|
-
<Name>
|
|
349
|
+
<Name>Channel 2</Name>
|
|
350
350
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
351
351
|
</Channel>
|
|
352
352
|
<Channel id="3">
|
|
353
353
|
<type>switch</type>
|
|
354
|
-
<Name>
|
|
354
|
+
<Name>Channel 3</Name>
|
|
355
355
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
356
356
|
</Channel>
|
|
357
357
|
<Channel id="5">
|
|
358
358
|
<type>switch</type>
|
|
359
|
-
<Name>
|
|
359
|
+
<Name>Channel 5</Name>
|
|
360
360
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
361
361
|
</Channel>
|
|
362
362
|
</Room>
|
|
363
363
|
<Room id="159">
|
|
364
364
|
<Type>Lights</Type>
|
|
365
|
-
<Title>
|
|
365
|
+
<Title>Room 159</Title>
|
|
366
366
|
<Channel id="1">
|
|
367
367
|
<type>switch</type>
|
|
368
|
-
<Name>
|
|
368
|
+
<Name>Channel 1</Name>
|
|
369
369
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
370
370
|
</Channel>
|
|
371
371
|
<Channel id="2">
|
|
372
372
|
<type>switch</type>
|
|
373
|
-
<Name>
|
|
373
|
+
<Name>Channel 2</Name>
|
|
374
374
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
375
375
|
</Channel>
|
|
376
376
|
<Channel id="3">
|
|
377
377
|
<type>switch</type>
|
|
378
|
-
<Name>
|
|
378
|
+
<Name>Channel 3</Name>
|
|
379
379
|
<Levels>FFBF7F3F000000000000000000000000</Levels>
|
|
380
380
|
</Channel>
|
|
381
381
|
</Room>
|
|
@@ -384,7 +384,7 @@
|
|
|
384
384
|
<Title>Fans</Title>
|
|
385
385
|
<mode>Named+OFF</mode>
|
|
386
386
|
<Scene id="1">
|
|
387
|
-
<Name>
|
|
387
|
+
<Name>Scene 1</Name>
|
|
388
388
|
</Scene>
|
|
389
389
|
<Channel id="1">
|
|
390
390
|
<type>switch</type>
|
|
@@ -304,7 +304,11 @@ def test_reconcile_does_not_overwrite_a_command_echo():
|
|
|
304
304
|
def test_reconcile_leaves_rooms_absent_from_the_cache_alone():
|
|
305
305
|
"""Absence from the scene cache means fade-controlled, not off."""
|
|
306
306
|
tracked = empty_snapshot().apply(ChannelStatusMessage(9, 1, 200))
|
|
307
|
-
|
|
307
|
+
# The real level table, as get_state_snapshot() passes it: room 9 is then
|
|
308
|
+
# listed in ``fresh`` with scene=None rather than missing from it.
|
|
309
|
+
fresh = BridgeStateSnapshot.from_caches(SceneCache({6: 1}), LEVEL_TABLE)
|
|
310
|
+
assert fresh.room_scene(9) is None
|
|
311
|
+
assert 9 in fresh.rooms
|
|
308
312
|
|
|
309
313
|
reconciled = tracked.reconcile(fresh)
|
|
310
314
|
|
|
@@ -312,6 +316,74 @@ def test_reconcile_leaves_rooms_absent_from_the_cache_alone():
|
|
|
312
316
|
assert reconciled.channel_state(9, 1).source is StateSource.LEVEL_BROADCAST
|
|
313
317
|
|
|
314
318
|
|
|
319
|
+
def test_reconcile_survives_a_bridge_restart_refilling_its_cache():
|
|
320
|
+
"""A restarted bridge's cache lists only the rooms used since the restart.
|
|
321
|
+
|
|
322
|
+
Observed 2026-11-03: the bridge's cache came back empty, then partly
|
|
323
|
+
refilled by occupancy-sensor rooms within minutes. That read is not empty,
|
|
324
|
+
so it passes an emptiness check, yet every other room appears with
|
|
325
|
+
scene=None. None of them changed; none of them may be wiped to unknown.
|
|
326
|
+
"""
|
|
327
|
+
tracked = (
|
|
328
|
+
empty_snapshot()
|
|
329
|
+
.apply(SceneStatusMessage(6, 0, 2))
|
|
330
|
+
.apply(ChannelStatusMessage(6, 1, 129))
|
|
331
|
+
.apply(SceneStatusMessage(7, 0, 0))
|
|
332
|
+
)
|
|
333
|
+
refilling = BridgeStateSnapshot.from_caches(SceneCache({9: 1}), LEVEL_TABLE)
|
|
334
|
+
|
|
335
|
+
reconciled = tracked.reconcile(refilling)
|
|
336
|
+
|
|
337
|
+
assert reconciled.room_scene(6) == 2
|
|
338
|
+
assert reconciled.channel_level(6, 1) == 129
|
|
339
|
+
assert reconciled.channel_state(6, 1).source is StateSource.LEVEL_BROADCAST
|
|
340
|
+
assert reconciled.channel_level(6, 2) == 192
|
|
341
|
+
assert reconciled.room_scene(7) == 0
|
|
342
|
+
assert reconciled.channel_level(7, 2) == 0
|
|
343
|
+
# The room that is in the cache is still reconciled normally.
|
|
344
|
+
assert reconciled.room_scene(9) == 1
|
|
345
|
+
assert reconciled.channel_level(9, 1) == 255
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_reconcile_keeps_the_fade_guard_while_the_room_is_absent():
|
|
349
|
+
"""The bridge drops a faded room from its cache; that must not reset the guard.
|
|
350
|
+
|
|
351
|
+
Otherwise the next read that still carries the pre-fade scene would be
|
|
352
|
+
adopted as a "missed scene change" and resurrect levels the fade invalidated.
|
|
353
|
+
"""
|
|
354
|
+
tracked = (
|
|
355
|
+
empty_snapshot()
|
|
356
|
+
.apply(SceneStatusMessage(6, 0, 1))
|
|
357
|
+
.apply(FadeMessage(6, 0, direction=FadeDirection.UP, command=CommandType.FADE))
|
|
358
|
+
)
|
|
359
|
+
absent = BridgeStateSnapshot.from_caches(SceneCache({7: 1}), LEVEL_TABLE)
|
|
360
|
+
|
|
361
|
+
after_absent = tracked.reconcile(absent)
|
|
362
|
+
|
|
363
|
+
assert after_absent.rooms[6].is_faded
|
|
364
|
+
assert after_absent.rooms[6].faded_from == 1
|
|
365
|
+
|
|
366
|
+
stale = BridgeStateSnapshot.from_caches(SceneCache({6: 1}), LEVEL_TABLE)
|
|
367
|
+
reconciled = after_absent.reconcile(stale)
|
|
368
|
+
|
|
369
|
+
assert reconciled.room_scene(6) is None
|
|
370
|
+
assert reconciled.channel_level(6, 1) is None
|
|
371
|
+
assert reconciled.channel_state(6, 1).source is StateSource.UNKNOWN_AFTER_FADE
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_reconcile_adopts_absent_rooms_it_has_never_seen_as_unknown():
|
|
375
|
+
"""A room nothing has told us about is added -- as unknown, never off."""
|
|
376
|
+
tracked = empty_snapshot().apply(SceneStatusMessage(6, 0, 1))
|
|
377
|
+
fresh = BridgeStateSnapshot.from_caches(SceneCache({6: 1}), LEVEL_TABLE)
|
|
378
|
+
|
|
379
|
+
reconciled = tracked.reconcile(fresh)
|
|
380
|
+
|
|
381
|
+
assert 9 in reconciled.rooms
|
|
382
|
+
assert reconciled.room_scene(9) is None
|
|
383
|
+
assert reconciled.channel_level(9, 1) is None
|
|
384
|
+
assert reconciled.channel_state(9, 1).source is StateSource.UNKNOWN_AFTER_FADE
|
|
385
|
+
|
|
386
|
+
|
|
315
387
|
def test_reconcile_adopts_rooms_we_have_never_seen():
|
|
316
388
|
tracked = empty_snapshot()
|
|
317
389
|
fresh = BridgeStateSnapshot.from_caches(SceneCache({6: 3}), LEVEL_TABLE)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.0"
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_rako_2025-0.5.0 → python_rako_2025-0.5.2}/tests_integration/test_bridge_info_live.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|