python-mobius 0.6.0__tar.gz → 0.7.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.6.0 → python_mobius-0.7.1}/CHANGELOG.md +23 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/PKG-INFO +1 -1
- {python_mobius-0.6.0 → python_mobius-0.7.1}/pyproject.toml +1 -1
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/__init__.py +6 -3
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/cli.py +46 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/constants.py +1 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/device.py +913 -226
- python_mobius-0.7.1/src/mobius/scenes.py +97 -0
- python_mobius-0.7.1/tests/test_full_poll_batch.py +229 -0
- python_mobius-0.7.1/tests/test_light_poll_batch.py +203 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_pump_schedule.py +40 -0
- python_mobius-0.7.1/tests/test_scenes.py +203 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/.forgejo/scripts/extract_changelog_section.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/.forgejo/workflows/release.yml +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/.gitignore +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/LICENSE +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/README.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/00-overview.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/01-ble-transport.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/02-framing-and-crc.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/03-attributes-and-opcodes.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/04-device-identity.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/05-scenes.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/06-light-schedule.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/07-pump-schedule.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/08-manufacturer-data.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/09-thread-coap-relay.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/10-known-gaps-and-open-questions.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/11-device-status-attributes.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/12-device-identity-and-address-stability.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/13-attribute-dump.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/documentation/14-batched-attribute-reads.md +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/coap.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/crc.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/device_status.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/discovery.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/dump.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/frame.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/manufacturer.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/mesh_address.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/modifiers.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/power.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/pump_status.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/relay.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/src/mobius/schedule.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_attributes_batch.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_coap.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_coap_indication_handling.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_connection_cleanup.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_device_control.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_device_status.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_device_summary.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_device_time.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_discovery_company_ids.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_dump.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_firmware_versions.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_frame.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_frame_sniffer.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_hyperdrive_device.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_insolation_detection.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_light_intensity_branching.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_light_schedule.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_manufacturer.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_manufacturer_lookup.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_mesh_address.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_mesh_discovery.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_mesh_discovery_direct_connect.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_metadata_batch.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_modifiers.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_networked_thread_devices.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_new_diagnostic_attributes.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_power.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_pump_status.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_pump_telemetry.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_relay.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_schedule_intensity_scalar.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_serial_decoding.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_serial_identity.py +0 -0
- {python_mobius-0.6.0 → python_mobius-0.7.1}/tests/test_supported_attributes.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
- Added scene support: get_configured_scenes(), get_current_scene(),
|
|
6
|
+
start_scene(). Fixed get_current_scene() crashing on a custom
|
|
7
|
+
(non-preset) scene, and start_scene()'s duration being in seconds,
|
|
8
|
+
not minutes as previously labeled.
|
|
9
|
+
|
|
10
|
+
## 0.7.0
|
|
11
|
+
|
|
12
|
+
- Added get_full_poll_batch() -- one round-trip for device identity +
|
|
13
|
+
metadata + light/pump state, confirmed 2-2.6x faster on real
|
|
14
|
+
hardware. Requires the caller to already know the device's own
|
|
15
|
+
PrimitiveType (permanent for its lifetime, so never re-fetched).
|
|
16
|
+
- get_current_pump_block() now accepts an optional pre-fetched
|
|
17
|
+
`points` param, skipping its own redundant re-fetch of the same
|
|
18
|
+
schedule attribute.
|
|
19
|
+
- Added get_light_poll_batch() -- one round-trip for a light's own
|
|
20
|
+
schedule, lunar/acclimation/insolation state, and Epoch/LocalTime,
|
|
21
|
+
falling back to the original individual reads if the batch fails.
|
|
22
|
+
- Extracted every light-schedule-related method's own decode/compute
|
|
23
|
+
logic into pure, standalone functions, laying the groundwork for
|
|
24
|
+
get_light_poll_batch() above. No behavior change.
|
|
25
|
+
|
|
3
26
|
## 0.6.0
|
|
4
27
|
|
|
5
28
|
- get_metadata_batch() now falls back to individual reads if the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-mobius"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.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" }
|
|
@@ -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,
|
|
@@ -56,7 +57,7 @@ from .mesh_address import (
|
|
|
56
57
|
)
|
|
57
58
|
from .relay import RelayedMobiusDevice
|
|
58
59
|
from .device import (
|
|
59
|
-
MobiusDevice, MobiusPump, LightIntensityResult,
|
|
60
|
+
MobiusDevice, MobiusPump, LightIntensityResult, LightPollResult, FullPollResult,
|
|
60
61
|
SERVICE_GENERAL, CHAR_RX_DATA, CHAR_RX_FINAL, CHAR_TX_DATA, CHAR_TX_FINAL,
|
|
61
62
|
)
|
|
62
63
|
from .discovery import (
|
|
@@ -71,7 +72,7 @@ from .dump import (
|
|
|
71
72
|
enrich_attribute_dump,
|
|
72
73
|
)
|
|
73
74
|
|
|
74
|
-
__version__ = "0.
|
|
75
|
+
__version__ = "0.7.1"
|
|
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
|
|
@@ -117,7 +120,7 @@ __all__ = [
|
|
|
117
120
|
"build_rloc_address", "extract_short_address", "is_short_address_derived",
|
|
118
121
|
"RelayedMobiusDevice",
|
|
119
122
|
# device
|
|
120
|
-
"MobiusDevice", "MobiusPump", "LightIntensityResult",
|
|
123
|
+
"MobiusDevice", "MobiusPump", "LightIntensityResult", "LightPollResult", "FullPollResult",
|
|
121
124
|
"SERVICE_GENERAL", "CHAR_RX_DATA", "CHAR_RX_FINAL", "CHAR_TX_DATA", "CHAR_TX_FINAL",
|
|
122
125
|
# discovery
|
|
123
126
|
"scan_for_mobius_devices", "scan_for_mobius_devices_with_info", "group_by_pan_id",
|
|
@@ -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 "
|