python-mobius 0.4.3__py3-none-any.whl → 0.5.0__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 +5 -5
- mobius/cli.py +98 -6
- mobius/constants.py +311 -91
- mobius/device.py +492 -61
- mobius/device_status.py +146 -6
- mobius/discovery.py +11 -4
- mobius/manufacturer.py +33 -14
- mobius/modifiers.py +6 -6
- mobius/relay.py +37 -2
- mobius/schedule.py +5 -6
- {python_mobius-0.4.3.dist-info → python_mobius-0.5.0.dist-info}/METADATA +37 -8
- python_mobius-0.5.0.dist-info/RECORD +21 -0
- python_mobius-0.4.3.dist-info/RECORD +0 -21
- {python_mobius-0.4.3.dist-info → python_mobius-0.5.0.dist-info}/WHEEL +0 -0
- {python_mobius-0.4.3.dist-info → python_mobius-0.5.0.dist-info}/entry_points.txt +0 -0
- {python_mobius-0.4.3.dist-info → python_mobius-0.5.0.dist-info}/licenses/LICENSE +0 -0
mobius/__init__.py
CHANGED
|
@@ -35,7 +35,7 @@ from .schedule import (
|
|
|
35
35
|
LightPrimitive, SchedulePoint, interpolate_light_schedule,
|
|
36
36
|
PumpPrimitiveValue, PumpSchedulePoint, get_active_pump_block,
|
|
37
37
|
)
|
|
38
|
-
from .manufacturer import
|
|
38
|
+
from .manufacturer import MOBIUS_COMPANY_ID_ECOTECH, MOBIUS_COMPANY_ID_AQUAILLUMINATION, MOBIUS_COMPANY_IDS, MobiusAdvertisement, parse_manufacturer_data
|
|
39
39
|
from .modifiers import (
|
|
40
40
|
AcclimationInfo, is_night_segment,
|
|
41
41
|
julian_day_from_date, lunar_days_into_phase, lunar_percent_reduction,
|
|
@@ -43,7 +43,7 @@ from .modifiers import (
|
|
|
43
43
|
)
|
|
44
44
|
from .power import ChannelPowerInfo, channel_percent_value
|
|
45
45
|
from .pump_status import PumpFlowRange, BatteryBackupInfo, BoostedBatteryInfo
|
|
46
|
-
from .device_status import GroupInfo, CalibrationInfo, MaintenanceInfo, DeviceTimeInfo, MeshPeer, Tank
|
|
46
|
+
from .device_status import GroupInfo, CalibrationInfo, AdvancedFeatures, VectraInfo, CoffeeInfo, MaintenanceInfo, DeviceTimeInfo, MeshPeer, Tank, LunarPhaseInfo
|
|
47
47
|
from .coap import (
|
|
48
48
|
CoapRequestType, CoapMethod, CoapResponseCode, CoapResponse,
|
|
49
49
|
encode_coap_request, decode_coap_response, COAP_OPCODE, COAP_INDICATION_OPCODE,
|
|
@@ -65,7 +65,7 @@ from .discovery import (
|
|
|
65
65
|
discover_tank,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
__version__ = "0.
|
|
68
|
+
__version__ = "0.5.0"
|
|
69
69
|
|
|
70
70
|
__all__ = [
|
|
71
71
|
"__version__",
|
|
@@ -93,7 +93,7 @@ __all__ = [
|
|
|
93
93
|
"LightPrimitive", "SchedulePoint", "interpolate_light_schedule",
|
|
94
94
|
"PumpPrimitiveValue", "PumpSchedulePoint", "get_active_pump_block",
|
|
95
95
|
# manufacturer
|
|
96
|
-
"
|
|
96
|
+
"MOBIUS_COMPANY_ID_ECOTECH", "MOBIUS_COMPANY_ID_AQUAILLUMINATION", "MOBIUS_COMPANY_IDS", "MobiusAdvertisement", "parse_manufacturer_data",
|
|
97
97
|
# modifiers
|
|
98
98
|
"AcclimationInfo", "is_night_segment",
|
|
99
99
|
"julian_day_from_date", "lunar_days_into_phase", "lunar_percent_reduction",
|
|
@@ -103,7 +103,7 @@ __all__ = [
|
|
|
103
103
|
# pump_status
|
|
104
104
|
"PumpFlowRange", "BatteryBackupInfo", "BoostedBatteryInfo",
|
|
105
105
|
# device_status
|
|
106
|
-
"GroupInfo", "CalibrationInfo", "MaintenanceInfo", "DeviceTimeInfo", "MeshPeer", "Tank",
|
|
106
|
+
"GroupInfo", "CalibrationInfo", "AdvancedFeatures", "VectraInfo", "CoffeeInfo", "MaintenanceInfo", "DeviceTimeInfo", "MeshPeer", "Tank", "LunarPhaseInfo",
|
|
107
107
|
"CoapRequestType", "CoapMethod", "CoapResponseCode", "CoapResponse",
|
|
108
108
|
"encode_coap_request", "decode_coap_response", "COAP_OPCODE", "COAP_INDICATION_OPCODE",
|
|
109
109
|
"next_coap_token",
|
mobius/cli.py
CHANGED
|
@@ -60,10 +60,27 @@ async def _dump_schedule(device: MobiusDevice, support: str) -> None:
|
|
|
60
60
|
print(f" {_format_time(point.time_minutes)}{_format_flags(point)}: {channels}")
|
|
61
61
|
|
|
62
62
|
lunar_enabled = await device.get_lunar_enabled()
|
|
63
|
+
print(f" lunar_enabled: {lunar_enabled}")
|
|
64
|
+
|
|
65
|
+
# The device's own firmware-computed lunar state -- see
|
|
66
|
+
# LunarPhaseInfo's own docstring in device_status.py for the
|
|
67
|
+
# confirmation and what this is useful for. Compare against
|
|
68
|
+
# "current_intensities"/"light_diagnostics" in the summary
|
|
69
|
+
# output above (NOT re-fetched here -- get_device_summary()
|
|
70
|
+
# already called get_current_light_intensities() once; a
|
|
71
|
+
# second call here would just be a redundant device read of
|
|
72
|
+
# the exact same thing). A real failure is shown directly
|
|
73
|
+
# (get_lunar_phase_info() no longer swallows it) rather than
|
|
74
|
+
# printing an indistinguishable "None".
|
|
75
|
+
try:
|
|
76
|
+
lunar_phase_info = await device.get_lunar_phase_info()
|
|
77
|
+
print(f" lunar_phase_info: {lunar_phase_info}")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f" lunar_phase_info: read failed: {e}")
|
|
80
|
+
|
|
63
81
|
insolation_active = await device.get_insolation_enabled()
|
|
64
82
|
schedule_intensity = await device.get_schedule_intensity()
|
|
65
83
|
acclimation = await device.get_acclimation_info()
|
|
66
|
-
print(f" lunar_enabled: {lunar_enabled}")
|
|
67
84
|
print(f" insolation_active: {insolation_active}")
|
|
68
85
|
print(f" schedule_intensity: {schedule_intensity}")
|
|
69
86
|
print(f" acclimation: {acclimation}")
|
|
@@ -241,7 +258,14 @@ async def _run(args) -> None:
|
|
|
241
258
|
device, info = found
|
|
242
259
|
print(f" currently at {device.address} model={info.model.name if info.model else info.model_raw}")
|
|
243
260
|
try:
|
|
244
|
-
|
|
261
|
+
# device= is the exact BLEDevice found_device_by_serial() just
|
|
262
|
+
# located above -- passing it avoids a second, independent scan
|
|
263
|
+
# inside connect() (see MobiusDevice.__init__()'s own docstring:
|
|
264
|
+
# re-scanning per device can cause BlueZ to drop it between
|
|
265
|
+
# "found" and "connect", the exact failure this fixes). serial=
|
|
266
|
+
# is still passed alongside it, for this same instance's own
|
|
267
|
+
# reconnect logic if the connection drops later in this run.
|
|
268
|
+
async with MobiusDevice(device=device, serial=args.by_serial, adapter=args.adapter,
|
|
245
269
|
connect_timeout=args.connect_timeout) as mdevice:
|
|
246
270
|
summary = await mdevice.get_device_summary()
|
|
247
271
|
for k, v in summary.items():
|
|
@@ -249,6 +273,7 @@ async def _run(args) -> None:
|
|
|
249
273
|
if args.dump_schedule:
|
|
250
274
|
await _dump_schedule(mdevice, summary.get("support", ""))
|
|
251
275
|
|
|
276
|
+
relayed = None # set below only if --relay-target resolves successfully
|
|
252
277
|
if args.dump_mesh_peers or args.relay_target:
|
|
253
278
|
print("\n=== Thread mesh peer discovery ===")
|
|
254
279
|
peers = await _debug_mesh_discovery(mdevice)
|
|
@@ -314,6 +339,47 @@ async def _run(args) -> None:
|
|
|
314
339
|
f"`--by-serial {args.relay_target}` run to confirm they match.")
|
|
315
340
|
except Exception as e:
|
|
316
341
|
print(f" relay failed: {e}")
|
|
342
|
+
|
|
343
|
+
if args.reboot:
|
|
344
|
+
# If --relay-target was also given, reboot THAT device
|
|
345
|
+
# via relay through the connected one, instead of the
|
|
346
|
+
# directly-connected device itself -- lets both the
|
|
347
|
+
# direct and relayed write paths get tested with the
|
|
348
|
+
# same flag, without a separate one. If relay resolution
|
|
349
|
+
# above failed, this deliberately does NOT fall back to
|
|
350
|
+
# rebooting the directly-connected device instead --
|
|
351
|
+
# that would silently reboot a device the user didn't
|
|
352
|
+
# actually ask to reboot.
|
|
353
|
+
if args.relay_target:
|
|
354
|
+
if relayed is None:
|
|
355
|
+
print(f"\n=== reboot() on {args.relay_target!r} (via relay) -- "
|
|
356
|
+
f"SKIPPED: relay target could not be resolved above ===")
|
|
357
|
+
reboot_target = None
|
|
358
|
+
else:
|
|
359
|
+
reboot_target_label = f"{args.relay_target!r} (via relay through {args.by_serial!r})"
|
|
360
|
+
reboot_target = relayed
|
|
361
|
+
else:
|
|
362
|
+
reboot_target_label = f"{args.by_serial!r} (direct)"
|
|
363
|
+
reboot_target = mdevice
|
|
364
|
+
|
|
365
|
+
if reboot_target is not None:
|
|
366
|
+
print(f"\n=== reboot() on {reboot_target_label} (WRITE -- Reset, soft) ===")
|
|
367
|
+
try:
|
|
368
|
+
await reboot_target.reboot()
|
|
369
|
+
print(f" reboot() on {reboot_target_label} succeeded (device "
|
|
370
|
+
f"acknowledged the request -- it may take a few seconds to "
|
|
371
|
+
f"actually finish rebooting and reconnect).")
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print(f" reboot() FAILED: {e}")
|
|
374
|
+
|
|
375
|
+
if args.set_time_to_now:
|
|
376
|
+
print(f"\n=== set_time_to_now() on {args.by_serial!r} (WRITE -- Epoch) ===")
|
|
377
|
+
try:
|
|
378
|
+
await mdevice.set_time_to_now()
|
|
379
|
+
print(f" set_time_to_now() on {args.by_serial!r} succeeded.")
|
|
380
|
+
except Exception as e:
|
|
381
|
+
print(f" set_time_to_now() FAILED: {e}")
|
|
382
|
+
|
|
317
383
|
except Exception as e:
|
|
318
384
|
print(" failed to connect/read:", e)
|
|
319
385
|
return
|
|
@@ -365,7 +431,10 @@ async def _run(args) -> None:
|
|
|
365
431
|
device, info = connectable[0]
|
|
366
432
|
print(f"\n=== Network addresses (connecting to {info.serial} to ask) ===")
|
|
367
433
|
try:
|
|
368
|
-
|
|
434
|
+
# device= already found above via the scan feeding `connectable`
|
|
435
|
+
# -- see the --by-serial path's own comment for why this avoids
|
|
436
|
+
# a redundant, race-prone second scan inside connect().
|
|
437
|
+
async with MobiusDevice(device=device, serial=info.serial, adapter=args.adapter,
|
|
369
438
|
connect_timeout=args.connect_timeout) as mdevice:
|
|
370
439
|
peers = await _debug_mesh_discovery(mdevice)
|
|
371
440
|
except Exception as e:
|
|
@@ -398,7 +467,10 @@ async def _run(args) -> None:
|
|
|
398
467
|
device, info = connectable[0]
|
|
399
468
|
print(f" pan_id {label} (connecting through {info.serial})...")
|
|
400
469
|
try:
|
|
401
|
-
|
|
470
|
+
# device= already found above -- see the --by-serial path's
|
|
471
|
+
# own comment for why this avoids a redundant, race-prone
|
|
472
|
+
# second scan inside connect().
|
|
473
|
+
async with MobiusDevice(device=device, serial=info.serial, adapter=args.adapter,
|
|
402
474
|
connect_timeout=args.connect_timeout) as mdevice:
|
|
403
475
|
tank = await discover_tank(mdevice)
|
|
404
476
|
except Exception as e:
|
|
@@ -508,8 +580,11 @@ def main() -> None:
|
|
|
508
580
|
"(every point's time, flags, and per-channel/mode values) -- not "
|
|
509
581
|
"just the interpolated 'current' value get_device_summary() shows. "
|
|
510
582
|
"For lights, also prints lunar/insolation/schedule-intensity/"
|
|
511
|
-
"acclimation state
|
|
512
|
-
"
|
|
583
|
+
"acclimation state, PLUS the device's own firmware-computed lunar "
|
|
584
|
+
"state (LunarPhasesCurrentDay/CurrentScalar, confirmed against real "
|
|
585
|
+
"hardware -- see LunarPhaseInfo's own docstring) -- compare against "
|
|
586
|
+
"\"current_intensities\"/\"light_diagnostics\" in the summary output "
|
|
587
|
+
"above for this library's own calculated value.")
|
|
513
588
|
parser.add_argument("--dump-mesh-peers", action="store_true",
|
|
514
589
|
help="Only usable with --by-serial. After connecting, calls "
|
|
515
590
|
"discover_mesh_peers_auto() on it and prints every other device "
|
|
@@ -542,6 +617,23 @@ def main() -> None:
|
|
|
542
617
|
"confirmed-meaningless opcode-25 ack and the real opcode-26 "
|
|
543
618
|
"indication, if it arrives). Useful for diagnosing a relay that "
|
|
544
619
|
"times out or returns unexpected data.")
|
|
620
|
+
parser.add_argument("--set-time-to-now", action="store_true",
|
|
621
|
+
help="Only usable with --by-serial. A WRITE -- use on hardware you're "
|
|
622
|
+
"prepared to have its clock changed on. Calls set_time_to_now() "
|
|
623
|
+
"on the directly-connected device (Epoch, reserved-byte group=1 "
|
|
624
|
+
"-- required for the write to be accepted at all, confirmed "
|
|
625
|
+
"against real hardware). See that method's own docstring, and "
|
|
626
|
+
"documentation/09-thread-coap-relay.md, for what's confirmed "
|
|
627
|
+
"about this vs the rest of the mesh.")
|
|
628
|
+
parser.add_argument("--reboot", action="store_true",
|
|
629
|
+
help="Only usable with --by-serial. A WRITE -- use on hardware you're "
|
|
630
|
+
"prepared to have reboot. Soft-reboots the directly-connected "
|
|
631
|
+
"device (Reset, ResetType.Soft) -- confirmed via the app's own "
|
|
632
|
+
"source to be exactly its own \"Restart\" button. If "
|
|
633
|
+
"--relay-target is also given, reboots THAT device via relay "
|
|
634
|
+
"through the connected one instead, to test the relayed write "
|
|
635
|
+
"path specifically -- see reboot()'s own docstring for what's "
|
|
636
|
+
"confirmed about this vs a direct connection.")
|
|
545
637
|
parser.add_argument("--build-peer-map", action="store_true",
|
|
546
638
|
help="Instead of full device summaries, scan for devices then "
|
|
547
639
|
"briefly connect to EACH one in turn to build a mesh peer map "
|