python-mobius 0.7.0__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
@@ -35,6 +35,7 @@ from .schedule import (
35
35
  LightPrimitive, SchedulePoint, interpolate_light_schedule,
36
36
  PumpPrimitiveValue, PumpSchedulePoint, get_active_pump_block,
37
37
  )
38
+ from .scenes import Scene, ActiveScene, decode_configured_scenes, decode_active_scene
38
39
  from .manufacturer import MOBIUS_COMPANY_ID_ECOTECH, MOBIUS_COMPANY_ID_AQUAILLUMINATION, MOBIUS_COMPANY_IDS, MobiusAdvertisement, parse_manufacturer_data
39
40
  from .modifiers import (
40
41
  AcclimationInfo, is_night_segment,
@@ -71,7 +72,7 @@ from .dump import (
71
72
  enrich_attribute_dump,
72
73
  )
73
74
 
74
- __version__ = "0.7.0"
75
+ __version__ = "0.7.2"
75
76
 
76
77
  __all__ = [
77
78
  "__version__",
@@ -98,6 +99,8 @@ __all__ = [
98
99
  # schedule
99
100
  "LightPrimitive", "SchedulePoint", "interpolate_light_schedule",
100
101
  "PumpPrimitiveValue", "PumpSchedulePoint", "get_active_pump_block",
102
+ # scenes
103
+ "Scene", "ActiveScene", "decode_configured_scenes", "decode_active_scene",
101
104
  # manufacturer
102
105
  "MOBIUS_COMPANY_ID_ECOTECH", "MOBIUS_COMPANY_ID_AQUAILLUMINATION", "MOBIUS_COMPANY_IDS", "MobiusAdvertisement", "parse_manufacturer_data",
103
106
  # modifiers
mobius/cli.py CHANGED
@@ -18,6 +18,7 @@ from .device import MobiusDevice
18
18
  from .relay import RelayedMobiusDevice
19
19
  from .device_status import MeshPeer
20
20
  from .mesh_address import extract_short_address
21
+ from .constants import PrimitiveType, SceneID
21
22
  from .discovery import (
22
23
  scan_for_mobius_devices_with_info, group_by_pan_id, dedupe_by_serial,
23
24
  find_device_by_serial, discover_mesh_peers_via_direct_connect, discover_tank,
@@ -99,6 +100,37 @@ async def _dump_schedule(device: MobiusDevice, support: str) -> None:
99
100
  print(f" (schedule dumping not supported for support={support!r})")
100
101
 
101
102
 
103
+ async def _dump_scenes(device: MobiusDevice, primitive: PrimitiveType | None) -> None:
104
+ """Prints every configured scene, at whatever slot it lives in,
105
+ including empty/unused slots. The currently active scene is
106
+ already shown by the summary above (info["current_scene"])."""
107
+ print(" --- configured scenes ---")
108
+ scenes = await device.get_configured_scenes(primitive=primitive)
109
+ if not scenes:
110
+ print(" (none)")
111
+ return
112
+ for scene in scenes:
113
+ if scene.scene_type == SceneID.EmptyScene and not scene.name:
114
+ print(f" [{scene.index}] (empty)")
115
+ continue
116
+ type_label = scene.scene_type.name if scene.scene_type else "custom"
117
+ if scene.light is not None:
118
+ payload = ", ".join(
119
+ f"{vid.name}={val}"
120
+ for vid, val in sorted(scene.light.channels.items(), key=lambda x: x[0].name)
121
+ )
122
+ elif scene.pump is not None:
123
+ params = ", ".join(
124
+ f"{p.name}={v.hex() if isinstance(v, bytes) else (v.name if hasattr(v, 'name') else v)}"
125
+ for p, v in scene.pump.params.items()
126
+ )
127
+ payload = f"mode={scene.pump.mode.name} {params}"
128
+ else:
129
+ payload = "(no primitive data decoded)"
130
+ print(f" [{scene.index}] id={scene.id} ({type_label}) name={scene.name!r} "
131
+ f"timeout={scene.timeout}s: {payload}")
132
+
133
+
102
134
  async def _debug_mesh_discovery(device) -> list:
103
135
  """
104
136
  Verbose, step-by-step version of discover_mesh_peers_auto() for CLI
@@ -273,6 +305,10 @@ async def _run(args) -> None:
273
305
  print(f" {k}: {v}")
274
306
  if args.dump_schedule:
275
307
  await _dump_schedule(mdevice, summary.get("support", ""))
308
+ if args.dump_scenes:
309
+ primitive_name = summary.get("primitive_type")
310
+ primitive = PrimitiveType[primitive_name] if primitive_name else None
311
+ await _dump_scenes(mdevice, primitive)
276
312
 
277
313
  relayed = None # set below only if --relay-target resolves successfully
278
314
  if args.dump_mesh_peers or args.relay_target:
@@ -620,6 +656,10 @@ async def _run(args) -> None:
620
656
  print(f" {k}: {v}")
621
657
  if args.dump_schedule:
622
658
  await _dump_schedule(mdevice, summary.get("support", ""))
659
+ if args.dump_scenes:
660
+ primitive_name = summary.get("primitive_type")
661
+ primitive = PrimitiveType[primitive_name] if primitive_name else None
662
+ await _dump_scenes(mdevice, primitive)
623
663
  except Exception as e:
624
664
  print(" failed to connect/read:", e)
625
665
 
@@ -667,6 +707,12 @@ def main() -> None:
667
707
  "hardware -- see LunarPhaseInfo's own docstring) -- compare against "
668
708
  "\"current_intensities\"/\"light_diagnostics\" in the summary output "
669
709
  "above for this library's own calculated value.")
710
+ parser.add_argument("--dump-scenes", action="store_true",
711
+ help="Also print every configured scene on each connected device -- "
712
+ "id, name, timeout, and its own light/pump payload -- at whatever "
713
+ "slot it lives in, including empty/unused slots. The currently "
714
+ "active scene is already shown by the summary above "
715
+ "(\"current_scene\").")
670
716
  parser.add_argument("--dump-mesh-peers", action="store_true",
671
717
  help="Only usable with --by-serial. After connecting, calls "
672
718
  "discover_mesh_peers_auto() on it and prints every other device "
mobius/constants.py CHANGED
@@ -436,6 +436,7 @@ class C2Attribute(IntEnum):
436
436
  SceneTimeout = 402
437
437
  SceneTimer = 403
438
438
  # Scene diagnostics -- named, not yet exposed by a get_*() method.
439
+ LiveDemoSceneNero = 404
439
440
  LiveDemoTimeout = 405
440
441
  LiveDemoTimer = 406
441
442
  LiveDemoScene = 407
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
@@ -43,6 +43,7 @@ from .schedule import (
43
43
  SchedulePoint, interpolate_light_schedule,
44
44
  PumpSchedulePoint, get_active_pump_block,
45
45
  )
46
+ from .scenes import Scene, ActiveScene, decode_configured_scenes, decode_active_scene
46
47
  from .modifiers import (
47
48
  AcclimationInfo, is_night_segment, lunar_percent_reduction,
48
49
  )
@@ -189,6 +190,11 @@ class FullPollResult:
189
190
  caller needing a separate get_pump_schedule() call just to also
190
191
  get schedule_point_count alongside everything else here.
191
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
+
192
198
  used_batch mirrors every other *Result/Snapshot's own field of the
193
199
  same name and purpose.
194
200
  """
@@ -197,6 +203,8 @@ class FullPollResult:
197
203
  light_poll: Optional[LightPollResult]
198
204
  pump_telemetry: Optional[dict]
199
205
  pump_schedule_points: Optional[list[PumpSchedulePoint]] = None
206
+ configured_scenes: list[Scene] = field(default_factory=list)
207
+ current_scene: Optional[ActiveScene] = None
200
208
  used_batch: bool = True
201
209
 
202
210
 
@@ -1276,22 +1284,53 @@ class MobiusDevice:
1276
1284
 
1277
1285
  # ---- scenes -----------------------------------------------------------
1278
1286
 
1279
- async def start_scene(self, scene_id: SceneID, duration_minutes: int = 0) -> None:
1280
- """duration_minutes=0 lets the device use the scene's own configured duration."""
1281
- value = struct.pack("<HH", int(scene_id), duration_minutes)
1282
- await self.set_attribute(C2Attribute.CurrentScene, value)
1287
+ async def start_scene(self, scene_id: int, duration_seconds: int = 0, broadcast: bool = True) -> None:
1288
+ """
1289
+ Activates a scene by id -- one of the ten SceneID presets, or a
1290
+ user-created scene's own device-assigned id (see get_configured_scenes()).
1291
+ duration_seconds=0 lets the device use the scene's own configured
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.
1305
+ """
1306
+ value = struct.pack("<HH", int(scene_id), duration_seconds)
1307
+ reserved = make_reserved(group=1) if broadcast else b"\x00\x00"
1308
+ await self.set_attribute(C2Attribute.CurrentScene, value, reserved=reserved)
1283
1309
 
1284
- async def start_feed_mode(self, duration_minutes: int = 0) -> None:
1285
- await self.start_scene(SceneID.FeedMode, duration_minutes)
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)
1286
1312
 
1287
1313
  async def resume_schedule(self) -> None:
1288
1314
  """Cancel whatever scene is running and go back to the normal schedule."""
1289
1315
  await self.set_attribute(C2Attribute.OperationState, bytes([OperationState.Schedule]))
1290
1316
 
1291
- async def get_current_scene(self) -> tuple[SceneID, int]:
1292
- raw = await self.get_attribute(C2Attribute.CurrentScene)
1293
- scene_id, duration = struct.unpack("<HH", raw[0])
1294
- return SceneID(scene_id), duration
1317
+ async def get_current_scene(self) -> Optional[ActiveScene]:
1318
+ try:
1319
+ raw = await self.get_attribute(C2Attribute.CurrentScene)
1320
+ except Exception:
1321
+ return None
1322
+ return decode_active_scene(raw[0]) if raw else None
1323
+
1324
+ async def get_configured_scenes(self, primitive: Optional[PrimitiveType] = None) -> list[Scene]:
1325
+ """
1326
+ Every configured scene, at whatever slots this device actually
1327
+ has one in -- including slots holding a genuinely empty/unused
1328
+ scene. Pass primitive so each scene's own light/pump payload
1329
+ can be decoded; omit it (or leave as None) to get every other
1330
+ field with light/pump both left None.
1331
+ """
1332
+ raw = await self.get_attribute(C2Attribute.ConfiguredScenes, index=0, count=0xFFFF)
1333
+ return decode_configured_scenes(raw, primitive)
1295
1334
 
1296
1335
  async def get_operation_state(self) -> OperationState:
1297
1336
  raw = await self.get_attribute(C2Attribute.OperationState)
@@ -2931,6 +2970,8 @@ class MobiusDevice:
2931
2970
  info["firmware_versions"] = await self.get_firmware_versions(model)
2932
2971
  info["hardware_info"] = await self.get_hardware_info()
2933
2972
  info["device_time"] = await self.get_device_time_info()
2973
+ current_scene = await self.get_current_scene()
2974
+ info["current_scene"] = current_scene
2934
2975
 
2935
2976
  return info
2936
2977
 
@@ -3634,6 +3675,8 @@ class MobiusDevice:
3634
3675
  (C2Attribute.FirmwareVersion, 0, 0xFFFF),
3635
3676
  (C2Attribute.HardwareRevision, 0, 0xFFFF),
3636
3677
  (schedule_attr, 0, 0xFFFF),
3678
+ (C2Attribute.ConfiguredScenes, 0, 0xFFFF),
3679
+ (C2Attribute.CurrentScene, 0, 1),
3637
3680
  ]
3638
3681
  if primitive == PrimitiveType.VisualV1:
3639
3682
  intensity_attr = C2Attribute.Schedule1Intensity if which == 1 else C2Attribute.Schedule2Intensity
@@ -3705,10 +3748,16 @@ class MobiusDevice:
3705
3748
  [motor_power_raw] if motor_power_raw else [], flow_range, model, primitive,
3706
3749
  )
3707
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
+
3708
3755
  return FullPollResult(
3709
3756
  device_info=device_info, metadata=metadata,
3710
3757
  light_poll=light_poll, pump_telemetry=pump_telemetry,
3711
- 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,
3712
3761
  )
3713
3762
 
3714
3763
  async def _get_full_poll_via_individual_reads(
@@ -3731,10 +3780,17 @@ class MobiusDevice:
3731
3780
  else:
3732
3781
  pump_schedule_points = await self.get_pump_schedule(which=which)
3733
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()
3734
3788
  return FullPollResult(
3735
3789
  device_info=device_info, metadata=metadata,
3736
3790
  light_poll=light_poll, pump_telemetry=pump_telemetry,
3737
- 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,
3738
3794
  )
3739
3795
 
3740
3796
  async def get_current_light_percentages(self, which: int = 1,
mobius/scenes.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ Scenes: a device-side preset that can be activated on demand, separate
3
+ from the normal time-based schedule (Schedule1/Schedule2). Each
4
+ configured scene stores exactly the same kind of payload a schedule
5
+ point would (channel intensities for a light, mode+parameters for a
6
+ pump), plus a name and a timeout, at a fixed slot within
7
+ ConfiguredScenes (400). Activating one writes CurrentScene (401).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import struct
13
+ from dataclasses import dataclass
14
+ from typing import Optional
15
+
16
+ from .constants import SceneID, PrimitiveType, LIGHT_PRIMITIVES, PUMP_PRIMITIVES_VERIFIED, PUMP_PRIMITIVES_EXPERIMENTAL
17
+ from .schedule import LightPrimitive, PumpPrimitiveValue
18
+
19
+ SCENE_HEADER_SIZE = 20 # id(2) + timeout(2) + name(16), before primitiveData
20
+ SCENE_NAME_SIZE = 16
21
+
22
+
23
+ @dataclass
24
+ class Scene:
25
+ """One configured scene, at a fixed slot (`index`) within
26
+ ConfiguredScenes. `scene_type` is one of the ten built-in presets
27
+ if `id` matches one, or None for a user-created scene (whose own
28
+ `id` is a device-assigned value outside that range). Exactly one
29
+ of `light`/`pump` is populated, matching the device's own
30
+ PrimitiveType -- the other stays None."""
31
+ index: int
32
+ id: int
33
+ scene_type: Optional[SceneID]
34
+ name: str
35
+ timeout: int
36
+ light: Optional[LightPrimitive]
37
+ pump: Optional[PumpPrimitiveValue]
38
+
39
+
40
+ @dataclass
41
+ class ActiveScene:
42
+ """The currently active scene (CurrentScene, 401) and how many
43
+ seconds it has left. `scene_type` is None if `id` doesn't match
44
+ one of the ten built-in presets (i.e. a user-created scene is
45
+ active) -- id itself is always present either way."""
46
+ id: int
47
+ scene_type: Optional[SceneID]
48
+ duration_seconds: int
49
+
50
+
51
+ def decode_configured_scenes(values: list[bytes], primitive: Optional[PrimitiveType]) -> list[Scene]:
52
+ """
53
+ Pure decode for ConfiguredScenes (400) -- one call's worth of
54
+ values from a "get all elements" read (index=0, count=0xFFFF),
55
+ one element per slot, in index order. A slot with fewer than
56
+ SCENE_HEADER_SIZE bytes is skipped rather than raising.
57
+ """
58
+ scenes = []
59
+ for index, raw in enumerate(values):
60
+ if len(raw) < SCENE_HEADER_SIZE:
61
+ continue
62
+ scene_id = struct.unpack_from("<H", raw, 0)[0]
63
+ timeout = struct.unpack_from("<H", raw, 2)[0]
64
+ name_bytes = raw[4:4 + SCENE_NAME_SIZE]
65
+ name = name_bytes.split(b"\x00", 1)[0].decode("utf-8", errors="replace")
66
+ primitive_data = raw[SCENE_HEADER_SIZE:]
67
+
68
+ try:
69
+ scene_type = SceneID(scene_id)
70
+ except ValueError:
71
+ scene_type = None
72
+
73
+ light = None
74
+ pump = None
75
+ if primitive in LIGHT_PRIMITIVES:
76
+ light = LightPrimitive.parse(primitive_data)
77
+ elif primitive in PUMP_PRIMITIVES_VERIFIED or primitive in PUMP_PRIMITIVES_EXPERIMENTAL:
78
+ pump = PumpPrimitiveValue.parse(primitive_data)
79
+
80
+ scenes.append(Scene(
81
+ index=index, id=scene_id, scene_type=scene_type, name=name,
82
+ timeout=timeout, light=light, pump=pump,
83
+ ))
84
+ return scenes
85
+
86
+
87
+ def decode_active_scene(raw: bytes) -> Optional[ActiveScene]:
88
+ """Pure decode for CurrentScene (401): id(u16 LE) + duration
89
+ seconds(u16 LE). None if raw is too short to contain both fields."""
90
+ if len(raw) < 4:
91
+ return None
92
+ scene_id, duration = struct.unpack_from("<HH", raw, 0)
93
+ try:
94
+ scene_type = SceneID(scene_id)
95
+ except ValueError:
96
+ scene_type = None
97
+ return ActiveScene(id=scene_id, scene_type=scene_type, duration_seconds=duration)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-mobius
3
- Version: 0.7.0
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=-jv9tL5vbjC5ZFfY1vAOZI6s1371TmIZPrkgjyVc7Yc,6610
2
- mobius/cli.py,sha256=QFphEX10dpPu9bPzUjuUq6K0U9vHG7Kuk-d7BbFfqEs,47541
1
+ mobius/__init__.py,sha256=gcj_rnHfu_zczKCP4aBKOuKREHxuvmdXV9zFvMcj4eQ,6788
2
+ mobius/cli.py,sha256=antaLPQysyPethop24fjeKowczEvfoVodgYkyIT5YxE,50008
3
3
  mobius/coap.py,sha256=dRE4yUp413etAMhDg2bLvFMLWusqjEWgUpC36i76mJs,10966
4
- mobius/constants.py,sha256=LA6COD5QE_x97Ue7hKY0KVarcAQxhxWEARYBJtLIww8,42452
4
+ mobius/constants.py,sha256=ytpf9S_cHBKFWi_scr06ydmv-MW1dLGlG3CYfOAkiAw,42480
5
5
  mobius/crc.py,sha256=kLtAYWZvLbo5_p9ep08ZlLg5Yah8Q2qq0IwTvqbBGW0,2700
6
- mobius/device.py,sha256=2C5Ry52eUFfSx-28DDesPFQuzkt6QDoUu8ETKBsu9ek,187683
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
@@ -14,9 +14,10 @@ mobius/modifiers.py,sha256=b_BLlkSweanYse0VM8TkscGc4It-Dm0Aq2JbNpK7pMU,7308
14
14
  mobius/power.py,sha256=2o91-Cl6tC3x9SKJ9gN0fP5HSTFeD31jtd2BwTyBn30,3913
15
15
  mobius/pump_status.py,sha256=RWTl8ZViCFDbiFU2mX-WAIH_cWhZOZ0M2B0oYXnPx3Y,1598
16
16
  mobius/relay.py,sha256=IG15TtKDjSkmmi_C2cJwPmucX7V4psfamI0hKgTb_v8,24197
17
+ mobius/scenes.py,sha256=Wt0Ys2x1YjF8MNDEd6kxWqYBIYPdBSbfzXD-L5ZKzag,3592
17
18
  mobius/schedule.py,sha256=kkQgwkFtM3pvik7UFzHKVhPVZrCGSH9AYvAlVA6kAck,10848
18
- python_mobius-0.7.0.dist-info/METADATA,sha256=LgBpHt0QN5xw5eKuoA6s3MPpPlZTaGSYVZtOFeUSHwg,8306
19
- python_mobius-0.7.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
20
- python_mobius-0.7.0.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
21
- python_mobius-0.7.0.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
22
- python_mobius-0.7.0.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,,