python-mobius 0.10.0__py3-none-any.whl → 0.10.2__py3-none-any.whl
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.
- mobius/__init__.py +1 -1
- mobius/device.py +90 -63
- {python_mobius-0.10.0.dist-info → python_mobius-0.10.2.dist-info}/METADATA +1 -1
- {python_mobius-0.10.0.dist-info → python_mobius-0.10.2.dist-info}/RECORD +7 -7
- {python_mobius-0.10.0.dist-info → python_mobius-0.10.2.dist-info}/WHEEL +0 -0
- {python_mobius-0.10.0.dist-info → python_mobius-0.10.2.dist-info}/entry_points.txt +0 -0
- {python_mobius-0.10.0.dist-info → python_mobius-0.10.2.dist-info}/licenses/LICENSE +0 -0
mobius/__init__.py
CHANGED
mobius/device.py
CHANGED
|
@@ -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:
|
|
@@ -1850,7 +1842,7 @@ class MobiusDevice:
|
|
|
1850
1842
|
return None
|
|
1851
1843
|
return raw
|
|
1852
1844
|
|
|
1853
|
-
async def discover_mesh_peers(self) -> list[MeshPeer]:
|
|
1845
|
+
async def discover_mesh_peers(self, raise_errors: bool = False) -> list[MeshPeer]:
|
|
1854
1846
|
"""
|
|
1855
1847
|
Mesh peers from the ShortAddressArray/SerialNumberArray/
|
|
1856
1848
|
DeviceModelArray (3700/3701/3702) parallel arrays, with addresses
|
|
@@ -1860,10 +1852,12 @@ class MobiusDevice:
|
|
|
1860
1852
|
UnsupportedAttribute and this returns []. Whether the connected
|
|
1861
1853
|
device lists itself is not known. Empty slots (serial
|
|
1862
1854
|
"00000000000000") and short entries are skipped. Returns [] on any
|
|
1863
|
-
failure.
|
|
1855
|
+
failure, or raises it with raise_errors=True.
|
|
1864
1856
|
"""
|
|
1865
1857
|
own_address = await self.get_own_mesh_address()
|
|
1866
1858
|
if own_address is None:
|
|
1859
|
+
if raise_errors:
|
|
1860
|
+
raise IOError("own mesh address (MeshLocalAddresses) not readable")
|
|
1867
1861
|
return []
|
|
1868
1862
|
prefix = mesh_local_prefix_from_own_address(own_address)
|
|
1869
1863
|
|
|
@@ -1872,6 +1866,8 @@ class MobiusDevice:
|
|
|
1872
1866
|
models_raw = await self.get_attribute(C2Attribute.DeviceModelArray, index=0, count=ALL_ELEMENTS)
|
|
1873
1867
|
shorts_raw = await self.get_attribute(C2Attribute.ShortAddressArray, index=0, count=ALL_ELEMENTS)
|
|
1874
1868
|
except Exception:
|
|
1869
|
+
if raise_errors:
|
|
1870
|
+
raise
|
|
1875
1871
|
return []
|
|
1876
1872
|
|
|
1877
1873
|
peers: list[MeshPeer] = []
|
|
@@ -1890,7 +1886,7 @@ class MobiusDevice:
|
|
|
1890
1886
|
))
|
|
1891
1887
|
return peers
|
|
1892
1888
|
|
|
1893
|
-
async def discover_networked_thread_devices(self) -> list[MeshPeer]:
|
|
1889
|
+
async def discover_networked_thread_devices(self, raise_errors: bool = False) -> list[MeshPeer]:
|
|
1894
1890
|
"""
|
|
1895
1891
|
Every device on this device's Thread mesh, including itself, from
|
|
1896
1892
|
MLPrefix (2128) and NetworkedThreadDevices (1001). Supported more
|
|
@@ -1904,7 +1900,8 @@ class MobiusDevice:
|
|
|
1904
1900
|
12-13 model id, i16 LE
|
|
1905
1901
|
14-27 serial, ASCII
|
|
1906
1902
|
Empty slots are all zeros and are skipped, as are entries of any
|
|
1907
|
-
other length. Returns [] on any failure
|
|
1903
|
+
other length. Returns [] on any failure, or raises it with
|
|
1904
|
+
raise_errors=True.
|
|
1908
1905
|
"""
|
|
1909
1906
|
try:
|
|
1910
1907
|
mlprefix_raw = await self.get_attribute(C2Attribute.MLPrefix)
|
|
@@ -1912,8 +1909,15 @@ class MobiusDevice:
|
|
|
1912
1909
|
C2Attribute.NetworkedThreadDevices, index=0, count=ALL_ELEMENTS,
|
|
1913
1910
|
)
|
|
1914
1911
|
except Exception:
|
|
1912
|
+
if raise_errors:
|
|
1913
|
+
raise
|
|
1915
1914
|
return []
|
|
1916
1915
|
if not mlprefix_raw or len(mlprefix_raw[0]) != 24:
|
|
1916
|
+
if raise_errors:
|
|
1917
|
+
raise IOError(
|
|
1918
|
+
f"unexpected MLPrefix: {mlprefix_raw[0].hex() if mlprefix_raw else 'empty'} "
|
|
1919
|
+
f"(expected 24 bytes)"
|
|
1920
|
+
)
|
|
1917
1921
|
return []
|
|
1918
1922
|
prefix = mesh_local_prefix_from_mlprefix(mlprefix_raw[0])
|
|
1919
1923
|
|
|
@@ -1936,18 +1940,35 @@ class MobiusDevice:
|
|
|
1936
1940
|
))
|
|
1937
1941
|
return peers
|
|
1938
1942
|
|
|
1939
|
-
async def discover_mesh_peers_auto(self) -> list[MeshPeer]:
|
|
1943
|
+
async def discover_mesh_peers_auto(self, raise_errors: bool = False) -> list[MeshPeer]:
|
|
1940
1944
|
"""
|
|
1941
1945
|
discover_networked_thread_devices(), falling back to
|
|
1942
1946
|
discover_mesh_peers() when it finds nothing. The recommended entry
|
|
1943
1947
|
point. mobius.discovery.discover_mesh_peers_via_direct_connect() is
|
|
1944
1948
|
the remaining fallback, but it needs scan results and has to be
|
|
1945
1949
|
called explicitly.
|
|
1950
|
+
|
|
1951
|
+
raise_errors=True: a failure raises instead of looking like an
|
|
1952
|
+
empty mesh. The fallback is then only tried when
|
|
1953
|
+
NetworkedThreadDevices can't be read (an MXM hub), and the error
|
|
1954
|
+
raised names both attempts.
|
|
1946
1955
|
"""
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1956
|
+
if not raise_errors:
|
|
1957
|
+
peers = await self.discover_networked_thread_devices()
|
|
1958
|
+
if peers:
|
|
1959
|
+
return peers
|
|
1960
|
+
return await self.discover_mesh_peers()
|
|
1961
|
+
|
|
1962
|
+
try:
|
|
1963
|
+
return await self.discover_networked_thread_devices(raise_errors=True)
|
|
1964
|
+
except Exception as networked_error:
|
|
1965
|
+
try:
|
|
1966
|
+
return await self.discover_mesh_peers(raise_errors=True)
|
|
1967
|
+
except Exception as arrays_error:
|
|
1968
|
+
raise IOError(
|
|
1969
|
+
f"NetworkedThreadDevices: {networked_error or type(networked_error).__name__}; "
|
|
1970
|
+
f"peer arrays: {arrays_error or type(arrays_error).__name__}"
|
|
1971
|
+
) from networked_error
|
|
1951
1972
|
|
|
1952
1973
|
async def get_firmware_versions(self, model: Optional[Model] = None) -> dict:
|
|
1953
1974
|
"""{label: version} for every firmware component (FirmwareVersion,
|
|
@@ -2506,10 +2527,12 @@ class MobiusDevice:
|
|
|
2506
2527
|
get_device_info(), get_metadata_batch() and
|
|
2507
2528
|
get_light_poll_batch() or get_pump_schedule()/get_pump_telemetry().
|
|
2508
2529
|
|
|
2509
|
-
|
|
2510
|
-
the
|
|
2511
|
-
|
|
2512
|
-
|
|
2530
|
+
The schedule and ConfiguredScenes are read in requests of their
|
|
2531
|
+
own after the batch, as the app does: combined with the rest, a
|
|
2532
|
+
light's reply is too large to be relayed. `cache` (one per device,
|
|
2533
|
+
kept by the caller): they are then read only when their checksum,
|
|
2534
|
+
part of the batch, changed since the previous poll (see
|
|
2535
|
+
ConfigurationCache). With its static_refresh_interval
|
|
2513
2536
|
set, the STATIC_POLL_ATTRIBUTES are also read only that often.
|
|
2514
2537
|
Used by the batched path only.
|
|
2515
2538
|
"""
|
|
@@ -2537,7 +2560,7 @@ class MobiusDevice:
|
|
|
2537
2560
|
(schedule_attr, _schedule_checksum_attr(which)),
|
|
2538
2561
|
(C2Attribute.ConfiguredScenes, C2Attribute.ConfiguredScenesChecksum),
|
|
2539
2562
|
)
|
|
2540
|
-
configuration_requests
|
|
2563
|
+
configuration_requests = _configuration_checksum_requests(configuration, cache, supported_attribute_ids)
|
|
2541
2564
|
if cache is not None:
|
|
2542
2565
|
configuration_requests.append((C2Attribute.SupportedAttributesCRC, 0, 1))
|
|
2543
2566
|
requests = _DEVICE_INFO_REQUESTS + _METADATA_REQUESTS + configuration_requests + [
|
|
@@ -2558,9 +2581,7 @@ class MobiusDevice:
|
|
|
2558
2581
|
)
|
|
2559
2582
|
if attribute_table_changed:
|
|
2560
2583
|
cache.invalidate_static()
|
|
2561
|
-
|
|
2562
|
-
by_attr_id, cache, configuration, data_read, supported_attribute_ids,
|
|
2563
|
-
)
|
|
2584
|
+
data_read = await self._read_configuration(by_attr_id, cache, configuration, supported_attribute_ids)
|
|
2564
2585
|
|
|
2565
2586
|
def first(attr_id, index=0):
|
|
2566
2587
|
return _first_value(by_attr_id, attr_id, index)
|
|
@@ -2621,38 +2642,44 @@ class MobiusDevice:
|
|
|
2621
2642
|
static_read=static_read,
|
|
2622
2643
|
)
|
|
2623
2644
|
|
|
2624
|
-
async def
|
|
2625
|
-
self, by_attr_id: dict, cache: ConfigurationCache,
|
|
2626
|
-
configuration: tuple,
|
|
2645
|
+
async def _read_configuration(
|
|
2646
|
+
self, by_attr_id: dict, cache: Optional[ConfigurationCache],
|
|
2647
|
+
configuration: tuple, supported_attribute_ids: set,
|
|
2627
2648
|
) -> set:
|
|
2628
2649
|
"""
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2650
|
+
Fills each data attribute of `configuration` (the schedule, the
|
|
2651
|
+
scenes) into `by_attr_id`: from `cache` when the checksum in the
|
|
2652
|
+
poll batch matches the cached one, else with a request of its own
|
|
2653
|
+
(plus its checksum, stored in the cache with it). Returns the
|
|
2654
|
+
attributes read.
|
|
2655
|
+
|
|
2656
|
+
Each gets its own request, as in the app: a light's schedule or
|
|
2657
|
+
scenes alone are close to the largest reply a relay carries, and
|
|
2658
|
+
together with the rest of the poll a relayed reply is too large
|
|
2659
|
+
and never arrives.
|
|
2632
2660
|
"""
|
|
2633
|
-
|
|
2661
|
+
read = set()
|
|
2634
2662
|
for data_attr, checksum_attr in configuration:
|
|
2635
|
-
if data_attr not in supported_attribute_ids
|
|
2663
|
+
if data_attr not in supported_attribute_ids:
|
|
2636
2664
|
continue
|
|
2637
|
-
|
|
2638
|
-
if
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
return {data_attr for data_attr, _checksum_attr in stale}
|
|
2665
|
+
checksum_usable = cache is not None and checksum_attr in supported_attribute_ids
|
|
2666
|
+
if checksum_usable:
|
|
2667
|
+
reported = _unpack(_first_value(by_attr_id, checksum_attr), "<H")
|
|
2668
|
+
cached = cache.entries.get(data_attr)
|
|
2669
|
+
if cached is not None and reported is not None and reported == cached[0]:
|
|
2670
|
+
by_attr_id[data_attr] = cached[1]
|
|
2671
|
+
continue
|
|
2672
|
+
requests = [(data_attr, 0, ALL_ELEMENTS)]
|
|
2673
|
+
if checksum_usable:
|
|
2674
|
+
requests.append((checksum_attr, 0, 1))
|
|
2675
|
+
response = await self._read_supported_batch(requests, supported_attribute_ids)
|
|
2676
|
+
by_attr_id[data_attr] = response.get(int(data_attr), [])
|
|
2677
|
+
if checksum_usable:
|
|
2678
|
+
_store_configuration(
|
|
2679
|
+
cache, data_attr, _unpack(_first_value(response, checksum_attr), "<H"), response,
|
|
2680
|
+
)
|
|
2681
|
+
read.add(data_attr)
|
|
2682
|
+
return read
|
|
2656
2683
|
|
|
2657
2684
|
async def _get_full_poll_via_individual_reads(
|
|
2658
2685
|
self, primitive: PrimitiveType, model: Optional[Model],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.2
|
|
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
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
mobius/__init__.py,sha256=
|
|
1
|
+
mobius/__init__.py,sha256=ownwnGpQ6cybzgLY8bzjmBG4D7JOQ1bw_m9hMPMdvA8,8555
|
|
2
2
|
mobius/checksums.py,sha256=PB9RqvaZu9AqfT6tU6NI2qbbZXrqaHUnow3Qi_6UFHs,5191
|
|
3
3
|
mobius/cli.py,sha256=tyft0SzbuLgbmzps-Ym1hCzcr6A9D5MsALkg6o4ynBs,57876
|
|
4
4
|
mobius/coap.py,sha256=-XV3kI4q-JoL8r0oLFNnHAdZAc_yUP-aICagKh-qbf4,4862
|
|
5
5
|
mobius/constants.py,sha256=pcG9Su_b81pmMA_GxR41lZz9UM9BjtV0T6nuslMICko,41138
|
|
6
6
|
mobius/crc.py,sha256=LOCHZe3a1UnR5thdpZ4S4SFP8BdmoMEnhDNiNMd0iJI,2508
|
|
7
|
-
mobius/device.py,sha256=
|
|
7
|
+
mobius/device.py,sha256=1awCx8TIzSFHHEnkGMbuItbpQtAt1DINdmSnLJqENWI,133014
|
|
8
8
|
mobius/device_status.py,sha256=XclYijAknrgT8SdSW4XvyBemWHUQqw6UPE4tmwq6Yug,15658
|
|
9
9
|
mobius/discovery.py,sha256=poNeXGekB44cpHOFbGyps9d4mwW_hJptmJODwSMTUSg,7176
|
|
10
10
|
mobius/dump.py,sha256=eQBYoi8J6CxRd2ojgWK5QoB8z416oywLRTBnnh0pLwo,9253
|
|
@@ -19,8 +19,8 @@ mobius/relay.py,sha256=trGkWQSarwURF05gXHeb5jTJ9UvWbQGu8NarEP6sRqE,10107
|
|
|
19
19
|
mobius/scenes.py,sha256=VLfDdTKSv3qTsBt2bJGg8iPtUkfNEmNgu0TTtYT6jJE,4274
|
|
20
20
|
mobius/schedule.py,sha256=si9uJdYOj5vh6owuxxMyMyZRY0-tAmCPt3s6ROAeRww,13243
|
|
21
21
|
mobius/timezone.py,sha256=Q7oPKtCdEYcEHXLx4lt-VPq6KH-MNgQwVnDtIXJV-kQ,2267
|
|
22
|
-
python_mobius-0.10.
|
|
23
|
-
python_mobius-0.10.
|
|
24
|
-
python_mobius-0.10.
|
|
25
|
-
python_mobius-0.10.
|
|
26
|
-
python_mobius-0.10.
|
|
22
|
+
python_mobius-0.10.2.dist-info/METADATA,sha256=O31L0MYu8ryhtqYYdQoXfTE9B23dqI4H2OJrWVH5EZI,5829
|
|
23
|
+
python_mobius-0.10.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
24
|
+
python_mobius-0.10.2.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
|
|
25
|
+
python_mobius-0.10.2.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
|
|
26
|
+
python_mobius-0.10.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|