python-mobius 0.7.1__py3-none-any.whl → 0.7.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 CHANGED
@@ -72,7 +72,7 @@ from .dump import (
72
72
  enrich_attribute_dump,
73
73
  )
74
74
 
75
- __version__ = "0.7.1"
75
+ __version__ = "0.7.2"
76
76
 
77
77
  __all__ = [
78
78
  "__version__",
mobius/device.py CHANGED
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
  import asyncio
13
13
  import struct
14
14
  import time
15
- from dataclasses import dataclass
15
+ from dataclasses import dataclass, field
16
16
  from typing import Optional, Callable
17
17
 
18
18
  from bleak import BleakClient, BleakScanner
@@ -190,6 +190,11 @@ class FullPollResult:
190
190
  caller needing a separate get_pump_schedule() call just to also
191
191
  get schedule_point_count alongside everything else here.
192
192
 
193
+ configured_scenes/current_scene -- everything get_configured_scenes()/
194
+ get_current_scene() themselves cover, for whichever device types
195
+ support scenes at all (an empty list / None if not, matching those
196
+ methods' own behavior exactly).
197
+
193
198
  used_batch mirrors every other *Result/Snapshot's own field of the
194
199
  same name and purpose.
195
200
  """
@@ -198,6 +203,8 @@ class FullPollResult:
198
203
  light_poll: Optional[LightPollResult]
199
204
  pump_telemetry: Optional[dict]
200
205
  pump_schedule_points: Optional[list[PumpSchedulePoint]] = None
206
+ configured_scenes: list[Scene] = field(default_factory=list)
207
+ current_scene: Optional[ActiveScene] = None
201
208
  used_batch: bool = True
202
209
 
203
210
 
@@ -1277,18 +1284,31 @@ class MobiusDevice:
1277
1284
 
1278
1285
  # ---- scenes -----------------------------------------------------------
1279
1286
 
1280
- async def start_scene(self, scene_id: int, duration_seconds: int = 0) -> None:
1287
+ async def start_scene(self, scene_id: int, duration_seconds: int = 0, broadcast: bool = True) -> None:
1281
1288
  """
1282
1289
  Activates a scene by id -- one of the ten SceneID presets, or a
1283
1290
  user-created scene's own device-assigned id (see get_configured_scenes()).
1284
1291
  duration_seconds=0 lets the device use the scene's own configured
1285
1292
  timeout instead.
1293
+
1294
+ broadcast=True (the default) sets the frame's own reserved-byte
1295
+ "group" field to 1 (see make_reserved()), which propagates this
1296
+ write to the rest of the mesh from a single write to just this
1297
+ device -- confirmed against real hardware for set_time_to_now()'s
1298
+ own Epoch write; the same group=1 mechanism is used here, though
1299
+ this hasn't been independently confirmed against real hardware
1300
+ for CurrentScene specifically. group=1 only ever reaches devices
1301
+ on THIS SAME mesh (a Thread network is its own isolated network,
1302
+ identified by its own pan_id) -- it cannot reach a different
1303
+ tank's own, separate mesh, even one within physical radio range.
1304
+ Pass broadcast=False to write to only this one device instead.
1286
1305
  """
1287
1306
  value = struct.pack("<HH", int(scene_id), duration_seconds)
1288
- await self.set_attribute(C2Attribute.CurrentScene, value)
1307
+ reserved = make_reserved(group=1) if broadcast else b"\x00\x00"
1308
+ await self.set_attribute(C2Attribute.CurrentScene, value, reserved=reserved)
1289
1309
 
1290
- async def start_feed_mode(self, duration_seconds: int = 0) -> None:
1291
- await self.start_scene(SceneID.FeedMode, duration_seconds)
1310
+ async def start_feed_mode(self, duration_seconds: int = 0, broadcast: bool = True) -> None:
1311
+ await self.start_scene(SceneID.FeedMode, duration_seconds, broadcast=broadcast)
1292
1312
 
1293
1313
  async def resume_schedule(self) -> None:
1294
1314
  """Cancel whatever scene is running and go back to the normal schedule."""
@@ -3655,6 +3675,8 @@ class MobiusDevice:
3655
3675
  (C2Attribute.FirmwareVersion, 0, 0xFFFF),
3656
3676
  (C2Attribute.HardwareRevision, 0, 0xFFFF),
3657
3677
  (schedule_attr, 0, 0xFFFF),
3678
+ (C2Attribute.ConfiguredScenes, 0, 0xFFFF),
3679
+ (C2Attribute.CurrentScene, 0, 1),
3658
3680
  ]
3659
3681
  if primitive == PrimitiveType.VisualV1:
3660
3682
  intensity_attr = C2Attribute.Schedule1Intensity if which == 1 else C2Attribute.Schedule2Intensity
@@ -3726,10 +3748,16 @@ class MobiusDevice:
3726
3748
  [motor_power_raw] if motor_power_raw else [], flow_range, model, primitive,
3727
3749
  )
3728
3750
 
3751
+ configured_scenes = decode_configured_scenes(_values_for(C2Attribute.ConfiguredScenes), primitive)
3752
+ current_scene_raw = _first_value(C2Attribute.CurrentScene)
3753
+ current_scene = decode_active_scene(current_scene_raw) if current_scene_raw else None
3754
+
3729
3755
  return FullPollResult(
3730
3756
  device_info=device_info, metadata=metadata,
3731
3757
  light_poll=light_poll, pump_telemetry=pump_telemetry,
3732
- pump_schedule_points=pump_schedule_points, used_batch=True,
3758
+ pump_schedule_points=pump_schedule_points,
3759
+ configured_scenes=configured_scenes, current_scene=current_scene,
3760
+ used_batch=True,
3733
3761
  )
3734
3762
 
3735
3763
  async def _get_full_poll_via_individual_reads(
@@ -3752,10 +3780,17 @@ class MobiusDevice:
3752
3780
  else:
3753
3781
  pump_schedule_points = await self.get_pump_schedule(which=which)
3754
3782
  pump_telemetry = await self.get_pump_telemetry(model=model, primitive=primitive)
3783
+ try:
3784
+ configured_scenes = await self.get_configured_scenes(primitive=primitive)
3785
+ except Exception:
3786
+ configured_scenes = []
3787
+ current_scene = await self.get_current_scene()
3755
3788
  return FullPollResult(
3756
3789
  device_info=device_info, metadata=metadata,
3757
3790
  light_poll=light_poll, pump_telemetry=pump_telemetry,
3758
- pump_schedule_points=pump_schedule_points, used_batch=False,
3791
+ pump_schedule_points=pump_schedule_points,
3792
+ configured_scenes=configured_scenes, current_scene=current_scene,
3793
+ used_batch=False,
3759
3794
  )
3760
3795
 
3761
3796
  async def get_current_light_percentages(self, which: int = 1,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-mobius
3
- Version: 0.7.1
3
+ Version: 0.7.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
@@ -108,14 +108,25 @@ mobius-scan --adapter hci0
108
108
  - **Read light schedules**: per-channel intensity at any given time,
109
109
  replicating the app's own client-side interpolation (there's no "current
110
110
  intensity" attribute — lights only expose the programmed curve).
111
+ - **Read every configured scene** (`get_configured_scenes()`) — name,
112
+ timeout, and its own light/pump payload, at whatever slot it lives in,
113
+ plus the currently active one (`get_current_scene()`).
114
+ - **Control scenes**: start feed mode, resume the normal schedule, or any
115
+ other configured scene — one write, broadcast to the whole mesh by
116
+ default (`broadcast=True`). Uses the same mesh-propagation mechanism
117
+ confirmed against real hardware for the clock-sync write below;
118
+ applying it to scene activation specifically hasn't been
119
+ independently verified the same way yet.
120
+ - **Fetch everything one device poll needs in a single round-trip**
121
+ (`get_full_poll_batch()`) — identity, metadata, light/pump state, and
122
+ scene data together, confirmed 2-2.6x faster on real hardware than
123
+ reading each piece separately.
111
124
  - **Read device-specific settings**: VorTech's own "Local Control"/"Led
112
125
  Auto Dim" and Radion's own "Max Fan Speed"/"Fan Shutdown"
113
126
  (`get_advanced_features()`), plus Vectra and NYOS Quantum settings
114
127
  (`get_vectra_info()`/`get_coffee_info()` — no real hardware to verify
115
128
  either against, see
116
129
  [known gaps](./documentation/10-known-gaps-and-open-questions.md)).
117
- - **Control scenes**: start feed mode, resume the normal schedule, or any
118
- other configured scene.
119
130
  - **Fix a desynced device clock** (`set_time_to_now()`) — a WRITE.
120
131
  Writing to one device appears to propagate to the rest of its Thread
121
132
  mesh too, confirmed against real hardware — see
@@ -1,9 +1,9 @@
1
- mobius/__init__.py,sha256=f4AxA0NoRP8SEz44ucmCOIDJ7pLbwubew8SmiooX15I,6788
1
+ mobius/__init__.py,sha256=gcj_rnHfu_zczKCP4aBKOuKREHxuvmdXV9zFvMcj4eQ,6788
2
2
  mobius/cli.py,sha256=antaLPQysyPethop24fjeKowczEvfoVodgYkyIT5YxE,50008
3
3
  mobius/coap.py,sha256=dRE4yUp413etAMhDg2bLvFMLWusqjEWgUpC36i76mJs,10966
4
4
  mobius/constants.py,sha256=ytpf9S_cHBKFWi_scr06ydmv-MW1dLGlG3CYfOAkiAw,42480
5
5
  mobius/crc.py,sha256=kLtAYWZvLbo5_p9ep08ZlLg5Yah8Q2qq0IwTvqbBGW0,2700
6
- mobius/device.py,sha256=sJcAeCRyfsvNTh_kkpLs4pefWzgiZfmucmuG9MevYTM,188683
6
+ mobius/device.py,sha256=YxfIgKWsERx1m-ueU1Bau1cXCWYOf-dzu2z3fUq7IeI,190773
7
7
  mobius/device_status.py,sha256=xlLCMU4gLnp59Uqix679jRdVBY2Jt6oI4EUr8fraeRg,34089
8
8
  mobius/discovery.py,sha256=Q_Z0riOBrEOwqpwAhk1wesh3340fiIYFi-8BWqhZ0JI,11949
9
9
  mobius/dump.py,sha256=S7-KNeDi9TOETm4ty5DwMXTrthvuMau196UENDQoAb4,16785
@@ -16,8 +16,8 @@ mobius/pump_status.py,sha256=RWTl8ZViCFDbiFU2mX-WAIH_cWhZOZ0M2B0oYXnPx3Y,1598
16
16
  mobius/relay.py,sha256=IG15TtKDjSkmmi_C2cJwPmucX7V4psfamI0hKgTb_v8,24197
17
17
  mobius/scenes.py,sha256=Wt0Ys2x1YjF8MNDEd6kxWqYBIYPdBSbfzXD-L5ZKzag,3592
18
18
  mobius/schedule.py,sha256=kkQgwkFtM3pvik7UFzHKVhPVZrCGSH9AYvAlVA6kAck,10848
19
- python_mobius-0.7.1.dist-info/METADATA,sha256=8JSsNXxyRdn4ZlcmVS_MdJrLAGbZIZke3cnfFGjcKWg,8306
20
- python_mobius-0.7.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
21
- python_mobius-0.7.1.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
22
- python_mobius-0.7.1.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
23
- python_mobius-0.7.1.dist-info/RECORD,,
19
+ python_mobius-0.7.2.dist-info/METADATA,sha256=X1Pa4FH6dPjYwjNRxFaRpu-hHgRn68f5F5eZubCNgqg,9036
20
+ python_mobius-0.7.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
21
+ python_mobius-0.7.2.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
22
+ python_mobius-0.7.2.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
23
+ python_mobius-0.7.2.dist-info/RECORD,,