python-mobius 0.10.0__tar.gz → 0.10.1__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_mobius-0.10.0 → python_mobius-0.10.1}/CHANGELOG.md +6 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/PKG-INFO +1 -1
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/09-thread-coap-relay.md +15 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/17-configuration-checksums.md +4 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/pyproject.toml +1 -1
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/__init__.py +1 -1
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/device.py +52 -54
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_configuration_cache.py +42 -12
- {python_mobius-0.10.0 → python_mobius-0.10.1}/.forgejo/scripts/extract_changelog_section.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/.forgejo/workflows/release.yml +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/.gitignore +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/LICENSE +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/README.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/00-overview.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/01-ble-transport.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/02-framing-and-crc.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/03-attributes-and-opcodes.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/04-device-identity.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/05-scenes.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/06-light-schedule.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/07-pump-schedule.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/08-manufacturer-data.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/10-known-gaps-and-open-questions.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/11-device-status-attributes.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/12-device-identity-and-address-stability.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/13-attribute-dump.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/14-attribute-reference.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/15-mob-template-format.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/documentation/16-python-library.md +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/checksums.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/cli.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/coap.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/constants.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/crc.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/device_status.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/discovery.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/dump.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/frame.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/manufacturer.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/mesh_address.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/mob.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/modifiers.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/power.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/pump_status.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/relay.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/scenes.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/schedule.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/src/mobius/timezone.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_attributes_batch.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_battery_settings.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_bluetooth_only.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_checksums.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_cli_group_targets.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_cli_mob_file.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_cli_schedule_file.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_coap.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_coap_indication_handling.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_connection_cleanup.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_device_control.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_device_status.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_device_summary.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_device_time.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_discovery_company_ids.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_dump.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_extra_physical_values.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_fan_thresholds.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_firmware_versions.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_frame.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_frame_sniffer.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_full_poll_batch.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_group_bitmap.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_hyperdrive_device.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_insolation_detection.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_light_intensity_branching.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_light_poll_batch.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_light_schedule.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_manufacturer.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_manufacturer_lookup.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_mesh_address.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_mesh_discovery.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_mesh_discovery_direct_connect.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_metadata_batch.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_mob.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_modifiers.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_networked_thread_devices.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_new_diagnostic_attributes.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_power.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_param_ranges.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_reverse.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_schedule.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_status.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_supported_modes.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_pump_telemetry.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_relay.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_scenes.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_schedule_file_format.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_schedule_intensity_scalar.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_serial_decoding.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_serial_identity.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_shared_helpers.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_supported_attributes.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_time_zone.py +0 -0
- {python_mobius-0.10.0 → python_mobius-0.10.1}/tests/test_write_size.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.1
|
|
4
|
+
|
|
5
|
+
- Fixed lights not being read through a gateway: the poll's reply was too
|
|
6
|
+
large to be relayed. The schedule and scenes are now read in requests
|
|
7
|
+
of their own, as the app does.
|
|
8
|
+
|
|
3
9
|
## 0.10.0
|
|
4
10
|
|
|
5
11
|
- Added `set_time_zone()`; the batched polls now include the device's time
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: Reverse-engineered Python client for the Mobius BLE protocol (EcoTech Marine VorTech/Radion, AquaIllumination, Neptune Systems, NYOS)
|
|
5
5
|
Project-URL: Homepage, https://code.r3pek.org/r3pek/python-mobius
|
|
6
6
|
Project-URL: Documentation, https://code.r3pek.org/r3pek/python-mobius/src/branch/main/documentation
|
|
@@ -156,6 +156,21 @@ A gateway can lose its route to a single target while everything else
|
|
|
156
156
|
keeps working. The protocol has no command to rebuild the mesh; switching
|
|
157
157
|
to another device as gateway is the practical recovery.
|
|
158
158
|
|
|
159
|
+
## Reply size
|
|
160
|
+
|
|
161
|
+
A relayed reply that is too large never arrives: the request times out
|
|
162
|
+
as if the target were unreachable, while smaller requests to the same
|
|
163
|
+
target keep working. Seen with a Radion XR15 G6 relayed through a VorTech
|
|
164
|
+
MP40: a poll batch whose reply was about 2 KB (schedule, scenes and
|
|
165
|
+
metadata together) always timed out, the same data read in three requests
|
|
166
|
+
arrived. The limit is somewhere above 1.1 KB (a 25-point light schedule
|
|
167
|
+
alone, about 1.05 KB of data, arrives).
|
|
168
|
+
|
|
169
|
+
The app reads the schedule (and the Insolation schedule and driver
|
|
170
|
+
information) in a request of its own, and everything else together; the
|
|
171
|
+
Insolation table even takes 8 requests. `get_full_poll_batch()` does the
|
|
172
|
+
same with the schedule and `ConfiguredScenes`.
|
|
173
|
+
|
|
159
174
|
## Writes and the mesh
|
|
160
175
|
|
|
161
176
|
A relayed Set reaches only its target. A Set sent with the reserved
|
|
@@ -62,6 +62,10 @@ To read scenes and a schedule only when they changed:
|
|
|
62
62
|
kept one, the kept data is current.
|
|
63
63
|
3. If it differs, or can't be read, read the data and its checksum again.
|
|
64
64
|
|
|
65
|
+
Read each data attribute in a request of its own: a light's schedule or
|
|
66
|
+
scenes are close to the largest reply a relay carries
|
|
67
|
+
([09](./09-thread-coap-relay.md#reply-size)).
|
|
68
|
+
|
|
65
69
|
The key is the device's own value, so this works without computing
|
|
66
70
|
anything and also catches changes made by other clients (the app, or
|
|
67
71
|
this client's own writes). Include `SupportedAttributesCRC` in the poll: a
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-mobius"
|
|
7
|
-
version = "0.10.
|
|
7
|
+
version = "0.10.1"
|
|
8
8
|
description = "Reverse-engineered Python client for the Mobius BLE protocol (EcoTech Marine VorTech/Radion, AquaIllumination, Neptune Systems, NYOS)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "GPL-2.0-only" }
|
|
@@ -169,26 +169,18 @@ def _schedule_checksum_attr(which: int) -> C2Attribute:
|
|
|
169
169
|
return C2Attribute.Schedule1Checksum if which == 1 else C2Attribute.Schedule2Checksum
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
def
|
|
172
|
+
def _configuration_checksum_requests(
|
|
173
173
|
configuration: tuple, cache: Optional[ConfigurationCache], supported_attribute_ids: set,
|
|
174
|
-
) ->
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if checksum_usable and data_attr in cache.entries:
|
|
185
|
-
requests.append((checksum_attr, 0, 1))
|
|
186
|
-
continue
|
|
187
|
-
requests.append((data_attr, 0, ALL_ELEMENTS))
|
|
188
|
-
data_read.add(data_attr)
|
|
189
|
-
if checksum_usable:
|
|
190
|
-
requests.append((checksum_attr, 0, 1))
|
|
191
|
-
return requests, data_read
|
|
174
|
+
) -> list:
|
|
175
|
+
"""The checksum request of each (data attribute, checksum attribute) of
|
|
176
|
+
`configuration` that the cache can use. The data itself is never part
|
|
177
|
+
of the poll batch (see _read_configuration())."""
|
|
178
|
+
if cache is None:
|
|
179
|
+
return []
|
|
180
|
+
return [
|
|
181
|
+
(checksum_attr, 0, 1) for _data_attr, checksum_attr in configuration
|
|
182
|
+
if checksum_attr in supported_attribute_ids
|
|
183
|
+
]
|
|
192
184
|
|
|
193
185
|
|
|
194
186
|
def _apply_static_cache(cache: ConfigurationCache, by_attr_id: dict, static_read: bool) -> None:
|
|
@@ -2506,10 +2498,12 @@ class MobiusDevice:
|
|
|
2506
2498
|
get_device_info(), get_metadata_batch() and
|
|
2507
2499
|
get_light_poll_batch() or get_pump_schedule()/get_pump_telemetry().
|
|
2508
2500
|
|
|
2509
|
-
|
|
2510
|
-
the
|
|
2511
|
-
|
|
2512
|
-
|
|
2501
|
+
The schedule and ConfiguredScenes are read in requests of their
|
|
2502
|
+
own after the batch, as the app does: combined with the rest, a
|
|
2503
|
+
light's reply is too large to be relayed. `cache` (one per device,
|
|
2504
|
+
kept by the caller): they are then read only when their checksum,
|
|
2505
|
+
part of the batch, changed since the previous poll (see
|
|
2506
|
+
ConfigurationCache). With its static_refresh_interval
|
|
2513
2507
|
set, the STATIC_POLL_ATTRIBUTES are also read only that often.
|
|
2514
2508
|
Used by the batched path only.
|
|
2515
2509
|
"""
|
|
@@ -2537,7 +2531,7 @@ class MobiusDevice:
|
|
|
2537
2531
|
(schedule_attr, _schedule_checksum_attr(which)),
|
|
2538
2532
|
(C2Attribute.ConfiguredScenes, C2Attribute.ConfiguredScenesChecksum),
|
|
2539
2533
|
)
|
|
2540
|
-
configuration_requests
|
|
2534
|
+
configuration_requests = _configuration_checksum_requests(configuration, cache, supported_attribute_ids)
|
|
2541
2535
|
if cache is not None:
|
|
2542
2536
|
configuration_requests.append((C2Attribute.SupportedAttributesCRC, 0, 1))
|
|
2543
2537
|
requests = _DEVICE_INFO_REQUESTS + _METADATA_REQUESTS + configuration_requests + [
|
|
@@ -2558,9 +2552,7 @@ class MobiusDevice:
|
|
|
2558
2552
|
)
|
|
2559
2553
|
if attribute_table_changed:
|
|
2560
2554
|
cache.invalidate_static()
|
|
2561
|
-
|
|
2562
|
-
by_attr_id, cache, configuration, data_read, supported_attribute_ids,
|
|
2563
|
-
)
|
|
2555
|
+
data_read = await self._read_configuration(by_attr_id, cache, configuration, supported_attribute_ids)
|
|
2564
2556
|
|
|
2565
2557
|
def first(attr_id, index=0):
|
|
2566
2558
|
return _first_value(by_attr_id, attr_id, index)
|
|
@@ -2621,38 +2613,44 @@ class MobiusDevice:
|
|
|
2621
2613
|
static_read=static_read,
|
|
2622
2614
|
)
|
|
2623
2615
|
|
|
2624
|
-
async def
|
|
2625
|
-
self, by_attr_id: dict, cache: ConfigurationCache,
|
|
2626
|
-
configuration: tuple,
|
|
2616
|
+
async def _read_configuration(
|
|
2617
|
+
self, by_attr_id: dict, cache: Optional[ConfigurationCache],
|
|
2618
|
+
configuration: tuple, supported_attribute_ids: set,
|
|
2627
2619
|
) -> set:
|
|
2628
2620
|
"""
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2621
|
+
Fills each data attribute of `configuration` (the schedule, the
|
|
2622
|
+
scenes) into `by_attr_id`: from `cache` when the checksum in the
|
|
2623
|
+
poll batch matches the cached one, else with a request of its own
|
|
2624
|
+
(plus its checksum, stored in the cache with it). Returns the
|
|
2625
|
+
attributes read.
|
|
2626
|
+
|
|
2627
|
+
Each gets its own request, as in the app: a light's schedule or
|
|
2628
|
+
scenes alone are close to the largest reply a relay carries, and
|
|
2629
|
+
together with the rest of the poll a relayed reply is too large
|
|
2630
|
+
and never arrives.
|
|
2632
2631
|
"""
|
|
2633
|
-
|
|
2632
|
+
read = set()
|
|
2634
2633
|
for data_attr, checksum_attr in configuration:
|
|
2635
|
-
if data_attr not in supported_attribute_ids
|
|
2636
|
-
continue
|
|
2637
|
-
reported = _unpack(_first_value(by_attr_id, checksum_attr), "<H")
|
|
2638
|
-
if data_attr in data_read:
|
|
2639
|
-
_store_configuration(cache, data_attr, reported, by_attr_id)
|
|
2634
|
+
if data_attr not in supported_attribute_ids:
|
|
2640
2635
|
continue
|
|
2641
|
-
|
|
2642
|
-
if
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2636
|
+
checksum_usable = cache is not None and checksum_attr in supported_attribute_ids
|
|
2637
|
+
if checksum_usable:
|
|
2638
|
+
reported = _unpack(_first_value(by_attr_id, checksum_attr), "<H")
|
|
2639
|
+
cached = cache.entries.get(data_attr)
|
|
2640
|
+
if cached is not None and reported is not None and reported == cached[0]:
|
|
2641
|
+
by_attr_id[data_attr] = cached[1]
|
|
2642
|
+
continue
|
|
2643
|
+
requests = [(data_attr, 0, ALL_ELEMENTS)]
|
|
2644
|
+
if checksum_usable:
|
|
2645
|
+
requests.append((checksum_attr, 0, 1))
|
|
2646
|
+
response = await self._read_supported_batch(requests, supported_attribute_ids)
|
|
2647
|
+
by_attr_id[data_attr] = response.get(int(data_attr), [])
|
|
2648
|
+
if checksum_usable:
|
|
2649
|
+
_store_configuration(
|
|
2650
|
+
cache, data_attr, _unpack(_first_value(response, checksum_attr), "<H"), response,
|
|
2651
|
+
)
|
|
2652
|
+
read.add(data_attr)
|
|
2653
|
+
return read
|
|
2656
2654
|
|
|
2657
2655
|
async def _get_full_poll_via_individual_reads(
|
|
2658
2656
|
self, primitive: PrimitiveType, model: Optional[Model],
|
|
@@ -60,6 +60,14 @@ def _requested(pump, attr, batch=-1):
|
|
|
60
60
|
return int(attr) in pump.batches[batch]
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def _requested_in_any(pump, attr, batches):
|
|
64
|
+
return any(int(attr) in batch for batch in batches)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
SCHEDULE_ALONE = [int(C2Attribute.Schedule1), int(C2Attribute.Schedule1Checksum)]
|
|
68
|
+
SCENES_ALONE = [int(C2Attribute.ConfiguredScenes), int(C2Attribute.ConfiguredScenesChecksum)]
|
|
69
|
+
|
|
70
|
+
|
|
63
71
|
@pytest.mark.asyncio
|
|
64
72
|
async def test_first_poll_reads_the_data_with_its_checksums():
|
|
65
73
|
pump, cache = FakePump(), ConfigurationCache()
|
|
@@ -67,7 +75,10 @@ async def test_first_poll_reads_the_data_with_its_checksums():
|
|
|
67
75
|
result = await pump.poll(cache)
|
|
68
76
|
|
|
69
77
|
assert result.configuration_read == ("Schedule1", "ConfiguredScenes")
|
|
70
|
-
|
|
78
|
+
# The poll batch, then the schedule and the scenes each on their own.
|
|
79
|
+
assert pump.batches[1:] == [SCHEDULE_ALONE, SCENES_ALONE]
|
|
80
|
+
assert not _requested(pump, C2Attribute.Schedule1, batch=0)
|
|
81
|
+
assert not _requested(pump, C2Attribute.ConfiguredScenes, batch=0)
|
|
71
82
|
assert cache.entries[int(C2Attribute.Schedule1)][0] == 0x1111
|
|
72
83
|
assert cache.entries[int(C2Attribute.ConfiguredScenes)][0] == 0x2222
|
|
73
84
|
assert result.pump_schedule_points[0].pump.params[PumpParam.MaxSpeed] == 500
|
|
@@ -80,7 +91,7 @@ async def test_unchanged_checksums_skip_the_data():
|
|
|
80
91
|
|
|
81
92
|
result = await pump.poll(cache)
|
|
82
93
|
|
|
83
|
-
assert len(pump.batches) ==
|
|
94
|
+
assert len(pump.batches) == 4 # one request for this poll
|
|
84
95
|
assert not _requested(pump, C2Attribute.Schedule1)
|
|
85
96
|
assert not _requested(pump, C2Attribute.ConfiguredScenes)
|
|
86
97
|
assert _requested(pump, C2Attribute.Schedule1Checksum)
|
|
@@ -90,7 +101,7 @@ async def test_unchanged_checksums_skip_the_data():
|
|
|
90
101
|
|
|
91
102
|
|
|
92
103
|
@pytest.mark.asyncio
|
|
93
|
-
async def
|
|
104
|
+
async def test_changed_checksum_reads_that_data_in_a_request_of_its_own():
|
|
94
105
|
pump, cache = FakePump(), ConfigurationCache()
|
|
95
106
|
await pump.poll(cache)
|
|
96
107
|
pump.set(C2Attribute.Schedule1, [_point(700)])
|
|
@@ -98,8 +109,9 @@ async def test_changed_checksum_reads_that_data_in_a_second_request():
|
|
|
98
109
|
|
|
99
110
|
result = await pump.poll(cache)
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
assert pump.batches
|
|
112
|
+
# This poll: the batch, then the schedule alone; the scenes are cached.
|
|
113
|
+
assert len(pump.batches) == 5
|
|
114
|
+
assert pump.batches[-1] == SCHEDULE_ALONE
|
|
103
115
|
assert result.configuration_read == ("Schedule1",)
|
|
104
116
|
assert result.pump_schedule_points[0].pump.params[PumpParam.MaxSpeed] == 700
|
|
105
117
|
assert cache.entries[int(C2Attribute.Schedule1)][0] == 0x3333
|
|
@@ -124,8 +136,7 @@ async def test_without_checksum_support_the_data_is_read_every_poll():
|
|
|
124
136
|
|
|
125
137
|
result = await pump.poll(cache)
|
|
126
138
|
|
|
127
|
-
assert
|
|
128
|
-
assert _requested(pump, C2Attribute.ConfiguredScenes)
|
|
139
|
+
assert pump.batches[-2:] == [[int(C2Attribute.Schedule1)], [int(C2Attribute.ConfiguredScenes)]]
|
|
129
140
|
assert result.configuration_read == ("Schedule1", "ConfiguredScenes")
|
|
130
141
|
assert cache.entries == {}
|
|
131
142
|
|
|
@@ -141,13 +152,13 @@ async def test_attribute_table_change_is_reported_once():
|
|
|
141
152
|
|
|
142
153
|
|
|
143
154
|
@pytest.mark.asyncio
|
|
144
|
-
async def
|
|
155
|
+
async def test_without_a_cache_the_data_is_read_in_its_own_requests():
|
|
145
156
|
pump = FakePump()
|
|
146
157
|
await pump.poll(None)
|
|
147
158
|
|
|
148
|
-
assert
|
|
149
|
-
assert not
|
|
150
|
-
assert not
|
|
159
|
+
assert pump.batches[1:] == [[int(C2Attribute.Schedule1)], [int(C2Attribute.ConfiguredScenes)]]
|
|
160
|
+
assert not _requested_in_any(pump, C2Attribute.Schedule1Checksum, pump.batches)
|
|
161
|
+
assert not _requested_in_any(pump, C2Attribute.SupportedAttributesCRC, pump.batches)
|
|
151
162
|
|
|
152
163
|
|
|
153
164
|
@pytest.mark.asyncio
|
|
@@ -256,4 +267,23 @@ async def test_pump_operation_state_is_part_of_the_batch():
|
|
|
256
267
|
result = await pump.poll(cache)
|
|
257
268
|
|
|
258
269
|
assert result.operation_state == OperationState(2)
|
|
259
|
-
assert
|
|
270
|
+
assert _requested(pump, C2Attribute.OperationState, batch=0)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.asyncio
|
|
274
|
+
async def test_poll_batch_stays_small_with_a_large_schedule_and_scenes():
|
|
275
|
+
"""A relay only carries replies up to a limit: the schedule and scenes
|
|
276
|
+
must never share a request with each other or with the rest."""
|
|
277
|
+
light, cache = FakePump(), ConfigurationCache()
|
|
278
|
+
light.set(C2Attribute.Schedule1, [_point(500)] * 25)
|
|
279
|
+
light.set(C2Attribute.ConfiguredScenes, [SCENE] * 10)
|
|
280
|
+
|
|
281
|
+
await light.device.get_full_poll_batch(
|
|
282
|
+
primitive=PrimitiveType.VisualV1, supported_attribute_ids=light.supported, cache=cache, minute_of_day=0,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
big = {int(C2Attribute.Schedule1), int(C2Attribute.ConfiguredScenes)}
|
|
286
|
+
for batch in light.batches:
|
|
287
|
+
assert len(big & set(batch)) <= 1
|
|
288
|
+
if big & set(batch):
|
|
289
|
+
assert len(batch) <= 2 # the data and its checksum only
|
|
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_mobius-0.10.0 → python_mobius-0.10.1}/documentation/10-known-gaps-and-open-questions.md
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
|
|
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
|
|
File without changes
|
|
File without changes
|