python-mobius 0.8.2__tar.gz → 0.8.3__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.8.2 → python_mobius-0.8.3}/CHANGELOG.md +6 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/PKG-INFO +1 -1
- {python_mobius-0.8.2 → python_mobius-0.8.3}/pyproject.toml +1 -1
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/__init__.py +1 -1
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/device.py +29 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_device_control.py +33 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/.forgejo/scripts/extract_changelog_section.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/.forgejo/workflows/release.yml +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/.gitignore +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/LICENSE +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/README.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/00-overview.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/01-ble-transport.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/02-framing-and-crc.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/03-attributes-and-opcodes.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/04-device-identity.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/05-scenes.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/06-light-schedule.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/07-pump-schedule.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/08-manufacturer-data.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/09-thread-coap-relay.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/10-known-gaps-and-open-questions.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/11-device-status-attributes.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/12-device-identity-and-address-stability.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/13-attribute-dump.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/14-batched-attribute-reads.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/15-mob-template-format.md +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/cli.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/coap.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/constants.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/crc.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/device_status.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/discovery.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/dump.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/frame.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/manufacturer.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/mesh_address.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/mob.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/modifiers.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/power.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/pump_status.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/relay.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/scenes.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/src/mobius/schedule.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_attributes_batch.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_cli_group_targets.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_cli_mob_file.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_cli_schedule_file.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_coap.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_coap_indication_handling.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_connection_cleanup.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_device_status.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_device_summary.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_device_time.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_discovery_company_ids.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_dump.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_extra_physical_values.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_fan_thresholds.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_firmware_versions.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_frame.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_frame_sniffer.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_full_poll_batch.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_group_bitmap.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_hyperdrive_device.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_insolation_detection.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_light_intensity_branching.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_light_poll_batch.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_light_schedule.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_manufacturer.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_manufacturer_lookup.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_mesh_address.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_mesh_discovery.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_mesh_discovery_direct_connect.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_metadata_batch.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_mob.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_modifiers.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_networked_thread_devices.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_new_diagnostic_attributes.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_power.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_pump_reverse.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_pump_schedule.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_pump_status.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_pump_supported_modes.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_pump_telemetry.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_relay.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_scenes.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_schedule_file_format.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_schedule_intensity_scalar.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_serial_decoding.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_serial_identity.py +0 -0
- {python_mobius-0.8.2 → python_mobius-0.8.3}/tests/test_supported_attributes.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.3
|
|
4
|
+
|
|
5
|
+
- Added `set_lunar_enabled(enabled)`. Confirmed byte-for-byte against
|
|
6
|
+
the app's own compiled bytecode: exactly what its "Lunar" chip (top
|
|
7
|
+
of the light schedule editor) writes when toggled.
|
|
8
|
+
|
|
3
9
|
## 0.8.2
|
|
4
10
|
|
|
5
11
|
- Added `LightPollResult.schedule_intensity`: the device's own raw
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.3
|
|
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.8.
|
|
7
|
+
version = "0.8.3"
|
|
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" }
|
|
@@ -3463,6 +3463,35 @@ class MobiusDevice:
|
|
|
3463
3463
|
return False
|
|
3464
3464
|
return decode_lunar_enabled(raw)
|
|
3465
3465
|
|
|
3466
|
+
async def set_lunar_enabled(self, enabled: bool) -> None:
|
|
3467
|
+
"""Writes LunarPhasesEnabled (907) -- confirmed byte-for-byte
|
|
3468
|
+
against the app's own compiled bytecode (LunarInfo.smali's own
|
|
3469
|
+
save() method): a single byte, the boolean enabled field stored
|
|
3470
|
+
directly as 0 or 1, via the exact same SetC2AttrFsciRequest.
|
|
3471
|
+
addAttribute() mechanism this library's own set_attribute()
|
|
3472
|
+
uses. This is exactly what the app's own "Lunar" chip (top of
|
|
3473
|
+
the light schedule editor) toggles: clicking it turns
|
|
3474
|
+
lunar-phase night-time dimming on/off, and while enabled that
|
|
3475
|
+
same chip also displays the current moon phase.
|
|
3476
|
+
|
|
3477
|
+
No group/broadcast involved -- confirmed by tracing
|
|
3478
|
+
LightingFragment's own saveLunar(), which loops over every
|
|
3479
|
+
light device in the tank and calls this same per-device,
|
|
3480
|
+
ungrouped write on EACH one individually (not a single
|
|
3481
|
+
mesh-wide broadcast the way set_time_to_now() works). The
|
|
3482
|
+
app's own UI applies the same new value to every light in the
|
|
3483
|
+
tank with that one tap, but the underlying wire mechanism is
|
|
3484
|
+
still one ordinary, targeted write per device -- this method
|
|
3485
|
+
only ever writes to the single device it's called on, matching
|
|
3486
|
+
every other attribute write in this library.
|
|
3487
|
+
|
|
3488
|
+
No which/schedule parameter -- unlike Schedule1Intensity/
|
|
3489
|
+
Schedule2Intensity, there's only ever a single
|
|
3490
|
+
LunarPhasesEnabled attribute (confirmed: no LunarPhasesEnabled2
|
|
3491
|
+
exists), so this applies to the light as a whole rather than to
|
|
3492
|
+
either schedule individually."""
|
|
3493
|
+
await self.set_attribute(C2Attribute.LunarPhasesEnabled, bytes([1 if enabled else 0]))
|
|
3494
|
+
|
|
3466
3495
|
async def get_acclimation_info(self) -> Optional[AcclimationInfo]:
|
|
3467
3496
|
"""
|
|
3468
3497
|
Fetches all 4 acclimation attributes (AcclimationEnabled/Period/
|
|
@@ -64,6 +64,39 @@ async def test_reboot_propagates_a_write_failure():
|
|
|
64
64
|
await device.reboot()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
async def test_set_lunar_enabled_writes_a_single_byte():
|
|
68
|
+
"""Confirmed directly against the app's own behavior: LunarPhasesEnabled
|
|
69
|
+
(907) is exactly what the app's own "Lunar" chip (top of the light
|
|
70
|
+
schedule editor) toggles."""
|
|
71
|
+
device = MobiusDevice(_fake_ble_device())
|
|
72
|
+
captured = {}
|
|
73
|
+
|
|
74
|
+
async def fake_set_attribute(attr_id, value, index=0, reserved=b"\x00\x00"):
|
|
75
|
+
captured["attr_id"] = attr_id
|
|
76
|
+
captured["value"] = value
|
|
77
|
+
|
|
78
|
+
device.set_attribute = fake_set_attribute
|
|
79
|
+
|
|
80
|
+
await device.set_lunar_enabled(True)
|
|
81
|
+
assert captured["attr_id"] == C2Attribute.LunarPhasesEnabled
|
|
82
|
+
assert captured["value"] == bytes([1])
|
|
83
|
+
|
|
84
|
+
await device.set_lunar_enabled(False)
|
|
85
|
+
assert captured["value"] == bytes([0])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def test_set_lunar_enabled_propagates_a_write_failure():
|
|
89
|
+
device = MobiusDevice(_fake_ble_device())
|
|
90
|
+
|
|
91
|
+
async def fake_set_attribute(attr_id, value, index=0, reserved=b"\x00\x00"):
|
|
92
|
+
raise IOError("device returned FSCI status Failed setting attribute 907")
|
|
93
|
+
|
|
94
|
+
device.set_attribute = fake_set_attribute
|
|
95
|
+
|
|
96
|
+
with pytest.raises(IOError, match="Failed setting attribute 907"):
|
|
97
|
+
await device.set_lunar_enabled(True)
|
|
98
|
+
|
|
99
|
+
|
|
67
100
|
@pytest.mark.asyncio
|
|
68
101
|
async def test_reboot_all_writes_reset_soft_with_group_1_reserved_byte():
|
|
69
102
|
"""Confirmed via the app's own SettingsFragment.reboot() (the
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_mobius-0.8.2 → python_mobius-0.8.3}/documentation/10-known-gaps-and-open-questions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|